From 930a915de89ce6b3ae4d1b902304df3b0fb507cc Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 15 May 2026 09:56:07 -0400 Subject: drm/amdgpu: don't reemit if there is nothing to reemit Return early in amdgpu_ring_set_fence_errors_and_reemit() if ring_backup_entries_to_copy is 0. That means that either the ring is idle and there is nothing to reemit, or there some reason why we should reemit, so return early and signal the fences (if applicable). Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index ea69b1bac7c6..6a43c8494fa8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -727,6 +727,15 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; + /* If there is nothing to reemit, return early and set an error on the fence + * if applicable. If all of the fences are siganlled, this will be a nop. + * if there are still fences and ring_backup_entries_to_copy is 0, then + * we are skipping it on purpose. + */ + if (!ring->ring_backup_entries_to_copy) { + amdgpu_fence_driver_force_completion(ring, &guilty_fence->base); + return; + } ring->reemit = true; amdgpu_ring_alloc(ring, ring->ring_backup_entries_to_copy); spin_lock_irqsave(&ring->fence_drv.lock, flags); -- cgit v1.2.3 From ce3f23a780851f848ae63b89f6ad51d86dfe33b5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 15 May 2026 10:16:51 -0400 Subject: drm/amdgpu: track guilty fence for queue reset If we've already seen a fence, don't backup the ring contents since presumably either the previous reset was not successful or there was something wrong with the data. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 11 +++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 1 + 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 6a43c8494fa8..a7a6db0bc694 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -803,6 +803,17 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; ring->ring_backup_entries_to_copy = 0; + /* if we've already seen this fence, return early. + * ring->ring_backup_entries_to_copy is set to 0 so + * the reemit helper will return early as well to + * avoid getting stuck in a reemit loop. + */ + if (ring->guilty_fence == guilty_fence) { + ring->guilty_fence = NULL; + return; + } + ring->guilty_fence = guilty_fence; + do { last_seq++; last_seq &= ring->fence_drv.num_fences_mask; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index 8f28b3bd7010..9276a3bb69de 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -314,6 +314,7 @@ struct amdgpu_ring { uint32_t *ring_backup; unsigned int ring_backup_entries_to_copy; bool reemit; + struct amdgpu_fence *guilty_fence; unsigned rptr_offs; u64 rptr_gpu_addr; u32 *rptr_cpu_addr; -- cgit v1.2.3 From 36ed61b1c01a24fd3891d1e01025751d7d0603ac Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 12 May 2026 10:23:02 -0400 Subject: drm/amdgpu/fence: add helper to extract the guilty fence Add a helper to extract the first amdgpu_fence which has not yet signalled and is thus guilty or at least collateral damage. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 31 +++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 2 ++ 2 files changed, 33 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index a7a6db0bc694..733e9b668ed8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -831,6 +831,37 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, } while (last_seq != seq); } +struct amdgpu_fence * +amdgpu_ring_find_guilty_fence(struct amdgpu_ring *ring) +{ + struct dma_fence *unprocessed; + struct dma_fence __rcu **ptr; + struct amdgpu_fence *fence; + u32 seq, last_seq; + + last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; + seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; + ring->ring_backup_entries_to_copy = 0; + + do { + last_seq++; + last_seq &= ring->fence_drv.num_fences_mask; + + ptr = &ring->fence_drv.fences[last_seq]; + rcu_read_lock(); + unprocessed = rcu_dereference(*ptr); + + if (unprocessed && !dma_fence_is_signaled(unprocessed)) { + fence = container_of(unprocessed, struct amdgpu_fence, base); + rcu_read_unlock(); + return fence; + } + rcu_read_unlock(); + } while (last_seq != seq); + + return NULL; +} + /* * Common fence implementation */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index 9276a3bb69de..71cd9bb12f75 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -589,6 +589,8 @@ 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, struct amdgpu_fence *guilty_fence); +struct amdgpu_fence * +amdgpu_ring_find_guilty_fence(struct amdgpu_ring *ring); void amdgpu_ring_reset_helper_begin(struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence); int amdgpu_ring_reset_helper_end(struct amdgpu_ring *ring, -- cgit v1.2.3 From 714d354479b132c411b9f1771c4868616ed0f5c0 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 12 May 2026 10:32:29 -0400 Subject: drm/amdgpu: amdgpu_ring_set_fence_errors_and_reemit() handle NULL fence All the guilty fence parameter to be NULL. Will be needed for future functionality. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 733e9b668ed8..8569c1c637a2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -750,7 +750,8 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, if (unprocessed && !dma_fence_is_signaled_locked(unprocessed)) { fence = container_of(unprocessed, struct amdgpu_fence, base); is_guilty_fence = fence == guilty_fence; - is_guilty_context = fence->context == guilty_fence->context; + is_guilty_context = guilty_fence ? + (fence->context == guilty_fence->context) : false; /* mark all fences from the guilty context with an error */ if (is_guilty_fence) -- cgit v1.2.3 From 659fe71521358f5bb9ac740a279ce868a32cd31f Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 12 May 2026 12:55:57 -0400 Subject: drm/amdgpu/vcn: handle pipe reset more gracefully Save any unprocessed work in the queues using the new ring helper. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c | 64 ++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c index 616967519869..e4d435d4a629 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c @@ -1485,6 +1485,37 @@ int vcn_set_powergating_state(struct amdgpu_ip_block *ip_block, return ret; } +static struct amdgpu_fence * +amdgpu_vcn_ring_reset_begin_helper(struct amdgpu_ring *ring, + struct amdgpu_ring *guilty_ring, + struct amdgpu_fence *timedout_fence) +{ + struct amdgpu_fence *fence; + + drm_sched_wqueue_stop(&ring->sched); + if (ring == guilty_ring) + fence = timedout_fence; + else + fence = amdgpu_ring_find_guilty_fence(ring); + amdgpu_ring_reset_helper_begin(ring, fence); + + return fence; +} + +static int +amdgpu_vcn_ring_reset_end_helper(struct amdgpu_ring *ring, + struct amdgpu_fence *fence) +{ + int r; + + r = amdgpu_ring_reset_helper_end(ring, fence); + if (r) + return r; + + drm_sched_wqueue_start(&ring->sched); + return 0; +} + /** * amdgpu_vcn_ring_reset - Reset a VCN ring * @ring: ring to reset @@ -1502,48 +1533,33 @@ int amdgpu_vcn_ring_reset(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; struct amdgpu_vcn_inst *vinst = &adev->vcn.inst[ring->me]; + struct amdgpu_fence *dec_fence; + struct amdgpu_fence *enc_fence[AMDGPU_VCN_MAX_ENC_RINGS]; int r, i; if (adev->vcn.inst[ring->me].using_unified_queue) return -EINVAL; mutex_lock(&vinst->engine_reset_mutex); - /* Stop the scheduler's work queue for the dec and enc rings if they are running. - * This ensures that no new tasks are submitted to the queues while - * the reset is in progress. - */ - drm_sched_wqueue_stop(&vinst->ring_dec.sched); + dec_fence = amdgpu_vcn_ring_reset_begin_helper(&vinst->ring_dec, ring, + timedout_fence); for (i = 0; i < vinst->num_enc_rings; i++) - drm_sched_wqueue_stop(&vinst->ring_enc[i].sched); + enc_fence[i] = amdgpu_vcn_ring_reset_begin_helper(&vinst->ring_enc[i], ring, + timedout_fence); /* Perform the VCN reset for the specified instance */ r = vinst->reset(vinst); if (r) goto unlock; - r = amdgpu_ring_test_ring(&vinst->ring_dec); + + r = amdgpu_vcn_ring_reset_end_helper(&vinst->ring_dec, dec_fence); if (r) goto unlock; for (i = 0; i < vinst->num_enc_rings; i++) { - r = amdgpu_ring_test_ring(&vinst->ring_enc[i]); + r = amdgpu_vcn_ring_reset_end_helper(&vinst->ring_enc[i], enc_fence[i]); if (r) goto unlock; } - amdgpu_fence_driver_force_completion(&vinst->ring_dec, - (&vinst->ring_dec == ring) ? - &timedout_fence->base : NULL); - for (i = 0; i < vinst->num_enc_rings; i++) - amdgpu_fence_driver_force_completion(&vinst->ring_enc[i], - (&vinst->ring_enc[i] == ring) ? - &timedout_fence->base : NULL); - - /* Restart the scheduler's work queue for the dec and enc rings - * if they were stopped by this function. This allows new tasks - * to be submitted to the queues after the reset is complete. - */ - drm_sched_wqueue_start(&vinst->ring_dec.sched); - for (i = 0; i < vinst->num_enc_rings; i++) - drm_sched_wqueue_start(&vinst->ring_enc[i].sched); - unlock: mutex_unlock(&vinst->engine_reset_mutex); -- cgit v1.2.3 From 59c66cc3605cc9246e227a942ba9fcdb90feeacb Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 12 May 2026 13:11:36 -0400 Subject: drm/amdgpu/sdma: handle pipe reset more gracefully Save any unprocessed work in the queues using the new ring helper. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.c index fcd81242059e..fbac732f3e01 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.c @@ -553,10 +553,11 @@ static int amdgpu_sdma_soft_reset(struct amdgpu_device *adev, u32 instance_id) int amdgpu_sdma_reset_engine(struct amdgpu_device *adev, uint32_t instance_id, bool caller_handles_kernel_queues) { - int ret = 0; struct amdgpu_sdma_instance *sdma_instance = &adev->sdma.instance[instance_id]; struct amdgpu_ring *gfx_ring = &sdma_instance->ring; struct amdgpu_ring *page_ring = &sdma_instance->page; + struct amdgpu_fence *gfx_fence, *page_fence; + int ret = 0; if (amdgpu_sriov_vf(adev)) return -EOPNOTSUPP; @@ -569,9 +570,14 @@ int amdgpu_sdma_reset_engine(struct amdgpu_device *adev, uint32_t instance_id, * the reset is in progress. */ drm_sched_wqueue_stop(&gfx_ring->sched); + gfx_fence = amdgpu_ring_find_guilty_fence(gfx_ring); + amdgpu_ring_reset_helper_begin(gfx_ring, gfx_fence); - if (adev->sdma.has_page_queue) + if (adev->sdma.has_page_queue) { drm_sched_wqueue_stop(&page_ring->sched); + page_fence = amdgpu_ring_find_guilty_fence(page_ring); + amdgpu_ring_reset_helper_begin(page_ring, page_fence); + } } if (sdma_instance->funcs->stop_kernel_queue) { @@ -600,14 +606,19 @@ exit: * to be submitted to the queues after the reset is complete. */ if (!ret) { - amdgpu_fence_driver_force_completion(gfx_ring, NULL); + ret = amdgpu_ring_reset_helper_end(gfx_ring, gfx_fence); + if (ret) + goto unlock; drm_sched_wqueue_start(&gfx_ring->sched); if (adev->sdma.has_page_queue) { - amdgpu_fence_driver_force_completion(page_ring, NULL); + ret = amdgpu_ring_reset_helper_end(page_ring, page_fence); + if (ret) + goto unlock; drm_sched_wqueue_start(&page_ring->sched); } } } +unlock: mutex_unlock(&sdma_instance->engine_reset_mutex); return ret; -- cgit v1.2.3 From b54a809c29e83a7f4cba908ba3f2398fb2d6b56e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 14 May 2026 15:10:21 -0400 Subject: drm/amdgpu/mes12: use proper grbm_select function s/soc21_grbm_select/soc24_grbm_select/ No functional difference as the register offsets are the same. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 7453fb11289e..08b10b9da7a5 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -26,7 +26,7 @@ #include "amdgpu.h" #include "gfx_v12_0.h" #include "soc15_common.h" -#include "soc21.h" +#include "soc24.h" #include "gc/gc_12_0_0_offset.h" #include "gc/gc_12_0_0_sh_mask.h" #include "gc/gc_11_0_0_default.h" @@ -442,7 +442,7 @@ static int mes_v12_0_reset_queue_mmio(struct amdgpu_mes *mes, uint32_t queue_typ mutex_unlock(&adev->gfx.reset_sem_mutex); mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, me_id, pipe_id, queue_id, 0); + soc24_grbm_select(adev, me_id, pipe_id, queue_id, 0); /* wait till dequeue take effects */ for (i = 0; i < adev->usec_timeout; i++) { if (!(RREG32_SOC15(GC, 0, regCP_GFX_HQD_ACTIVE) & 1)) @@ -454,13 +454,13 @@ static int mes_v12_0_reset_queue_mmio(struct amdgpu_mes *mes, uint32_t queue_typ r = -ETIMEDOUT; } - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); } else if (queue_type == AMDGPU_RING_TYPE_COMPUTE) { dev_info(adev->dev, "reset compute queue (%d:%d:%d)\n", me_id, pipe_id, queue_id); mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, me_id, pipe_id, queue_id, 0); + soc24_grbm_select(adev, me_id, pipe_id, queue_id, 0); WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0x2); WREG32_SOC15(GC, 0, regSPI_COMPUTE_QUEUE_RESET, 0x1); @@ -474,7 +474,7 @@ static int mes_v12_0_reset_queue_mmio(struct amdgpu_mes *mes, uint32_t queue_typ dev_err(adev->dev, "failed to wait on hqd deactivate\n"); r = -ETIMEDOUT; } - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); } else if (queue_type == AMDGPU_RING_TYPE_SDMA) { dev_info(adev->dev, "reset sdma queue (%d:%d:%d)\n", @@ -1092,7 +1092,7 @@ static void mes_v12_0_enable(struct amdgpu_device *adev, bool enable) if (enable) { mutex_lock(&adev->srbm_mutex); for (pipe = 0; pipe < AMDGPU_MAX_MES_PIPES; pipe++) { - soc21_grbm_select(adev, 3, pipe, 0, 0); + soc24_grbm_select(adev, 3, pipe, 0, 0); if (amdgpu_mes_log_enable) { u32 log_size = AMDGPU_MES_LOG_BUFFER_SIZE + AMDGPU_MES_MSCRATCH_SIZE; /* In case uni mes is not enabled, only program for pipe 0 */ @@ -1131,7 +1131,7 @@ static void mes_v12_0_enable(struct amdgpu_device *adev, bool enable) WREG32_SOC15(GC, 0, regCP_MES_CNTL, data); } - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); if (amdgpu_emu_mode) @@ -1163,7 +1163,7 @@ static void mes_v12_0_set_ucode_start_addr(struct amdgpu_device *adev) mutex_lock(&adev->srbm_mutex); for (pipe = 0; pipe < AMDGPU_MAX_MES_PIPES; pipe++) { /* me=3, queue=0 */ - soc21_grbm_select(adev, 3, pipe, 0, 0); + soc24_grbm_select(adev, 3, pipe, 0, 0); /* set ucode start address */ ucode_addr = adev->mes.uc_start_addr[pipe] >> 2; @@ -1172,7 +1172,7 @@ static void mes_v12_0_set_ucode_start_addr(struct amdgpu_device *adev) WREG32_SOC15(GC, 0, regCP_MES_PRGRM_CNTR_START_HI, upper_32_bits(ucode_addr)); - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); } mutex_unlock(&adev->srbm_mutex); } @@ -1201,7 +1201,7 @@ static int mes_v12_0_load_microcode(struct amdgpu_device *adev, mutex_lock(&adev->srbm_mutex); /* me=3, pipe=0, queue=0 */ - soc21_grbm_select(adev, 3, pipe, 0, 0); + soc24_grbm_select(adev, 3, pipe, 0, 0); WREG32_SOC15(GC, 0, regCP_MES_IC_BASE_CNTL, 0); @@ -1236,7 +1236,7 @@ static int mes_v12_0_load_microcode(struct amdgpu_device *adev, WREG32_SOC15(GC, 0, regCP_MES_IC_OP_CNTL, data); } - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); return 0; @@ -1383,7 +1383,7 @@ static void mes_v12_0_queue_init_register(struct amdgpu_ring *ring) uint32_t data = 0; mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, 3, ring->pipe, 0, 0); + soc24_grbm_select(adev, 3, ring->pipe, 0, 0); /* set CP_HQD_VMID.VMID = 0. */ data = RREG32_SOC15(GC, 0, regCP_HQD_VMID); @@ -1434,7 +1434,7 @@ static void mes_v12_0_queue_init_register(struct amdgpu_ring *ring) /* set CP_HQD_ACTIVE.ACTIVE=1 */ WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, mqd->cp_hqd_active); - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); } @@ -1500,14 +1500,14 @@ static int mes_v12_0_queue_init(struct amdgpu_device *adev, ((pipe == AMDGPU_MES_KIQ_PIPE) && !adev->mes.kiq_version)) { /* get MES scheduler/KIQ versions */ mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, 3, pipe, 0, 0); + soc24_grbm_select(adev, 3, pipe, 0, 0); if (pipe == AMDGPU_MES_SCHED_PIPE) adev->mes.sched_version = RREG32_SOC15(GC, 0, regCP_MES_GP3_LO); else if (pipe == AMDGPU_MES_KIQ_PIPE && adev->enable_mes_kiq) adev->mes.kiq_version = RREG32_SOC15(GC, 0, regCP_MES_GP3_LO); - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); } @@ -1695,7 +1695,7 @@ static void mes_v12_0_kiq_dequeue_sched(struct amdgpu_device *adev) int i; mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, 3, AMDGPU_MES_SCHED_PIPE, 0, 0); + soc24_grbm_select(adev, 3, AMDGPU_MES_SCHED_PIPE, 0, 0); /* disable the queue if it's active */ if (RREG32_SOC15(GC, 0, regCP_HQD_ACTIVE) & 1) { @@ -1719,7 +1719,7 @@ static void mes_v12_0_kiq_dequeue_sched(struct amdgpu_device *adev) WREG32_SOC15(GC, 0, regCP_HQD_PQ_WPTR_HI, 0); WREG32_SOC15(GC, 0, regCP_HQD_PQ_RPTR, 0); - soc21_grbm_select(adev, 0, 0, 0, 0); + soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); adev->mes.ring[0].sched.ready = false; -- cgit v1.2.3 From 86a1b84d85c7c410ce72a72572350aeff79e924e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 6 May 2026 17:20:46 -0400 Subject: drm/amdgpu/gfx11: only need to remap KCQs when reset via MMIO MES remaps kernels queues as part of it's reset sequence. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index f856b0cf5bec..890f45413fc5 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -7048,11 +7048,12 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; + bool use_mmio = true; int r = 0; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, true, 0); + r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); if (r) { dev_warn(adev->dev, "fail(%d) to reset kcq and try pipe reset\n", r); r = gfx_v11_0_reset_compute_pipe(ring); @@ -7060,15 +7061,17 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, return r; } - r = gfx_v11_0_kcq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "fail to init kcq\n"); - return r; - } - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kcq\n"); - return r; + if (use_mmio) { + r = gfx_v11_0_kcq_init_queue(ring, true); + if (r) { + dev_err(adev->dev, "fail to init kcq\n"); + return r; + } + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); + if (r) { + dev_err(adev->dev, "failed to remap kcq\n"); + return r; + } } return amdgpu_ring_reset_helper_end(ring, timedout_fence); -- cgit v1.2.3 From 974fa2e7dc0d7dce4c7dc88471d0d3b86b089e52 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 6 May 2026 17:23:46 -0400 Subject: drm/amdgpu/gfx12: only need to remap KCQs when reset via MMIO MES remaps kernels queues as part of it's reset sequence. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index f66293fc675e..be3231c574b7 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5426,11 +5426,12 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; + bool use_mmio = true; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, true, 0); + r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); if (r) { dev_warn(adev->dev, "fail(%d) to reset kcq and try pipe reset\n", r); r = gfx_v12_0_reset_compute_pipe(ring); @@ -5438,15 +5439,17 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, return r; } - r = gfx_v12_0_kcq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "failed to init kcq\n"); - return r; - } - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kcq\n"); - return r; + if (use_mmio) { + r = gfx_v12_0_kcq_init_queue(ring, true); + if (r) { + dev_err(adev->dev, "failed to init kcq\n"); + return r; + } + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); + if (r) { + dev_err(adev->dev, "failed to remap kcq\n"); + return r; + } } return amdgpu_ring_reset_helper_end(ring, timedout_fence); -- cgit v1.2.3 From 5ec4cc91708370847772f2a2397316dbd8d8f066 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 30 Mar 2026 09:33:27 +0800 Subject: drm/amdgpu/mes_v12_0: use mes schedule pipe for legacy queues on unified MES when suspend_all_gangs is issued to pipe0 MES during system suspend or runtime PM, pipe0 can only suspend and resume queues it has tracked. KCQs registered with a non-zero pipe slot may not be correctly handled, leaving them in an inconsistent state after resume. v3: fix the schedule pipe issue v4: use schedule pipe for KQ resets Reviewed-by: Michael Chen Suggested-by: Michael Chen Suggested-by: Alex Deucher Suggested-by: Shaoyun Liu Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 08b10b9da7a5..3d4728b74274 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -528,10 +528,15 @@ static int mes_v12_0_map_legacy_queue(struct amdgpu_mes *mes, convert_to_mes_queue_type(input->queue_type); mes_add_queue_pkt.map_legacy_kq = 1; - if (mes->adev->enable_uni_mes) - pipe = AMDGPU_MES_KIQ_PIPE; - else + if (mes->adev->enable_uni_mes) { + /* Keep scheduler queue on KIQ pipe; map all other kernel queues on sched pipe. */ + if (input->queue_type == AMDGPU_RING_TYPE_MES) + pipe = AMDGPU_MES_KIQ_PIPE; + else + pipe = AMDGPU_MES_SCHED_PIPE; + } else { pipe = AMDGPU_MES_SCHED_PIPE; + } return mes_v12_0_submit_pkt_and_poll_completion(mes, pipe, &mes_add_queue_pkt, sizeof(mes_add_queue_pkt), @@ -567,10 +572,15 @@ static int mes_v12_0_unmap_legacy_queue(struct amdgpu_mes *mes, convert_to_mes_queue_type(input->queue_type); } - if (mes->adev->enable_uni_mes) - pipe = AMDGPU_MES_KIQ_PIPE; - else + if (mes->adev->enable_uni_mes) { + /* Keep scheduler queue on KIQ pipe; unmap all other kernel queues on sched pipe. */ + if (input->queue_type == AMDGPU_RING_TYPE_MES) + pipe = AMDGPU_MES_KIQ_PIPE; + else + pipe = AMDGPU_MES_SCHED_PIPE; + } else { pipe = AMDGPU_MES_SCHED_PIPE; + } return mes_v12_0_submit_pkt_and_poll_completion(mes, pipe, &mes_remove_queue_pkt, sizeof(mes_remove_queue_pkt), @@ -913,10 +923,7 @@ static int mes_v12_0_reset_hw_queue(struct amdgpu_mes *mes, mes_reset_queue_pkt.doorbell_offset = input->doorbell_offset; } - if (input->is_kq) - pipe = AMDGPU_MES_KIQ_PIPE; - else - pipe = AMDGPU_MES_SCHED_PIPE; + pipe = AMDGPU_MES_SCHED_PIPE; return mes_v12_0_submit_pkt_and_poll_completion(mes, pipe, &mes_reset_queue_pkt, sizeof(mes_reset_queue_pkt), -- cgit v1.2.3 From 9d2da45b1d0a33683364b39fa16bd50121f8f8e2 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 30 Mar 2026 09:33:28 +0800 Subject: drm/amdgpu/mes_v12_1: use mes schedule pipe for legacy queues on unified MES when suspend_all_gangs is issued to pipe0 MES during system suspend or runtime PM, pipe0 can only suspend and resume queues it has tracked. KCQs registered with a non-zero pipe slot may not be correctly handled, leaving them in an inconsistent state after resume. v3: fix the schedule pipe issue Suggested-by: Michael Chen Suggested-by: Alex Deucher Suggested-by: Shaoyun Liu Signed-off-by: Jesse Zhang Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_1.c | 22 ++++++++++++++++------ 1 file changed, 16 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 8a90ad5a51b8..8007a6e69305 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c @@ -417,10 +417,15 @@ static int mes_v12_1_map_legacy_queue(struct amdgpu_mes *mes, convert_to_mes_queue_type(input->queue_type); mes_add_queue_pkt.map_legacy_kq = 1; - if (mes->adev->enable_uni_mes) - pipe = AMDGPU_MES_KIQ_PIPE; - else + if (mes->adev->enable_uni_mes) { + /* Keep scheduler queue on KIQ pipe; map all other kernel queues on sched pipe. */ + if (input->queue_type == AMDGPU_RING_TYPE_MES) + pipe = AMDGPU_MES_KIQ_PIPE; + else + pipe = AMDGPU_MES_SCHED_PIPE; + } else { pipe = AMDGPU_MES_SCHED_PIPE; + } return mes_v12_1_submit_pkt_and_poll_completion(mes, input->xcc_id, pipe, @@ -457,10 +462,15 @@ static int mes_v12_1_unmap_legacy_queue(struct amdgpu_mes *mes, convert_to_mes_queue_type(input->queue_type); } - if (mes->adev->enable_uni_mes) - pipe = AMDGPU_MES_KIQ_PIPE; - else + if (mes->adev->enable_uni_mes) { + /* Keep scheduler queue on KIQ pipe; map all other kernel queues on sched pipe. */ + if (input->queue_type == AMDGPU_RING_TYPE_MES) + pipe = AMDGPU_MES_KIQ_PIPE; + else + pipe = AMDGPU_MES_SCHED_PIPE; + } else { pipe = AMDGPU_MES_SCHED_PIPE; + } return mes_v12_1_submit_pkt_and_poll_completion(mes, input->xcc_id, pipe, -- cgit v1.2.3 From 5adb005e26321a23566dba746359ed5816f9f2e5 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 14 Apr 2026 16:58:49 +0800 Subject: drm/amdgpu/gfx11: Refactor compute pipe reset and add HQD cleanup Refactor gfx_v11_0_reset_compute_pipe() to accept explicit me, pipe, and queue parameters instead of deriving them from the ring structure. This enables the function to be used in generic pipe reset flows. Introduce gfx_v11_0_clear_hqds_on_mec_pipe() to properly clear CP_HQD_ACTIVE and CP_HQD_DEQUEUE_REQUEST for all queues on a given MEC pipe while the pipe reset is asserted, ensuring the HQDs are torn down correctly before deasserting reset. Switch the KCQ reset path to use the common MEC pipe reset helper amdgpu_gfx_mec_pipe_reset_run(), which coordinates the reset sequence including KFD suspend/resume to avoid conflicts with user mode queues. v2: just update the sequence (Alex) v3: directly clear ACTIVE and DEQUEUE_REQUEST (Shaoyun Liu) Suggested-by: Manu Rastogi Suggested-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Prike Liang Reviewed-by: Shaoyun Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 154 +++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 66 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 890f45413fc5..c1efb778ebcb 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6913,11 +6913,29 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -static int gfx_v11_0_reset_compute_pipe(struct amdgpu_ring *ring) +/* + * With MEC pipe reset asserted, clear CP_HQD_ACTIVE / CP_HQD_DEQUEUE_REQUEST for + * every queue on (me, pipe). HQDs must be torn down while pipe reset stays + * asserted; only then clear the pipe reset bit. + * Caller must hold adev->srbm_mutex. + */ +static void gfx_v11_0_clear_hqds_on_mec_pipe(struct amdgpu_device *adev, u32 me, + u32 pipe) { + unsigned int q; - struct amdgpu_device *adev = ring->adev; - uint32_t reset_pipe = 0, clean_pipe = 0; + for (q = 0; q < adev->gfx.mec.num_queue_per_pipe; q++) { + soc21_grbm_select(adev, me, pipe, q, 0); + /* Start from a clean HQD dequeue state before forcing HQD inactive. */ + WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, 0); + WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0); + } +} + +static int gfx_v11_0_reset_compute_pipe(struct amdgpu_device *adev, + u32 me, u32 pipe, u32 queue) +{ + uint32_t reset_val, clean_val; int r; if (!gfx_v11_pipe_reset_support(adev)) @@ -6925,109 +6943,113 @@ static int gfx_v11_0_reset_compute_pipe(struct amdgpu_ring *ring) gfx_v11_0_set_safe_mode(adev, 0); mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - - reset_pipe = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); - clean_pipe = reset_pipe; + soc21_grbm_select(adev, me, pipe, queue, 0); if (adev->gfx.rs64_enable) { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); + clean_val = reset_val; - switch (ring->pipe) { + switch (pipe) { case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 0); break; case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 0); break; case 2: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 0); break; case 3: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 0); break; default: break; } - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_pipe); - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_pipe); + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_val); + gfx_v11_0_clear_hqds_on_mec_pipe(adev, me, pipe); + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_val); r = (RREG32_SOC15(GC, 0, regCP_MEC_RS64_INSTR_PNTR) << 2) - RS64_FW_UC_START_ADDR_LO; } else { - if (ring->me == 1) { - switch (ring->pipe) { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_CNTL); + clean_val = reset_val; + + if (me == 1) { + switch (pipe) { case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 0); break; case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 0); break; case 2: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE2_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE2_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE2_RESET, 0); break; case 3: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE3_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE3_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE3_RESET, 0); break; default: break; } /* mec1 fw pc: CP_MEC1_INSTR_PNTR */ } else { - switch (ring->pipe) { + switch (pipe) { case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE0_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE0_RESET, 0); break; case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE1_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE1_RESET, 0); break; case 2: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE2_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE2_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE2_RESET, 0); break; case 3: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE3_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME2_PIPE3_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE3_RESET, 0); break; default: break; } /* mec2 fw pc: CP:CP_MEC2_INSTR_PNTR */ } - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_pipe); - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_pipe); + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_val); + gfx_v11_0_clear_hqds_on_mec_pipe(adev, me, pipe); + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_val); r = RREG32(SOC15_REG_OFFSET(GC, 0, regCP_MEC1_INSTR_PNTR)); } @@ -7035,8 +7057,8 @@ static int gfx_v11_0_reset_compute_pipe(struct amdgpu_ring *ring) mutex_unlock(&adev->srbm_mutex); gfx_v11_0_unset_safe_mode(adev, 0); - dev_info(adev->dev, "The ring %s pipe resets to MEC FW start PC: %s\n", ring->name, - r == 0 ? "successfully" : "failed"); + dev_dbg(adev->dev, "MEC pipe me%u pipe%u queue%u resets to MEC FW start PC: %s\n", + me, pipe, queue, r == 0 ? "successfully" : "failed"); /*FIXME:Sometimes driver can't cache the MEC firmware start PC correctly, so the pipe * reset status relies on the compute ring test result. */ @@ -7056,7 +7078,7 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); if (r) { dev_warn(adev->dev, "fail(%d) to reset kcq and try pipe reset\n", r); - r = gfx_v11_0_reset_compute_pipe(ring); + r = gfx_v11_0_reset_compute_pipe(adev, ring->me, ring->pipe, ring->queue); if (r) return r; } -- cgit v1.2.3 From 2c476a67c6452ffe56ee14c0789c0acdb044427b Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 14 Apr 2026 16:58:52 +0800 Subject: drm/amdgpu/gfx12: Refactor compute pipe reset and add HQD cleanup Refactor gfx_v12_0_reset_compute_pipe() to accept explicit me, pipe, and queue parameters instead of deriving them from the ring structure. This enables the function to be used in generic pipe reset flows. Introduce gfx_v12_0_clear_hqds_on_mec_pipe() to properly clear CP_HQD_ACTIVE and CP_HQD_DEQUEUE_REQUEST for all queues on a given MEC pipe while the pipe reset is asserted, ensuring the HQDs are torn down correctly before deasserting reset. Switch the KCQ reset path to use the common MEC pipe reset helper amdgpu_gfx_mec_pipe_reset_run(), which coordinates the reset sequence including KFD suspend/resume to avoid conflicts with user mode queues. v2: just update the sequence (Alex) v3: directly clear ACTIVE and DEQUEUE_REQUEST (Shaoyun Liu) Suggested-by: Manu Rastogi Suggested-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Prike Liang Reviewed-by: Shaoyun Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 112 +++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index be3231c574b7..8d68d40808f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5338,10 +5338,29 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -static int gfx_v12_0_reset_compute_pipe(struct amdgpu_ring *ring) +/* + * With MEC pipe reset asserted, clear CP_HQD_ACTIVE / CP_HQD_DEQUEUE_REQUEST for + * every queue on (me, pipe). HQDs must be torn down while pipe reset stays + * asserted; only then clear the pipe reset bit. + * Caller must hold adev->srbm_mutex. + */ +static void gfx_v12_0_clear_hqds_on_mec_pipe(struct amdgpu_device *adev, u32 me, + u32 pipe) { - struct amdgpu_device *adev = ring->adev; - uint32_t reset_pipe = 0, clean_pipe = 0; + unsigned int q; + + for (q = 0; q < adev->gfx.mec.num_queue_per_pipe; q++) { + soc24_grbm_select(adev, me, pipe, q, 0); + /* Start from a clean HQD dequeue state before forcing HQD inactive. */ + WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, 0); + WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0); + } +} + +static int gfx_v12_0_reset_compute_pipe(struct amdgpu_device *adev, + u32 me, u32 pipe, u32 queue) +{ + uint32_t reset_val, clean_val; int r = 0; if (!gfx_v12_pipe_reset_support(adev)) @@ -5349,75 +5368,76 @@ static int gfx_v12_0_reset_compute_pipe(struct amdgpu_ring *ring) gfx_v12_0_set_safe_mode(adev, 0); mutex_lock(&adev->srbm_mutex); - soc24_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - - reset_pipe = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); - clean_pipe = reset_pipe; - + soc24_grbm_select(adev, me, pipe, queue, 0); if (adev->gfx.rs64_enable) { - switch (ring->pipe) { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); + clean_val = reset_val; + + switch (pipe) { case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 0); break; case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 0); break; case 2: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 0); break; case 3: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 0); break; default: break; } - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_pipe); - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_pipe); + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_val); + gfx_v12_0_clear_hqds_on_mec_pipe(adev, me, pipe); + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_val); r = (RREG32_SOC15(GC, 0, regCP_MEC_RS64_INSTR_PNTR) << 2) - RS64_FW_UC_START_ADDR_LO; } else { - switch (ring->pipe) { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_CNTL); + clean_val = reset_val; + + switch (pipe) { case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 0); break; case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 0); + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 0); break; default: - break; + break; } - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_pipe); - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_pipe); - /* Doesn't find the F32 MEC instruction pointer register, and suppose - * the driver won't run into the F32 mode. - */ + + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_val); + gfx_v12_0_clear_hqds_on_mec_pipe(adev, me, pipe); + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_val); } soc24_grbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); gfx_v12_0_unset_safe_mode(adev, 0); - dev_info(adev->dev, "The ring %s pipe resets: %s\n", ring->name, - r == 0 ? "successfully" : "failed"); - /* Need the ring test to verify the pipe reset result.*/ + dev_dbg(adev->dev, "MEC pipe me%u pipe%u queue%u resets to MEC FW start PC: %s\n", + me, pipe, queue, r == 0 ? "successfully" : "failed"); return 0; } @@ -5434,7 +5454,7 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); if (r) { dev_warn(adev->dev, "fail(%d) to reset kcq and try pipe reset\n", r); - r = gfx_v12_0_reset_compute_pipe(ring); + r = gfx_v12_0_reset_compute_pipe(adev, ring->me, ring->pipe, ring->queue); if (r) return r; } -- cgit v1.2.3 From fb1d4b21125a6bac87cb94af8ed086230e007462 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 3 Jun 2026 16:28:43 +0800 Subject: drm/amdgpu/mes11: move pipe reset to mes use_mmio patch This makes the code flows cleaner and it's only supported on the use_mmio path. v2: fix typo v3: fix typo v4: directly clear ACTIVE and DEQUEUE_REQUEST (Shaoyun Liu) Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 232 +------------------------------ drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 241 ++++++++++++++++++++++++++++++++- 2 files changed, 241 insertions(+), 232 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index c1efb778ebcb..9fcb2781468b 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6814,69 +6814,6 @@ static void gfx_v11_0_emit_mem_sync(struct amdgpu_ring *ring) amdgpu_ring_write(ring, gcr_cntl); /* GCR_CNTL */ } -static bool gfx_v11_pipe_reset_support(struct amdgpu_device *adev) -{ - /* Disable the pipe reset until the CPFW fully support it.*/ - dev_warn_once(adev->dev, "The CPFW hasn't support pipe reset yet.\n"); - return false; -} - - -static int gfx_v11_reset_gfx_pipe(struct amdgpu_ring *ring) -{ - struct amdgpu_device *adev = ring->adev; - uint32_t reset_pipe = 0, clean_pipe = 0; - int r; - - if (!gfx_v11_pipe_reset_support(adev)) - return -EOPNOTSUPP; - - gfx_v11_0_set_safe_mode(adev, 0); - mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - - switch (ring->pipe) { - case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - PFP_PIPE0_RESET, 1); - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - ME_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - PFP_PIPE0_RESET, 0); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - ME_PIPE0_RESET, 0); - break; - case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - PFP_PIPE1_RESET, 1); - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - ME_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - PFP_PIPE1_RESET, 0); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - ME_PIPE1_RESET, 0); - break; - default: - break; - } - - WREG32_SOC15(GC, 0, regCP_ME_CNTL, reset_pipe); - WREG32_SOC15(GC, 0, regCP_ME_CNTL, clean_pipe); - - r = (RREG32(SOC15_REG_OFFSET(GC, 0, regCP_GFX_RS64_INSTR_PNTR1)) << 2) - - RS64_FW_UC_START_ADDR_LO; - soc21_grbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - gfx_v11_0_unset_safe_mode(adev, 0); - - dev_info(adev->dev, "The ring %s pipe reset to the ME firmware start PC: %s\n", ring->name, - r == 0 ? "successfully" : "failed"); - /* FIXME: Sometimes driver can't cache the ME firmware start PC correctly, - * so the pipe reset status relies on the later gfx ring test result. - */ - return 0; -} - static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -6888,13 +6825,8 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) { - - dev_warn(adev->dev, "reset via MES failed and try pipe reset %d\n", r); - r = gfx_v11_reset_gfx_pipe(ring); - if (r) - return r; - } + if (r) + return r; if (use_mmio) { r = gfx_v11_0_kgq_init_queue(ring, true); @@ -6913,158 +6845,6 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -/* - * With MEC pipe reset asserted, clear CP_HQD_ACTIVE / CP_HQD_DEQUEUE_REQUEST for - * every queue on (me, pipe). HQDs must be torn down while pipe reset stays - * asserted; only then clear the pipe reset bit. - * Caller must hold adev->srbm_mutex. - */ -static void gfx_v11_0_clear_hqds_on_mec_pipe(struct amdgpu_device *adev, u32 me, - u32 pipe) -{ - unsigned int q; - - for (q = 0; q < adev->gfx.mec.num_queue_per_pipe; q++) { - soc21_grbm_select(adev, me, pipe, q, 0); - /* Start from a clean HQD dequeue state before forcing HQD inactive. */ - WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, 0); - WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0); - } -} - -static int gfx_v11_0_reset_compute_pipe(struct amdgpu_device *adev, - u32 me, u32 pipe, u32 queue) -{ - uint32_t reset_val, clean_val; - int r; - - if (!gfx_v11_pipe_reset_support(adev)) - return -EOPNOTSUPP; - - gfx_v11_0_set_safe_mode(adev, 0); - mutex_lock(&adev->srbm_mutex); - soc21_grbm_select(adev, me, pipe, queue, 0); - - if (adev->gfx.rs64_enable) { - reset_val = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); - clean_val = reset_val; - - switch (pipe) { - case 0: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 0); - break; - case 1: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 0); - break; - case 2: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 0); - break; - case 3: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 0); - break; - default: - break; - } - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_val); - gfx_v11_0_clear_hqds_on_mec_pipe(adev, me, pipe); - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_val); - r = (RREG32_SOC15(GC, 0, regCP_MEC_RS64_INSTR_PNTR) << 2) - - RS64_FW_UC_START_ADDR_LO; - } else { - reset_val = RREG32_SOC15(GC, 0, regCP_MEC_CNTL); - clean_val = reset_val; - - if (me == 1) { - switch (pipe) { - case 0: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 0); - break; - case 1: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 0); - break; - case 2: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME1_PIPE2_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME1_PIPE2_RESET, 0); - break; - case 3: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME1_PIPE3_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME1_PIPE3_RESET, 0); - break; - default: - break; - } - /* mec1 fw pc: CP_MEC1_INSTR_PNTR */ - } else { - switch (pipe) { - case 0: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME2_PIPE0_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME2_PIPE0_RESET, 0); - break; - case 1: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME2_PIPE1_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME2_PIPE1_RESET, 0); - break; - case 2: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME2_PIPE2_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME2_PIPE2_RESET, 0); - break; - case 3: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME2_PIPE3_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME2_PIPE3_RESET, 0); - break; - default: - break; - } - /* mec2 fw pc: CP:CP_MEC2_INSTR_PNTR */ - } - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_val); - gfx_v11_0_clear_hqds_on_mec_pipe(adev, me, pipe); - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_val); - r = RREG32(SOC15_REG_OFFSET(GC, 0, regCP_MEC1_INSTR_PNTR)); - } - - soc21_grbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - gfx_v11_0_unset_safe_mode(adev, 0); - - dev_dbg(adev->dev, "MEC pipe me%u pipe%u queue%u resets to MEC FW start PC: %s\n", - me, pipe, queue, r == 0 ? "successfully" : "failed"); - /*FIXME:Sometimes driver can't cache the MEC firmware start PC correctly, so the pipe - * reset status relies on the compute ring test result. - */ - return 0; -} - static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -7076,12 +6856,8 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) { - dev_warn(adev->dev, "fail(%d) to reset kcq and try pipe reset\n", r); - r = gfx_v11_0_reset_compute_pipe(adev, ring->me, ring->pipe, ring->queue); - if (r) - return r; - } + if (r) + return r; if (use_mmio) { r = gfx_v11_0_kcq_init_queue(ring, true); diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index ac6d4f277336..820ee7a1d0b6 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -392,6 +392,233 @@ static int mes_v11_0_remove_hw_queue(struct amdgpu_mes *mes, offsetof(union MESAPI__REMOVE_QUEUE, api_status)); } +static bool mes_v11_0_pipe_reset_support(struct amdgpu_device *adev) +{ + /* Disable the pipe reset until the CPFW fully support it.*/ + dev_warn_once(adev->dev, "The CPFW hasn't support pipe reset yet.\n"); + return false; +} +static int mes_v11_0_reset_gfx_pipe_mmio(struct amdgpu_device *adev, + u32 me, u32 pipe, u32 queue) +{ + uint32_t reset_pipe = 0, clean_pipe = 0; + int r; + + if (!mes_v11_0_pipe_reset_support(adev)) + return -EOPNOTSUPP; + + amdgpu_gfx_rlc_enter_safe_mode(adev, 0); + mutex_lock(&adev->srbm_mutex); + soc21_grbm_select(adev, me, pipe, queue, 0); + + switch (pipe) { + case 0: + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + PFP_PIPE0_RESET, 1); + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + ME_PIPE0_RESET, 1); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + PFP_PIPE0_RESET, 0); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + ME_PIPE0_RESET, 0); + break; + case 1: + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + PFP_PIPE1_RESET, 1); + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + ME_PIPE1_RESET, 1); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + PFP_PIPE1_RESET, 0); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + ME_PIPE1_RESET, 0); + break; + default: + break; + } + + WREG32_SOC15(GC, 0, regCP_ME_CNTL, reset_pipe); + WREG32_SOC15(GC, 0, regCP_ME_CNTL, clean_pipe); + + r = (RREG32(SOC15_REG_OFFSET(GC, 0, regCP_GFX_RS64_INSTR_PNTR1)) << 2) - + RS64_FW_UC_START_ADDR_LO; + soc21_grbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_rlc_exit_safe_mode(adev, 0); + + dev_info(adev->dev, "The gfx pipe reset to the ME firmware start PC: %s\n", + r == 0 ? "successfully" : "failed"); + /* FIXME: Sometimes driver can't cache the ME firmware start PC correctly, + * so the pipe reset status relies on the later gfx ring test result. + */ + return 0; +} + +/* + * With MEC pipe reset asserted, clear CP_HQD_ACTIVE / CP_HQD_DEQUEUE_REQUEST for + * every queue on (me, pipe). HQDs must be torn down while pipe reset stays + * asserted; only then clear the pipe reset bit. + * Caller must hold adev->srbm_mutex. + */ +static void mes_v11_0_clear_hqds_on_mec_pipe(struct amdgpu_device *adev, u32 me, + u32 pipe) +{ + unsigned int q; + + for (q = 0; q < adev->gfx.mec.num_queue_per_pipe; q++) { + soc21_grbm_select(adev, me, pipe, q, 0); + /* Start from a clean HQD dequeue state before forcing HQD inactive. */ + WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, 0); + WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0); + } +} + +static int mes_v11_0_reset_compute_pipe_mmio(struct amdgpu_device *adev, + u32 me, u32 pipe, u32 queue) +{ + uint32_t reset_val, clean_val; + int r; + + if (!mes_v11_0_pipe_reset_support(adev)) + return -EOPNOTSUPP; + + amdgpu_gfx_rlc_enter_safe_mode(adev, 0); + mutex_lock(&adev->srbm_mutex); + soc21_grbm_select(adev, me, pipe, queue, 0); + + if (adev->gfx.rs64_enable) { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); + clean_val = reset_val; + + switch (pipe) { + case 0: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 0); + break; + case 1: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 0); + break; + case 2: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 0); + break; + case 3: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 0); + break; + default: + break; + } + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_val); + mes_v11_0_clear_hqds_on_mec_pipe(adev, me, pipe); + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_val); + r = (RREG32_SOC15(GC, 0, regCP_MEC_RS64_INSTR_PNTR) << 2) - + RS64_FW_UC_START_ADDR_LO; + } else { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_CNTL); + clean_val = reset_val; + + if (me == 1) { + switch (pipe) { + case 0: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 0); + break; + case 1: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 0); + break; + case 2: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE2_RESET, 0); + break; + case 3: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE3_RESET, 0); + break; + default: + break; + } + /* mec1 fw pc: CP_MEC1_INSTR_PNTR */ + } else { + switch (pipe) { + case 0: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE0_RESET, 0); + break; + case 1: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE1_RESET, 0); + break; + case 2: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE2_RESET, 0); + break; + case 3: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME2_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME2_PIPE3_RESET, 0); + break; + default: + break; + } + /* mec2 fw pc: CP:CP_MEC2_INSTR_PNTR */ + } + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_val); + mes_v11_0_clear_hqds_on_mec_pipe(adev, me, pipe); + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_val); + r = RREG32(SOC15_REG_OFFSET(GC, 0, regCP_MEC1_INSTR_PNTR)); + } + + soc21_grbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_rlc_exit_safe_mode(adev, 0); + + dev_dbg(adev->dev, "MEC pipe me%u pipe%u queue%u resets to MEC FW start PC: %s\n", + me, pipe, queue, r == 0 ? "successfully" : "failed"); + /*FIXME:Sometimes driver can't cache the MEC firmware start PC correctly, so the pipe + * reset status relies on the compute ring test result. + */ + return 0; +} + +static int mes_v11_0_reset_pipe_mmio(struct amdgpu_mes *mes, uint32_t queue_type, + uint32_t me_id, uint32_t pipe_id, + uint32_t queue_id, uint32_t vmid) +{ + struct amdgpu_device *adev = mes->adev; + + if (queue_type == AMDGPU_RING_TYPE_GFX) + return mes_v11_0_reset_gfx_pipe_mmio(adev, me_id, pipe_id, queue_id); + else if (queue_type == AMDGPU_RING_TYPE_COMPUTE) + return mes_v11_0_reset_compute_pipe_mmio(adev, me_id, pipe_id, queue_id); + else + return -EOPNOTSUPP; +} + static int mes_v11_0_reset_queue_mmio(struct amdgpu_mes *mes, uint32_t queue_type, uint32_t me_id, uint32_t pipe_id, uint32_t queue_id, uint32_t vmid) @@ -764,10 +991,16 @@ static int mes_v11_0_reset_hw_queue(struct amdgpu_mes *mes, { union MESAPI__RESET mes_reset_queue_pkt; - if (input->use_mmio) - return mes_v11_0_reset_queue_mmio(mes, input->queue_type, - input->me_id, input->pipe_id, - input->queue_id, input->vmid); + if (input->use_mmio) { + int r = mes_v11_0_reset_queue_mmio(mes, input->queue_type, + input->me_id, input->pipe_id, + input->queue_id, input->vmid); + if (r) + return mes_v11_0_reset_pipe_mmio(mes, input->queue_type, + input->me_id, input->pipe_id, + input->queue_id, input->vmid); + return 0; + } memset(&mes_reset_queue_pkt, 0, sizeof(mes_reset_queue_pkt)); -- cgit v1.2.3 From 27c128973c78ee54825351a4e06d49951c67e11e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 1 Jun 2026 18:16:05 +0800 Subject: drm/amdgpu/mes12: move pipe reset to mes use_mmio patch This makes the code flows cleaner and it's only supported on the use_mmio path. v2: fix typo v3: fix typo v4: directly clear ACTIVE and DEQUEUE_REQUEST (Shaoyun Liu) Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 182 +----------------------------- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 196 ++++++++++++++++++++++++++++++++- 2 files changed, 196 insertions(+), 182 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 8d68d40808f4..7ae30d589537 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5240,69 +5240,6 @@ static void gfx_v12_ip_dump(struct amdgpu_ip_block *ip_block) amdgpu_gfx_off_ctrl(adev, true); } -static bool gfx_v12_pipe_reset_support(struct amdgpu_device *adev) -{ - /* Disable the pipe reset until the CPFW fully support it.*/ - dev_warn_once(adev->dev, "The CPFW hasn't support pipe reset yet.\n"); - return false; -} - -static int gfx_v12_reset_gfx_pipe(struct amdgpu_ring *ring) -{ - struct amdgpu_device *adev = ring->adev; - uint32_t reset_pipe = 0, clean_pipe = 0; - int r; - - if (!gfx_v12_pipe_reset_support(adev)) - return -EOPNOTSUPP; - - gfx_v12_0_set_safe_mode(adev, 0); - mutex_lock(&adev->srbm_mutex); - soc24_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - - switch (ring->pipe) { - case 0: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - PFP_PIPE0_RESET, 1); - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - ME_PIPE0_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - PFP_PIPE0_RESET, 0); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - ME_PIPE0_RESET, 0); - break; - case 1: - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - PFP_PIPE1_RESET, 1); - reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, - ME_PIPE1_RESET, 1); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - PFP_PIPE1_RESET, 0); - clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, - ME_PIPE1_RESET, 0); - break; - default: - break; - } - - WREG32_SOC15(GC, 0, regCP_ME_CNTL, reset_pipe); - WREG32_SOC15(GC, 0, regCP_ME_CNTL, clean_pipe); - - r = (RREG32(SOC15_REG_OFFSET(GC, 0, regCP_GFX_RS64_INSTR_PNTR1)) << 2) - - RS64_FW_UC_START_ADDR_LO; - soc24_grbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - gfx_v12_0_unset_safe_mode(adev, 0); - - dev_info(adev->dev, "The ring %s pipe reset: %s\n", ring->name, - r == 0 ? "successfully" : "failed"); - /* Sometimes the ME start pc counter can't cache correctly, so the - * PC check only as a reference and pipe reset result rely on the - * later ring test. - */ - return 0; -} - static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -5314,12 +5251,8 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) { - dev_warn(adev->dev, "reset via MES failed and try pipe reset %d\n", r); - r = gfx_v12_reset_gfx_pipe(ring); - if (r) - return r; - } + if (r) + return r; if (use_mmio) { r = gfx_v12_0_kgq_init_queue(ring, true); @@ -5338,109 +5271,6 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -/* - * With MEC pipe reset asserted, clear CP_HQD_ACTIVE / CP_HQD_DEQUEUE_REQUEST for - * every queue on (me, pipe). HQDs must be torn down while pipe reset stays - * asserted; only then clear the pipe reset bit. - * Caller must hold adev->srbm_mutex. - */ -static void gfx_v12_0_clear_hqds_on_mec_pipe(struct amdgpu_device *adev, u32 me, - u32 pipe) -{ - unsigned int q; - - for (q = 0; q < adev->gfx.mec.num_queue_per_pipe; q++) { - soc24_grbm_select(adev, me, pipe, q, 0); - /* Start from a clean HQD dequeue state before forcing HQD inactive. */ - WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, 0); - WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0); - } -} - -static int gfx_v12_0_reset_compute_pipe(struct amdgpu_device *adev, - u32 me, u32 pipe, u32 queue) -{ - uint32_t reset_val, clean_val; - int r = 0; - - if (!gfx_v12_pipe_reset_support(adev)) - return -EOPNOTSUPP; - - gfx_v12_0_set_safe_mode(adev, 0); - mutex_lock(&adev->srbm_mutex); - soc24_grbm_select(adev, me, pipe, queue, 0); - if (adev->gfx.rs64_enable) { - reset_val = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); - clean_val = reset_val; - - switch (pipe) { - case 0: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE0_RESET, 0); - break; - case 1: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE1_RESET, 0); - break; - case 2: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE2_RESET, 0); - break; - case 3: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, - MEC_PIPE3_RESET, 0); - break; - default: - break; - } - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_val); - gfx_v12_0_clear_hqds_on_mec_pipe(adev, me, pipe); - WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_val); - r = (RREG32_SOC15(GC, 0, regCP_MEC_RS64_INSTR_PNTR) << 2) - - RS64_FW_UC_START_ADDR_LO; - } else { - reset_val = RREG32_SOC15(GC, 0, regCP_MEC_CNTL); - clean_val = reset_val; - - switch (pipe) { - case 0: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME1_PIPE0_RESET, 0); - break; - case 1: - reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 1); - clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, - MEC_ME1_PIPE1_RESET, 0); - break; - default: - break; - } - - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_val); - gfx_v12_0_clear_hqds_on_mec_pipe(adev, me, pipe); - WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_val); - } - - soc24_grbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - gfx_v12_0_unset_safe_mode(adev, 0); - - dev_dbg(adev->dev, "MEC pipe me%u pipe%u queue%u resets to MEC FW start PC: %s\n", - me, pipe, queue, r == 0 ? "successfully" : "failed"); - return 0; -} - static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -5452,12 +5282,8 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) { - dev_warn(adev->dev, "fail(%d) to reset kcq and try pipe reset\n", r); - r = gfx_v12_0_reset_compute_pipe(adev, ring->me, ring->pipe, ring->queue); - if (r) - return r; - } + if (r) + return r; if (use_mmio) { r = gfx_v12_0_kcq_init_queue(ring, true); diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 3d4728b74274..95dd0106e43c 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -413,6 +413,174 @@ int gfx_v12_0_request_gfx_index_mutex(struct amdgpu_device *adev, return 0; } +static bool mes_v12_0_pipe_reset_support(struct amdgpu_device *adev) +{ + /* Disable the pipe reset until the CPFW fully support it.*/ + dev_warn_once(adev->dev, "The CPFW hasn't support pipe reset yet.\n"); + return false; +} + +static int mes_v12_0_reset_gfx_pipe_mmio(struct amdgpu_device *adev, + u32 me, u32 pipe, u32 queue) +{ + uint32_t reset_pipe = 0, clean_pipe = 0; + int r; + + if (!mes_v12_0_pipe_reset_support(adev)) + return -EOPNOTSUPP; + + amdgpu_gfx_rlc_enter_safe_mode(adev, 0); + mutex_lock(&adev->srbm_mutex); + soc24_grbm_select(adev, me, pipe, queue, 0); + + switch (pipe) { + case 0: + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + PFP_PIPE0_RESET, 1); + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + ME_PIPE0_RESET, 1); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + PFP_PIPE0_RESET, 0); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + ME_PIPE0_RESET, 0); + break; + case 1: + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + PFP_PIPE1_RESET, 1); + reset_pipe = REG_SET_FIELD(reset_pipe, CP_ME_CNTL, + ME_PIPE1_RESET, 1); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + PFP_PIPE1_RESET, 0); + clean_pipe = REG_SET_FIELD(clean_pipe, CP_ME_CNTL, + ME_PIPE1_RESET, 0); + break; + default: + break; + } + + WREG32_SOC15(GC, 0, regCP_ME_CNTL, reset_pipe); + WREG32_SOC15(GC, 0, regCP_ME_CNTL, clean_pipe); + + r = (RREG32(SOC15_REG_OFFSET(GC, 0, regCP_GFX_RS64_INSTR_PNTR1)) << 2) - + RS64_FW_UC_START_ADDR_LO; + soc24_grbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_rlc_exit_safe_mode(adev, 0); + + dev_info(adev->dev, "The gfx pipe reset: %s\n", + r == 0 ? "successfully" : "failed"); + /* Sometimes the ME start pc counter can't cache correctly, so the + * PC check only as a reference and pipe reset result rely on the + * later ring test. + */ + return 0; +} + +/* + * With MEC pipe reset asserted, clear CP_HQD_ACTIVE / CP_HQD_DEQUEUE_REQUEST for + * every queue on (me, pipe). HQDs must be torn down while pipe reset stays + * asserted; only then clear the pipe reset bit. + * Caller must hold adev->srbm_mutex. + */ +static void mes_v12_0_clear_hqds_on_mec_pipe(struct amdgpu_device *adev, u32 me, + u32 pipe) +{ + unsigned int q; + + for (q = 0; q < adev->gfx.mec.num_queue_per_pipe; q++) { + soc24_grbm_select(adev, me, pipe, q, 0); + /* Start from a clean HQD dequeue state before forcing HQD inactive. */ + WREG32_SOC15(GC, 0, regCP_HQD_ACTIVE, 0); + WREG32_SOC15(GC, 0, regCP_HQD_DEQUEUE_REQUEST, 0); + } +} + +static int mes_v12_0_reset_compute_pipe_mmio(struct amdgpu_device *adev, + u32 me, u32 pipe, u32 queue) +{ + uint32_t reset_val, clean_val; + int r = 0; + + if (!mes_v12_0_pipe_reset_support(adev)) + return -EOPNOTSUPP; + + amdgpu_gfx_rlc_enter_safe_mode(adev, 0); + mutex_lock(&adev->srbm_mutex); + soc24_grbm_select(adev, me, pipe, queue, 0); + if (adev->gfx.rs64_enable) { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL); + clean_val = reset_val; + + switch (pipe) { + case 0: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE0_RESET, 0); + break; + case 1: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE1_RESET, 0); + break; + case 2: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE2_RESET, 0); + break; + case 3: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_RS64_CNTL, + MEC_PIPE3_RESET, 0); + break; + default: + break; + } + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, reset_val); + mes_v12_0_clear_hqds_on_mec_pipe(adev, me, pipe); + soc24_grbm_select(adev, me, pipe, queue, 0); + WREG32_SOC15(GC, 0, regCP_MEC_RS64_CNTL, clean_val); + r = (RREG32_SOC15(GC, 0, regCP_MEC_RS64_INSTR_PNTR) << 2) - + RS64_FW_UC_START_ADDR_LO; + } else { + reset_val = RREG32_SOC15(GC, 0, regCP_MEC_CNTL); + clean_val = reset_val; + + switch (pipe) { + case 0: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE0_RESET, 0); + break; + case 1: + reset_val = REG_SET_FIELD(reset_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 1); + clean_val = REG_SET_FIELD(clean_val, CP_MEC_CNTL, + MEC_ME1_PIPE1_RESET, 0); + break; + default: + break; + } + + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, reset_val); + mes_v12_0_clear_hqds_on_mec_pipe(adev, me, pipe); + soc24_grbm_select(adev, me, pipe, queue, 0); + WREG32_SOC15(GC, 0, regCP_MEC_CNTL, clean_val); + } + + soc24_grbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_rlc_exit_safe_mode(adev, 0); + + dev_dbg(adev->dev, "MEC pipe me%u pipe%u queue%u resets to MEC FW start PC: %s\n", + me, pipe, queue, r == 0 ? "successfully" : "failed"); + return 0; +} + static int mes_v12_0_reset_queue_mmio(struct amdgpu_mes *mes, uint32_t queue_type, uint32_t me_id, uint32_t pipe_id, uint32_t queue_id, uint32_t vmid) @@ -507,6 +675,20 @@ static int mes_v12_0_reset_queue_mmio(struct amdgpu_mes *mes, uint32_t queue_typ return r; } +static int mes_v12_0_reset_pipe_mmio(struct amdgpu_mes *mes, uint32_t queue_type, + uint32_t me_id, uint32_t pipe_id, + uint32_t queue_id, uint32_t vmid) +{ + struct amdgpu_device *adev = mes->adev; + + if (queue_type == AMDGPU_RING_TYPE_GFX) + return mes_v12_0_reset_gfx_pipe_mmio(adev, me_id, pipe_id, queue_id); + else if (queue_type == AMDGPU_RING_TYPE_COMPUTE) + return mes_v12_0_reset_compute_pipe_mmio(adev, me_id, pipe_id, queue_id); + else + return -EOPNOTSUPP; +} + static int mes_v12_0_map_legacy_queue(struct amdgpu_mes *mes, struct mes_map_legacy_queue_input *input) { @@ -896,10 +1078,16 @@ static int mes_v12_0_reset_hw_queue(struct amdgpu_mes *mes, union MESAPI__RESET mes_reset_queue_pkt; int pipe; - if (input->use_mmio) - return mes_v12_0_reset_queue_mmio(mes, input->queue_type, - input->me_id, input->pipe_id, - input->queue_id, input->vmid); + if (input->use_mmio) { + int r = mes_v12_0_reset_queue_mmio(mes, input->queue_type, + input->me_id, input->pipe_id, + input->queue_id, input->vmid); + if (r) + return mes_v12_0_reset_pipe_mmio(mes, input->queue_type, + input->me_id, input->pipe_id, + input->queue_id, input->vmid); + return 0; + } memset(&mes_reset_queue_pkt, 0, sizeof(mes_reset_queue_pkt)); -- cgit v1.2.3 From b83490ad9845a7f9c1e91e9c076249bcfb745cfe Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Apr 2026 12:00:26 -0400 Subject: drm/amdgpu/mes: add userq reset helper Implement a userq reset helper using the doorbell index. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 23 +++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index e3972673fd64..34e040b7fb49 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -439,6 +439,29 @@ int amdgpu_mes_reset_legacy_queue(struct amdgpu_device *adev, return r; } +int amdgpu_mes_reset_user_queue(struct amdgpu_device *adev, + int queue_type, + unsigned int doorbell_index, + unsigned int xcc_id) +{ + struct mes_reset_queue_input queue_input; + int r; + + memset(&queue_input, 0, sizeof(queue_input)); + + queue_input.xcc_id = xcc_id; + queue_input.queue_type = queue_type; + queue_input.doorbell_offset = doorbell_index; + + amdgpu_mes_lock(&adev->mes); + r = adev->mes.funcs->reset_hw_queue(&adev->mes, &queue_input); + amdgpu_mes_unlock(&adev->mes); + if (r) + dev_err(adev->dev, "failed to reset user queue\n"); + + return r; +} + int amdgpu_mes_get_hung_queue_db_array_size(struct amdgpu_device *adev) { return adev->mes.hung_queue_db_array_size; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index fdd06a17520a..07c144c8e3b6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -459,6 +459,10 @@ int amdgpu_mes_reset_legacy_queue(struct amdgpu_device *adev, unsigned int vmid, bool use_mmio, uint32_t xcc_id); +int amdgpu_mes_reset_user_queue(struct amdgpu_device *adev, + int queue_type, + unsigned int doorbell_index, + unsigned int xcc_id); int amdgpu_mes_get_hung_queue_db_array_size(struct amdgpu_device *adev); int amdgpu_mes_detect_and_reset_hung_queues(struct amdgpu_device *adev, -- cgit v1.2.3 From 51fe463018a311083195f95b3e4067f4b3833065 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 14 May 2026 15:39:36 -0400 Subject: drm/amdgpu/mes: add a MMIO queue reset helper Will be used by KFD for MMIO based resets. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 30 ++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 7 +++++++ 2 files changed, 37 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 34e040b7fb49..3aa5bd1e67c1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -439,6 +439,36 @@ int amdgpu_mes_reset_legacy_queue(struct amdgpu_device *adev, return r; } +int amdgpu_mes_reset_queue_mmio(struct amdgpu_device *adev, + int queue_type, + unsigned int vmid, + unsigned int me, + unsigned int pipe, + unsigned int queue, + uint32_t xcc_id) +{ + struct mes_reset_queue_input queue_input; + int r; + + memset(&queue_input, 0, sizeof(queue_input)); + + queue_input.xcc_id = xcc_id; + queue_input.me_id = me; + queue_input.pipe_id = pipe; + queue_input.queue_id = queue; + queue_input.vmid = vmid; + queue_input.queue_type = queue_type; + queue_input.use_mmio = true; + + amdgpu_mes_lock(&adev->mes); + r = adev->mes.funcs->reset_hw_queue(&adev->mes, &queue_input); + amdgpu_mes_unlock(&adev->mes); + if (r) + dev_err(adev->dev, "failed to reset legacy queue\n"); + + return r; +} + int amdgpu_mes_reset_user_queue(struct amdgpu_device *adev, int queue_type, unsigned int doorbell_index, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 07c144c8e3b6..454a5a58863e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -459,6 +459,13 @@ int amdgpu_mes_reset_legacy_queue(struct amdgpu_device *adev, unsigned int vmid, bool use_mmio, uint32_t xcc_id); +int amdgpu_mes_reset_queue_mmio(struct amdgpu_device *adev, + int queue_type, + unsigned int vmid, + unsigned int me, + unsigned int pipe, + unsigned int queue, + uint32_t xcc_id); int amdgpu_mes_reset_user_queue(struct amdgpu_device *adev, int queue_type, unsigned int doorbell_index, -- cgit v1.2.3 From 9910d4df91c705f8b6b436c856c47aa69e51aba0 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Apr 2026 12:30:11 -0400 Subject: drm/amdgpu/userq: split the queue reset from adapter reset No functional change intended. Separate the per queue reset handling from the adapter reset handling. Reviewed-by: Jesse Zhang Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 57 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 91554e7c092c..1b47ea1406dc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -88,6 +88,38 @@ static void amdgpu_userq_mgr_reset_work(struct work_struct *work) container_of(work, struct amdgpu_userq_mgr, reset_work); struct amdgpu_device *adev = uq_mgr->adev; + struct amdgpu_reset_context reset_context; + + if (unlikely(adev->debug_disable_gpu_ring_reset)) { + dev_err(adev->dev, "userq reset disabled by debug mask\n"); + return; + } + + /* + * If GPU recovery feature is disabled system-wide, + * skip all reset detection logic + */ + if (!amdgpu_gpu_recovery) + return; + + memset(&reset_context, 0, sizeof(reset_context)); + + reset_context.method = AMD_RESET_METHOD_NONE; + reset_context.reset_req_dev = adev; + reset_context.src = AMDGPU_RESET_SRC_USERQ; + set_bit(AMDGPU_NEED_FULL_RESET, &reset_context.flags); + /*set_bit(AMDGPU_SKIP_COREDUMP, &reset_context.flags);*/ + + amdgpu_device_gpu_recover(adev, NULL, &reset_context); +} + +static void amdgpu_userq_hang_detect_work(struct work_struct *work) +{ + struct amdgpu_usermode_queue *queue = + container_of(work, struct amdgpu_usermode_queue, + hang_detect_work.work); + struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; + struct amdgpu_device *adev = uq_mgr->adev; const int queue_types[] = { AMDGPU_RING_TYPE_COMPUTE, AMDGPU_RING_TYPE_GFX, @@ -131,33 +163,12 @@ static void amdgpu_userq_mgr_reset_work(struct work_struct *work) } } } - - if (gpu_reset) { - struct amdgpu_reset_context reset_context; - - memset(&reset_context, 0, sizeof(reset_context)); - - reset_context.method = AMD_RESET_METHOD_NONE; - reset_context.reset_req_dev = adev; - reset_context.src = AMDGPU_RESET_SRC_USERQ; - set_bit(AMDGPU_NEED_FULL_RESET, &reset_context.flags); - /*set_bit(AMDGPU_SKIP_COREDUMP, &reset_context.flags);*/ - - amdgpu_device_gpu_recover(adev, NULL, &reset_context); - } -} - -static void amdgpu_userq_hang_detect_work(struct work_struct *work) -{ - struct amdgpu_usermode_queue *queue = - container_of(work, struct amdgpu_usermode_queue, - hang_detect_work.work); - /* * Don't schedule the work here! Scheduling or queue work from one reset * handler to another is illegal if you don't take extra precautions! */ - amdgpu_userq_mgr_reset_work(&queue->userq_mgr->reset_work); + if (gpu_reset) + amdgpu_userq_mgr_reset_work(&queue->userq_mgr->reset_work); } /* -- cgit v1.2.3 From 6ecafeaba9b065b842e0dff604fd0c9c29ce50d6 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Apr 2026 12:35:52 -0400 Subject: drm/amdgpu/userq: add per queue reset callback Add a per queue reset callback. Reviewed-by: Jesse Zhang Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index d1751febaefe..4559f7440788 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -113,6 +113,7 @@ struct amdgpu_userq_funcs { int (*restore)(struct amdgpu_usermode_queue *queue); int (*detect_and_reset)(struct amdgpu_device *adev, int queue_type); + int (*reset)(struct amdgpu_usermode_queue *queue); }; /* Usermode queues for gfx */ -- cgit v1.2.3 From 5f98f9d1a2d423ef5adcaa6783a351f728b7f373 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Apr 2026 12:49:06 -0400 Subject: drm/amdgpu/userq: add mes userq reset callback Enable per queue reset for MES managed queues. Reviewed-by: Jesse Zhang Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 16625c31bfd3..ebcb829f7d04 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -179,6 +179,26 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) return r; } +static int mes_userq_reset(struct amdgpu_usermode_queue *queue) +{ + struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; + struct amdgpu_device *adev = uq_mgr->adev; + struct mes_reset_queue_input queue_input; + int r; + + /* XXX: add a FW version check for SDMA per queue reset */ + memset(&queue_input, 0x0, sizeof(struct mes_reset_queue_input)); + queue_input.doorbell_offset = queue->doorbell_index; + queue_input.queue_type = queue->queue_type; + + amdgpu_mes_lock(&adev->mes); + r = adev->mes.funcs->reset_hw_queue(&adev->mes, &queue_input); + amdgpu_mes_unlock(&adev->mes); + if (r) + return r; + return mes_userq_unmap(queue); +} + static int mes_userq_create_ctx_space(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_queue *queue, struct drm_amdgpu_userq_in *mqd_user) @@ -552,4 +572,5 @@ const struct amdgpu_userq_funcs userq_mes_funcs = { .detect_and_reset = mes_userq_detect_and_reset, .preempt = mes_userq_preempt, .restore = mes_userq_restore, + .reset = mes_userq_reset, }; -- cgit v1.2.3 From 8505975d7be1419cb5455d381c965261ab552698 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 3 Jun 2026 16:38:54 +0800 Subject: drm/amdgpu/userq: switch to per queue reset Switch to using the per queue reset rather than the detect and reset interface. Reviewed-by: Jesse Zhang Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 38 ++++++++----------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 1b47ea1406dc..c29d97b786b9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -120,14 +120,9 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) hang_detect_work.work); struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; struct amdgpu_device *adev = uq_mgr->adev; - const int queue_types[] = { - AMDGPU_RING_TYPE_COMPUTE, - AMDGPU_RING_TYPE_GFX, - AMDGPU_RING_TYPE_SDMA - }; - const int num_queue_types = ARRAY_SIZE(queue_types); + const struct amdgpu_userq_funcs *userq_funcs = + adev->userq_funcs[queue->queue_type]; bool gpu_reset = false; - int i, r; if (unlikely(adev->debug_disable_gpu_ring_reset)) { dev_err(adev->dev, "userq reset disabled by debug mask\n"); @@ -141,28 +136,15 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) if (!amdgpu_gpu_recovery) return; - /* - * Iterate through all queue types to detect and reset problematic queues - * Process each queue type in the defined order - */ - for (i = 0; i < num_queue_types; i++) { - int ring_type = queue_types[i]; - const struct amdgpu_userq_funcs *funcs = - adev->userq_funcs[ring_type]; - - if (!amdgpu_userq_is_reset_type_supported(adev, ring_type, - AMDGPU_RESET_TYPE_PER_QUEUE)) - continue; - - if (atomic_read(&uq_mgr->userq_count[ring_type]) > 0 && - funcs && funcs->detect_and_reset) { - r = funcs->detect_and_reset(adev, ring_type); - if (r) { - gpu_reset = true; - break; - } - } + if (amdgpu_userq_is_reset_type_supported(adev, queue->queue_type, + AMDGPU_RESET_TYPE_PER_QUEUE)) { + int r = userq_funcs->reset(queue); + if (r) + gpu_reset = true; + } else { + gpu_reset = true; } + /* * Don't schedule the work here! Scheduling or queue work from one reset * handler to another is illegal if you don't take extra precautions! -- cgit v1.2.3 From 7f9569006302c764e692831ef0095aaa9b1eff85 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Apr 2026 14:57:59 -0400 Subject: drm/amdgpu/userq: drop detect_and_reset callback No longer needed. Reviewed-by: Jesse Zhang Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 2 -- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 53 ------------------------------ 2 files changed, 55 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 4559f7440788..9df1b78407f5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -111,8 +111,6 @@ struct amdgpu_userq_funcs { int (*map)(struct amdgpu_usermode_queue *queue); int (*preempt)(struct amdgpu_usermode_queue *queue); int (*restore)(struct amdgpu_usermode_queue *queue); - int (*detect_and_reset)(struct amdgpu_device *adev, - int queue_type); int (*reset)(struct amdgpu_usermode_queue *queue); }; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index ebcb829f7d04..b8f77ac5760a 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -225,58 +225,6 @@ static int mes_userq_create_ctx_space(struct amdgpu_userq_mgr *uq_mgr, return 0; } -static int mes_userq_detect_and_reset(struct amdgpu_device *adev, - int queue_type) -{ - int db_array_size = amdgpu_mes_get_hung_queue_db_array_size(adev); - struct mes_detect_and_reset_queue_input input; - struct amdgpu_usermode_queue *queue; - unsigned int hung_db_num = 0; - unsigned long queue_id; - u32 db_array[8]; - bool found_hung_queue = false; - int r, i; - - if (db_array_size > 8) { - dev_err(adev->dev, "DB array size (%d vs 8) too small\n", - db_array_size); - return -EINVAL; - } - - memset(&input, 0x0, sizeof(struct mes_detect_and_reset_queue_input)); - - input.queue_type = queue_type; - - amdgpu_mes_lock(&adev->mes); - r = amdgpu_mes_detect_and_reset_hung_queues(adev, queue_type, false, - &hung_db_num, db_array, 0); - amdgpu_mes_unlock(&adev->mes); - if (r) { - dev_err(adev->dev, "Failed to detect and reset queues, err (%d)\n", r); - } else if (hung_db_num) { - xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { - if (queue->queue_type == queue_type) { - for (i = 0; i < hung_db_num; i++) { - if (queue->doorbell_index == db_array[i]) { - queue->state = AMDGPU_USERQ_STATE_HUNG; - found_hung_queue = true; - 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, NULL); - } - } - } - } - } - - if (found_hung_queue) { - /* Resume scheduling after hang recovery */ - r = amdgpu_mes_resume(adev, input.xcc_id); - } - - return r; -} - static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, struct drm_amdgpu_userq_in *args_in) { @@ -569,7 +517,6 @@ const struct amdgpu_userq_funcs userq_mes_funcs = { .mqd_destroy = mes_userq_mqd_destroy, .unmap = mes_userq_unmap, .map = mes_userq_map, - .detect_and_reset = mes_userq_detect_and_reset, .preempt = mes_userq_preempt, .restore = mes_userq_restore, .reset = mes_userq_reset, -- cgit v1.2.3 From a5b9f68d384a225f56d1b7453eb92b98d58cbe94 Mon Sep 17 00:00:00 2001 From: Shaoyun Liu Date: Mon, 20 Apr 2026 10:45:50 -0400 Subject: drm/amd/amdgpu/include : update mes api header v11/v12 Update the parameter in SET_HW_RESOURCES API 1. Align with the setting of enable_lr_compute_wa 2. Add enable_compute_pipe_reset to enable pipe reset when compute queue reset failes v2: add driver flags to track when we enable it Signed-off-by: Shaoyun Liu Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 3 +++ drivers/gpu/drm/amd/include/mes_v11_api_def.h | 5 +++-- drivers/gpu/drm/amd/include/mes_v12_api_def.h | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 454a5a58863e..5255360353f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -168,6 +168,9 @@ struct amdgpu_mes { int master_xcc_ids[AMDGPU_MAX_MES_INST_PIPES]; struct amdgpu_bo *shared_cmd_buf_obj[AMDGPU_MAX_MES_INST_PIPES]; uint64_t shared_cmd_buf_gpu_addr[AMDGPU_MAX_MES_INST_PIPES]; + + bool compute_pipe_reset_enabled; + bool gfx_pipe_reset_enabled; }; struct amdgpu_mes_hung_queue_hqd_info { diff --git a/drivers/gpu/drm/amd/include/mes_v11_api_def.h b/drivers/gpu/drm/amd/include/mes_v11_api_def.h index f9629d42ada2..6644fabeb0b7 100644 --- a/drivers/gpu/drm/amd/include/mes_v11_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v11_api_def.h @@ -238,8 +238,9 @@ union MESAPI_SET_HW_RESOURCES { uint32_t enable_mes_sch_stb_log : 1; uint32_t limit_single_process : 1; uint32_t is_strix_tmz_wa_enabled :1; - uint32_t enable_lr_compute_wa : 1; - uint32_t reserved : 12; + uint32_t enable_lr_compute_wa : 2; + uint32_t enable_compute_pipe_reset : 1; + uint32_t reserved : 10; }; uint32_t uint32_t_all; }; diff --git a/drivers/gpu/drm/amd/include/mes_v12_api_def.h b/drivers/gpu/drm/amd/include/mes_v12_api_def.h index e541a43714a1..cb7ebdfffeeb 100644 --- a/drivers/gpu/drm/amd/include/mes_v12_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v12_api_def.h @@ -294,8 +294,9 @@ union MESAPI_SET_HW_RESOURCES { uint32_t limit_single_process : 1; uint32_t unmapped_doorbell_handling: 2; uint32_t enable_mes_fence_int: 1; - uint32_t enable_lr_compute_wa : 1; - uint32_t reserved : 9; + uint32_t enable_lr_compute_wa : 2; + uint32_t enable_compute_pipe_reset : 1; + uint32_t reserved : 7; }; uint32_t uint32_all; }; -- cgit v1.2.3 From fe5dfb55dd70eed75d5f8f50657334f03c71deef Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Wed, 6 May 2026 15:02:35 -0400 Subject: drm/amdgpu: Allocate enough space for hpd info on gfx11 MES in newer versions on gfx11 and gfx12 can support queue/pipe reset via MES. Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 3aa5bd1e67c1..ae45d840a066 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -217,7 +217,7 @@ int amdgpu_mes_init(struct amdgpu_device *adev) if (r) goto error_doorbell; - if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(12, 1, 0)) { + if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(11, 0, 0)) { /* When queue/pipe reset is done in MES instead of in the * driver, MES passes hung queues information to the driver in * hung_queue_hqd_info. Calculate required space to store this -- cgit v1.2.3 From c3e8df87af3e067be3e53e44592e4a34a12c1017 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 7 May 2026 12:11:29 -0400 Subject: drm/amdkfd: rework MES queue reset sequence Call MES with detect only to get the list of hung queues rather than detecting an resetting. Then loop over the bad queues and reset them individually and finally remove them. Skip queues not owned by KFD. v2: always call resume_all after queue reset Reviewed-by: Amber Lin Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 2e010c1f8828..481afa1f975a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -445,7 +445,7 @@ static int reset_queues_mes(struct device_queue_manager *dqm) * Passed parameter is for targeting queues not scheduled by MES add_queue. */ r = amdgpu_mes_detect_and_reset_hung_queues(adev, AMDGPU_RING_TYPE_COMPUTE, - false, &num_hung, hung_array, ffs(dqm->dev->xcc_mask) - 1); + true, &num_hung, hung_array, ffs(dqm->dev->xcc_mask) - 1); if (!num_hung || r) { r = -ENOTRECOVERABLE; @@ -467,10 +467,9 @@ static int reset_queues_mes(struct device_queue_manager *dqm) } q = find_queue_by_doorbell_offset(dqm, hung_array[i]); - if (!q) { - r = -ENOTRECOVERABLE; - goto fail; - } + /* skip queues not owned by KFD */ + if (!q) + continue; pdd = kfd_get_process_device_data(q->device, q->process); if (!pdd) { @@ -480,6 +479,10 @@ static int reset_queues_mes(struct device_queue_manager *dqm) pr_warn("Hang detected doorbell %x pipe %d queue %d type %d\n", hung_array[i], pipe, queue, queue_type); + r = amdgpu_mes_reset_user_queue(adev, queue_type, hung_array[i], + ffs(dqm->dev->xcc_mask) - 1); + if (r) + goto fail; /* Proceed remove_queue with reset=true */ remove_queue_mes_on_reset_option(dqm, q, &pdd->qpd, true, false); set_queue_as_reset(dqm, q, &pdd->qpd); @@ -505,13 +508,17 @@ static int suspend_all_queues_mes(struct device_queue_manager *dqm) up_read(&adev->reset_domain->sem); if (r) { - if (!reset_queues_mes(dqm)) - return 0; + if (!reset_queues_mes(dqm)) { + r = 0; + goto out; + } dev_err(adev->dev, "failed to suspend gangs from MES\n"); dev_err(adev->dev, "MES might be in unrecoverable state, issue a GPU reset\n"); kfd_hws_hang(dqm); } +out: + resume_all_queues_mes(dqm); return r; } -- cgit v1.2.3 From a3cc79650141670f6db74f90e2d6c173144ace0a Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 15:51:53 -0400 Subject: drm/amdgpu/gfx: add a helper for MQD restore The handling is common so extract it to a helper. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 24 ++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 1 + 2 files changed, 25 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 1e190fb54a97..d88e346b65ee 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -377,6 +377,30 @@ int amdgpu_gfx_kiq_init(struct amdgpu_device *adev, return 0; } +void amdgpu_gfx_mqd_reset_restore(struct amdgpu_ring *ring) +{ + struct amdgpu_device *adev = ring->adev; + int mqd_idx, mqd_size; + + /* restore mqd with the backup copy */ + if (ring->funcs->type == AMDGPU_RING_TYPE_COMPUTE) { + mqd_idx = ring - &adev->gfx.compute_ring[0]; + mqd_size = adev->mqds[AMDGPU_HW_IP_COMPUTE].mqd_size; + if (adev->gfx.mec.mqd_backup[mqd_idx]) + memcpy_toio(ring->mqd_ptr, adev->gfx.mec.mqd_backup[mqd_idx], mqd_size); + } else if (ring->funcs->type == AMDGPU_RING_TYPE_GFX) { + mqd_size = adev->mqds[AMDGPU_HW_IP_GFX].mqd_size; + mqd_idx = ring - &adev->gfx.gfx_ring[0]; + + if (adev->gfx.me.mqd_backup[mqd_idx]) + memcpy_toio(ring->mqd_ptr, adev->gfx.me.mqd_backup[mqd_idx], mqd_size); + } + /* reset the ring */ + ring->wptr = 0; + atomic64_set((atomic64_t *)ring->wptr_cpu_addr, 0); + amdgpu_ring_clear_ring(ring); +} + /* create MQD for each compute/gfx queue */ int amdgpu_gfx_mqd_sw_init(struct amdgpu_device *adev, unsigned int mqd_size, int xcc_id) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index 54c1eb9c499b..ab54dc46e4e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -586,6 +586,7 @@ void amdgpu_gfx_kiq_fini(struct amdgpu_device *adev, int xcc_id); int amdgpu_gfx_kiq_init(struct amdgpu_device *adev, unsigned hpd_size, int xcc_id); +void amdgpu_gfx_mqd_reset_restore(struct amdgpu_ring *ring); int amdgpu_gfx_mqd_sw_init(struct amdgpu_device *adev, unsigned mqd_size, int xcc_id); void amdgpu_gfx_mqd_sw_fini(struct amdgpu_device *adev, int xcc_id); -- cgit v1.2.3 From 0f1c75242b529ca5185bb8311954c44269b728cc Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 15:58:26 -0400 Subject: drm/amdgpu/gfx11: use the new MQD helper for queue reset And while we are at it remove the reset parameter as it's no longer needed. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 9fcb2781468b..cecac4b97e6a 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -4219,13 +4219,13 @@ static int gfx_v11_0_gfx_mqd_init(struct amdgpu_device *adev, void *m, return 0; } -static int gfx_v11_0_kgq_init_queue(struct amdgpu_ring *ring, bool reset) +static int gfx_v11_0_kgq_init_queue(struct amdgpu_ring *ring) { struct amdgpu_device *adev = ring->adev; struct v11_gfx_mqd *mqd = ring->mqd_ptr; int mqd_idx = ring - &adev->gfx.gfx_ring[0]; - if (!reset && !amdgpu_in_reset(adev) && !adev->in_suspend) { + if (!amdgpu_in_reset(adev) && !adev->in_suspend) { memset((void *)mqd, 0, sizeof(*mqd)); mutex_lock(&adev->srbm_mutex); soc21_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); @@ -4252,7 +4252,7 @@ static int gfx_v11_0_cp_async_gfx_ring_resume(struct amdgpu_device *adev) int r, i; for (i = 0; i < adev->gfx.num_gfx_rings; i++) { - r = gfx_v11_0_kgq_init_queue(&adev->gfx.gfx_ring[i], false); + r = gfx_v11_0_kgq_init_queue(&adev->gfx.gfx_ring[i]); if (r) return r; } @@ -4589,13 +4589,13 @@ static int gfx_v11_0_kiq_init_queue(struct amdgpu_ring *ring) return 0; } -static int gfx_v11_0_kcq_init_queue(struct amdgpu_ring *ring, bool reset) +static int gfx_v11_0_kcq_init_queue(struct amdgpu_ring *ring) { struct amdgpu_device *adev = ring->adev; struct v11_compute_mqd *mqd = ring->mqd_ptr; int mqd_idx = ring - &adev->gfx.compute_ring[0]; - if (!reset && !amdgpu_in_reset(adev) && !adev->in_suspend) { + if (!amdgpu_in_reset(adev) && !adev->in_suspend) { memset((void *)mqd, 0, sizeof(*mqd)); mutex_lock(&adev->srbm_mutex); soc21_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); @@ -4632,7 +4632,7 @@ static int gfx_v11_0_kcq_resume(struct amdgpu_device *adev) gfx_v11_0_cp_compute_enable(adev, true); for (i = 0; i < adev->gfx.num_compute_rings; i++) { - r = gfx_v11_0_kcq_init_queue(&adev->gfx.compute_ring[i], false); + r = gfx_v11_0_kcq_init_queue(&adev->gfx.compute_ring[i]); if (r) return r; } @@ -6829,11 +6829,7 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, return r; if (use_mmio) { - r = gfx_v11_0_kgq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "failed to init kgq\n"); - return r; - } + amdgpu_gfx_mqd_reset_restore(ring); r = amdgpu_mes_map_legacy_queue(adev, ring, 0); if (r) { @@ -6860,11 +6856,8 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, return r; if (use_mmio) { - r = gfx_v11_0_kcq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "fail to init kcq\n"); - return r; - } + amdgpu_gfx_mqd_reset_restore(ring); + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); if (r) { dev_err(adev->dev, "failed to remap kcq\n"); -- cgit v1.2.3 From 1563844c5b07456d15974c959ff74e667e797a25 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 16:02:42 -0400 Subject: drm/amdgpu/gfx12: use the new MQD helper for queue reset And while we are at it remove the reset parameter as it's no longer needed. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 7ae30d589537..fc6ecdbd03b8 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -3071,13 +3071,13 @@ static int gfx_v12_0_gfx_mqd_init(struct amdgpu_device *adev, void *m, return 0; } -static int gfx_v12_0_kgq_init_queue(struct amdgpu_ring *ring, bool reset) +static int gfx_v12_0_kgq_init_queue(struct amdgpu_ring *ring) { struct amdgpu_device *adev = ring->adev; struct v12_gfx_mqd *mqd = ring->mqd_ptr; int mqd_idx = ring - &adev->gfx.gfx_ring[0]; - if (!reset && !amdgpu_in_reset(adev) && !adev->in_suspend) { + if (!amdgpu_in_reset(adev) && !adev->in_suspend) { memset((void *)mqd, 0, sizeof(*mqd)); mutex_lock(&adev->srbm_mutex); soc24_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); @@ -3104,7 +3104,7 @@ static int gfx_v12_0_cp_async_gfx_ring_resume(struct amdgpu_device *adev) int i, r; for (i = 0; i < adev->gfx.num_gfx_rings; i++) { - r = gfx_v12_0_kgq_init_queue(&adev->gfx.gfx_ring[i], false); + r = gfx_v12_0_kgq_init_queue(&adev->gfx.gfx_ring[i]); if (r) return r; } @@ -3441,13 +3441,13 @@ static int gfx_v12_0_kiq_init_queue(struct amdgpu_ring *ring) return 0; } -static int gfx_v12_0_kcq_init_queue(struct amdgpu_ring *ring, bool reset) +static int gfx_v12_0_kcq_init_queue(struct amdgpu_ring *ring) { struct amdgpu_device *adev = ring->adev; struct v12_compute_mqd *mqd = ring->mqd_ptr; int mqd_idx = ring - &adev->gfx.compute_ring[0]; - if (!reset && !amdgpu_in_reset(adev) && !adev->in_suspend) { + if (!amdgpu_in_reset(adev) && !adev->in_suspend) { memset((void *)mqd, 0, sizeof(*mqd)); mutex_lock(&adev->srbm_mutex); soc24_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0); @@ -3485,7 +3485,7 @@ static int gfx_v12_0_kcq_resume(struct amdgpu_device *adev) gfx_v12_0_cp_compute_enable(adev, true); for (i = 0; i < adev->gfx.num_compute_rings; i++) { - r = gfx_v12_0_kcq_init_queue(&adev->gfx.compute_ring[i], false); + r = gfx_v12_0_kcq_init_queue(&adev->gfx.compute_ring[i]); if (r) return r; } @@ -5255,11 +5255,7 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, return r; if (use_mmio) { - r = gfx_v12_0_kgq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "failed to init kgq\n"); - return r; - } + amdgpu_gfx_mqd_reset_restore(ring); r = amdgpu_mes_map_legacy_queue(adev, ring, 0); if (r) { @@ -5286,11 +5282,8 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, return r; if (use_mmio) { - r = gfx_v12_0_kcq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "failed to init kcq\n"); - return r; - } + amdgpu_gfx_mqd_reset_restore(ring); + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); if (r) { dev_err(adev->dev, "failed to remap kcq\n"); -- cgit v1.2.3 From 2b8fb9308e74c4ce0eadd5d978b083ec28ba5fef Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 16:06:13 -0400 Subject: drm/amdgpu/gfx11: unmap the queue via MES on reset for MMIO path To keep MES in sync. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index cecac4b97e6a..2594fadb26ac 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6829,6 +6829,10 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, return r; if (use_mmio) { + r = amdgpu_mes_unmap_legacy_queue(adev, ring, + RESET_QUEUES, 0, 0, 0); + if (r) + return r; amdgpu_gfx_mqd_reset_restore(ring); r = amdgpu_mes_map_legacy_queue(adev, ring, 0); @@ -6856,6 +6860,10 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, return r; if (use_mmio) { + r = amdgpu_mes_unmap_legacy_queue(adev, ring, + RESET_QUEUES, 0, 0, 0); + if (r) + return r; amdgpu_gfx_mqd_reset_restore(ring); r = amdgpu_mes_map_legacy_queue(adev, ring, 0); -- cgit v1.2.3 From 9b8a22c3962c5d09f37e319a922061a7b19e0162 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 16:08:16 -0400 Subject: drm/amdgpu/gfx12: unmap the queue via MES on reset for MMIO path To keep MES in sync. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index fc6ecdbd03b8..af15908c9b19 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5255,6 +5255,10 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, return r; if (use_mmio) { + r = amdgpu_mes_unmap_legacy_queue(adev, ring, + RESET_QUEUES, 0, 0, 0); + if (r) + return r; amdgpu_gfx_mqd_reset_restore(ring); r = amdgpu_mes_map_legacy_queue(adev, ring, 0); @@ -5282,6 +5286,10 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, return r; if (use_mmio) { + r = amdgpu_mes_unmap_legacy_queue(adev, ring, + RESET_QUEUES, 0, 0, 0); + if (r) + return r; amdgpu_gfx_mqd_reset_restore(ring); r = amdgpu_mes_map_legacy_queue(adev, ring, 0); -- cgit v1.2.3 From 026825998817993c354c581e163a4fb59121907f Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 17:36:09 -0400 Subject: drm/amdgpu: store whether to use MMIO or MES for reset Separate settings for gfx (ME) and compute (MEC). Use this rather than explicitly specifying it. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 2 ++ drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 7 +++++-- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index ab54dc46e4e3..60679615c317 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -116,6 +116,7 @@ struct amdgpu_mec { u32 num_pipe_per_mec; u32 num_queue_per_pipe; void *mqd_backup[AMDGPU_MAX_COMPUTE_RINGS * AMDGPU_MAX_GC_INSTANCES]; + bool use_mmio_for_reset; }; struct amdgpu_mec_bitmap { @@ -401,6 +402,7 @@ struct amdgpu_me { uint32_t num_pipe_per_me; uint32_t num_queue_per_pipe; void *mqd_backup[AMDGPU_MAX_GFX_RINGS]; + bool use_mmio_for_reset; /* These are the resources for which amdgpu takes ownership */ DECLARE_BITMAP(queue_bitmap, AMDGPU_MAX_GFX_QUEUES); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 2594fadb26ac..0751199b8e42 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1911,6 +1911,9 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; + adev->gfx.me.use_mmio_for_reset = false; + adev->gfx.mec.use_mmio_for_reset = true; + return 0; } @@ -6819,7 +6822,7 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; - bool use_mmio = false; + bool use_mmio = adev->gfx.me.use_mmio_for_reset; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); @@ -6850,7 +6853,7 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; - bool use_mmio = true; + bool use_mmio = adev->gfx.mec.use_mmio_for_reset; int r = 0; amdgpu_ring_reset_helper_begin(ring, timedout_fence); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index af15908c9b19..14ce595a5df9 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1603,6 +1603,9 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; + adev->gfx.me.use_mmio_for_reset = false; + adev->gfx.mec.use_mmio_for_reset = true; + return 0; } @@ -5245,7 +5248,7 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; - bool use_mmio = false; + bool use_mmio = adev->gfx.me.use_mmio_for_reset; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); @@ -5276,7 +5279,7 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; - bool use_mmio = true; + bool use_mmio = adev->gfx.mec.use_mmio_for_reset; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); -- cgit v1.2.3 From b5ded0313519e7f84e4f20ba956843a212ab821b Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 16:32:59 -0400 Subject: drm/amdgpu: Use a common KGQ and KCQ reset helper for gfx11/12 They are all the same so use a common implementation. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 33 ++++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 6 ++++- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 46 ++------------------------------- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 46 ++------------------------------- 4 files changed, 41 insertions(+), 90 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index d88e346b65ee..529f61528948 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -377,7 +377,7 @@ int amdgpu_gfx_kiq_init(struct amdgpu_device *adev, return 0; } -void amdgpu_gfx_mqd_reset_restore(struct amdgpu_ring *ring) +static void amdgpu_gfx_mqd_reset_restore(struct amdgpu_ring *ring) { struct amdgpu_device *adev = ring->adev; int mqd_idx, mqd_size; @@ -1988,6 +1988,37 @@ static ssize_t amdgpu_gfx_get_compute_reset_mask(struct device *dev, return amdgpu_show_reset_mask(buf, adev->gfx.compute_supported_reset); } +int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, + unsigned int vmid, + struct amdgpu_fence *timedout_fence, + bool use_mmio) +{ + struct amdgpu_device *adev = ring->adev; + int r; + + amdgpu_ring_reset_helper_begin(ring, timedout_fence); + + r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); + if (r) + return r; + + if (use_mmio) { + r = amdgpu_mes_unmap_legacy_queue(adev, ring, + RESET_QUEUES, 0, 0, 0); + if (r) + return r; + amdgpu_gfx_mqd_reset_restore(ring); + + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); + if (r) { + dev_err(adev->dev, "failed to remap kgq\n"); + return r; + } + } + + return amdgpu_ring_reset_helper_end(ring, timedout_fence); +} + static DEVICE_ATTR(run_cleaner_shader, 0200, NULL, amdgpu_gfx_set_run_cleaner_shader); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index 60679615c317..59fae26ef050 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -588,7 +588,6 @@ void amdgpu_gfx_kiq_fini(struct amdgpu_device *adev, int xcc_id); int amdgpu_gfx_kiq_init(struct amdgpu_device *adev, unsigned hpd_size, int xcc_id); -void amdgpu_gfx_mqd_reset_restore(struct amdgpu_ring *ring); int amdgpu_gfx_mqd_sw_init(struct amdgpu_device *adev, unsigned mqd_size, int xcc_id); void amdgpu_gfx_mqd_sw_fini(struct amdgpu_device *adev, int xcc_id); @@ -670,6 +669,11 @@ void amdgpu_debugfs_compute_sched_mask_init(struct amdgpu_device *adev); int amdgpu_gfx_ring_preempt_ib(struct amdgpu_ring *ring); +int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, + unsigned int vmid, + struct amdgpu_fence *timedout_fence, + bool use_mmio); + static inline const char *amdgpu_gfx_compute_mode_desc(int mode) { switch (mode) { diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 0751199b8e42..1701a4acbde1 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6823,29 +6823,8 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; bool use_mmio = adev->gfx.me.use_mmio_for_reset; - int r; - - amdgpu_ring_reset_helper_begin(ring, timedout_fence); - - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) - return r; - - if (use_mmio) { - r = amdgpu_mes_unmap_legacy_queue(adev, ring, - RESET_QUEUES, 0, 0, 0); - if (r) - return r; - amdgpu_gfx_mqd_reset_restore(ring); - - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kgq\n"); - return r; - } - } - return amdgpu_ring_reset_helper_end(ring, timedout_fence); + return amdgpu_gfx_mes_reset_queue(ring, vmid, timedout_fence, use_mmio); } static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, @@ -6854,29 +6833,8 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; bool use_mmio = adev->gfx.mec.use_mmio_for_reset; - int r = 0; - - amdgpu_ring_reset_helper_begin(ring, timedout_fence); - - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) - return r; - - if (use_mmio) { - r = amdgpu_mes_unmap_legacy_queue(adev, ring, - RESET_QUEUES, 0, 0, 0); - if (r) - return r; - amdgpu_gfx_mqd_reset_restore(ring); - - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kcq\n"); - return r; - } - } - return amdgpu_ring_reset_helper_end(ring, timedout_fence); + return amdgpu_gfx_mes_reset_queue(ring, vmid, timedout_fence, use_mmio); } static void gfx_v11_ip_print(struct amdgpu_ip_block *ip_block, struct drm_printer *p) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 14ce595a5df9..5c846fcd3f83 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5249,29 +5249,8 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; bool use_mmio = adev->gfx.me.use_mmio_for_reset; - int r; - - amdgpu_ring_reset_helper_begin(ring, timedout_fence); - - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) - return r; - - if (use_mmio) { - r = amdgpu_mes_unmap_legacy_queue(adev, ring, - RESET_QUEUES, 0, 0, 0); - if (r) - return r; - amdgpu_gfx_mqd_reset_restore(ring); - - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kgq\n"); - return r; - } - } - return amdgpu_ring_reset_helper_end(ring, timedout_fence); + return amdgpu_gfx_mes_reset_queue(ring, vmid, timedout_fence, use_mmio); } static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, @@ -5280,29 +5259,8 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; bool use_mmio = adev->gfx.mec.use_mmio_for_reset; - int r; - - amdgpu_ring_reset_helper_begin(ring, timedout_fence); - - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); - if (r) - return r; - - if (use_mmio) { - r = amdgpu_mes_unmap_legacy_queue(adev, ring, - RESET_QUEUES, 0, 0, 0); - if (r) - return r; - amdgpu_gfx_mqd_reset_restore(ring); - - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kcq\n"); - return r; - } - } - return amdgpu_ring_reset_helper_end(ring, timedout_fence); + return amdgpu_gfx_mes_reset_queue(ring, vmid, timedout_fence, use_mmio); } static void gfx_v12_0_ring_begin_use(struct amdgpu_ring *ring) -- cgit v1.2.3 From 7b806702e0794fc355c104db8c5bbc3021fa0158 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 18 May 2026 12:05:15 -0400 Subject: drm/amdkfd: split out mes queue reset sequence into standalone function No intended functional change. Reviewed-by: Amber Lin Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 48 ++++++++++++++-------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 481afa1f975a..54c19d31e30e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -407,6 +407,32 @@ static int add_all_kfd_queues_mes(struct device_queue_manager *dqm) return retval; } +static int reset_queue_mes(struct device_queue_manager *dqm, struct queue *q, + int queue_type, int pipe, int queue, unsigned int db) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; + struct kfd_process_device *pdd; + bool use_mmio = false; + int r; + + pdd = kfd_get_process_device_data(q->device, q->process); + if (!pdd) + return -ENODEV; + + if (use_mmio) + r = amdgpu_mes_reset_queue_mmio(adev, queue_type, 0, 1, pipe, queue, + ffs(dqm->dev->xcc_mask) - 1); + else + r = amdgpu_mes_reset_user_queue(adev, queue_type, db, + ffs(dqm->dev->xcc_mask) - 1); + if (r) + return r; + /* Proceed remove_queue with reset=true */ + remove_queue_mes_on_reset_option(dqm, q, &pdd->qpd, true, false); + set_queue_as_reset(dqm, q, &pdd->qpd); + return 0; +} + static int reset_queues_mes(struct device_queue_manager *dqm) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; @@ -414,7 +440,6 @@ static int reset_queues_mes(struct device_queue_manager *dqm) int num_hung = 0, r = 0, i, pipe, queue, queue_type; u32 *hung_array = dqm->hung_db_array; struct amdgpu_mes_hung_queue_hqd_info *hqd_info = dqm->hqd_info; - struct kfd_process_device *pdd; struct queue *q; if (!amdgpu_mes_queue_reset_by_mes_supported(adev)) { @@ -468,24 +493,13 @@ static int reset_queues_mes(struct device_queue_manager *dqm) q = find_queue_by_doorbell_offset(dqm, hung_array[i]); /* skip queues not owned by KFD */ - if (!q) + if (!q) { continue; - - pdd = kfd_get_process_device_data(q->device, q->process); - if (!pdd) { - r = -ENODEV; - goto fail; + } else { + r = reset_queue_mes(dqm, q, queue_type, pipe, queue, hung_array[i]); + if (r) + goto fail; } - - pr_warn("Hang detected doorbell %x pipe %d queue %d type %d\n", - hung_array[i], pipe, queue, queue_type); - r = amdgpu_mes_reset_user_queue(adev, queue_type, hung_array[i], - ffs(dqm->dev->xcc_mask) - 1); - if (r) - goto fail; - /* Proceed remove_queue with reset=true */ - remove_queue_mes_on_reset_option(dqm, q, &pdd->qpd, true, false); - set_queue_as_reset(dqm, q, &pdd->qpd); } dqm->detect_hang_count = num_hung; -- cgit v1.2.3 From 47f1a5dafd7704c9fc729dad76a37231c93694bd Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 18 May 2026 12:37:19 -0400 Subject: drm/amdkfd: plumb a helper to reset a KFD user queue Can be called from KGD. Reviewed-by: Amber Lin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 14 +++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 16 ++++++++++++++- drivers/gpu/drm/amd/amdkfd/kfd_device.c | 24 ++++++++++++++++++++++ .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 11 ++++++++++ .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.h | 2 ++ 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 9783a3cefb04..f25759962e0c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -942,3 +942,17 @@ int amdgpu_amdkfd_config_sq_perfmon(struct amdgpu_device *adev, uint32_t xcp_id, return r; } + +/* Reset an MES queue */ +int amdgpu_amdkfd_reset_mes_queue(struct amdgpu_device *adev, + uint32_t node_id, + int queue_type, + int pipe, int queue, + unsigned int db) +{ + if (!adev->kfd.init_complete) + return 0; + + return kgd2kfd_reset_mes_queue(adev->kfd.dev, node_id, queue_type, + pipe, queue, db); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index 5333e052d56d..d403af5fb552 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -275,7 +275,11 @@ int amdgpu_amdkfd_stop_sched(struct amdgpu_device *adev, uint32_t node_id); int amdgpu_amdkfd_config_sq_perfmon(struct amdgpu_device *adev, uint32_t xcp_id, bool core_override_enable, bool reg_override_enable, bool perfmon_override_enable); bool amdgpu_amdkfd_compute_active(struct amdgpu_device *adev, uint32_t node_id); - +int amdgpu_amdkfd_reset_mes_queue(struct amdgpu_device *adev, + uint32_t node_id, + int queue_type, + int pipe, int queue, + unsigned int db); /* Read user wptr from a specified user address space with page fault * disabled. The memory must be pinned and mapped to the hardware when @@ -446,6 +450,9 @@ bool kgd2kfd_vmfault_fast_path(struct amdgpu_device *adev, struct amdgpu_iv_entr bool retry_fault); void kgd2kfd_lock_kfd(void); void kgd2kfd_teardown_processes(struct amdgpu_device *adev); +int kgd2kfd_reset_mes_queue(struct kfd_dev *kfd, uint32_t node_id, + int queue_type, int pipe, int queue, + unsigned int db); #else static inline int kgd2kfd_init(void) @@ -576,5 +583,12 @@ static inline void kgd2kfd_teardown_processes(struct amdgpu_device *adev) { } +static inline int kgd2kfd_reset_mes_queue(struct kfd_dev *kfd, uint32_t node_id, + int queue_type, int pipe, int queue, + unsigned int db) +{ + return 0; +} + #endif #endif /* AMDGPU_AMDKFD_H_INCLUDED */ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device.c b/drivers/gpu/drm/amd/amdkfd/kfd_device.c index 5eb863dec8f4..b40b6a566aae 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device.c @@ -1796,6 +1796,30 @@ void kgd2kfd_teardown_processes(struct amdgpu_device *adev) cond_resched(); } +int kgd2kfd_reset_mes_queue(struct kfd_dev *kfd, uint32_t node_id, + int queue_type, int pipe, int queue, + unsigned int db) +{ + struct kfd_node *node; + int ret; + + if (!kfd->init_complete) + return 0; + + if (node_id >= kfd->num_nodes) { + dev_warn(kfd->adev->dev, "Invalid node ID: %u exceeds %u\n", + node_id, kfd->num_nodes - 1); + return -EINVAL; + } + node = kfd->nodes[node_id]; + + ret = kfd_reset_queue_mes(node->dqm, queue_type, pipe, queue, db); + if (ret) + dev_err(kfd_device, "Error resetting queue\n"); + + return ret; +} + #if defined(CONFIG_DEBUG_FS) /* This function will send a package to HIQ to hang the HWS 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 54c19d31e30e..d2c81a79b614 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -433,6 +433,17 @@ static int reset_queue_mes(struct device_queue_manager *dqm, struct queue *q, return 0; } +int kfd_reset_queue_mes(struct device_queue_manager *dqm, int queue_type, + int pipe, int queue, unsigned int db) +{ + struct queue *q; + + q = find_queue_by_doorbell_offset(dqm, db); + if (!q) + return 0; + return reset_queue_mes(dqm, q, queue_type, pipe, queue, db); +} + static int reset_queues_mes(struct device_queue_manager *dqm) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h index e0b6a47e7722..2229f8b2f446 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h @@ -333,6 +333,8 @@ int debug_refresh_runlist(struct device_queue_manager *dqm); bool kfd_dqm_is_queue_in_process(struct device_queue_manager *dqm, struct qcm_process_device *qpd, int doorbell_off, u32 *queue_format); +int kfd_reset_queue_mes(struct device_queue_manager *dqm, int queue_type, + int pipe, int queue, unsigned int db); static inline unsigned int get_sh_mem_bases_32(struct kfd_process_device *pdd) { -- cgit v1.2.3 From b86e1ea9e2290088d676442ddec29da9663416c2 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 20 May 2026 16:11:40 -0400 Subject: drm/amdgpu/userq: add MES userq reset helper Will be used by the common compute queue reset handler. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 39 +++++++++++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/mes_userqueue.h | 9 +++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index b8f77ac5760a..3e5f3ee0a82c 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -179,7 +179,7 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) return r; } -static int mes_userq_reset(struct amdgpu_usermode_queue *queue) +int mes_userq_reset(struct amdgpu_usermode_queue *queue) { struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; struct amdgpu_device *adev = uq_mgr->adev; @@ -199,6 +199,43 @@ static int mes_userq_reset(struct amdgpu_usermode_queue *queue) return mes_userq_unmap(queue); } +int mes_userq_reset_queue(struct amdgpu_device *adev, + struct amdgpu_usermode_queue *guilty_uq, + int queue_type, + unsigned int pipe, + unsigned int queue, + unsigned int db) +{ + struct amdgpu_usermode_queue *uq; + bool use_mmio = false; + unsigned long uq_id; + int r; + + xa_for_each(&adev->userq_doorbell_xa, uq_id, uq) { + if (uq->queue_type == queue_type) { + if (uq == guilty_uq) + continue; + if (uq->doorbell_index == db) { + uq->state = AMDGPU_USERQ_STATE_HUNG; + if (use_mmio) + r = amdgpu_mes_reset_queue_mmio(adev, queue_type, 0, 1, pipe, queue, 0); + else + r = amdgpu_mes_reset_user_queue(adev, queue_type, db, 0); + if (r) + return r; + 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; + } + } + } + return 0; +} + static int mes_userq_create_ctx_space(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_queue *queue, struct drm_amdgpu_userq_in *mqd_user) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.h b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.h index 090ae8897770..a473360d6a8b 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.h +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.h @@ -27,4 +27,13 @@ #include "amdgpu_userq.h" extern const struct amdgpu_userq_funcs userq_mes_funcs; + +int mes_userq_reset(struct amdgpu_usermode_queue *queue); +int mes_userq_reset_queue(struct amdgpu_device *adev, + struct amdgpu_usermode_queue *guilty_uq, + int queue_type, + unsigned int pipe, + unsigned int queue, + unsigned int db); + #endif -- cgit v1.2.3 From e49044061b37cc4be99bfd17f6ccdd3509300469 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 7 May 2026 12:03:47 -0400 Subject: drm/amdgpu/gfx: add a common helper to handle MES compute resets Add helpers to handle MES compute queue resets when multiple queues are affected. Can you be used by both KGD and KFD. v2: sqaush in updates v3: squash in userq updates Co-developed-by: Jesse Zhang Co-developed-by: Amber Lin Signed-off-by: Amber Lin Signed-off-by: Jesse Zhang Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 140 +++++++++++++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 9 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 6 ++ drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 2 + drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 2 + drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 2 + 6 files changed, 160 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 529f61528948..d7b595e3f115 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -34,6 +34,7 @@ #include "amdgpu_xcp.h" #include "amdgpu_xgmi.h" #include "amdgpu_mes.h" +#include "mes_userqueue.h" #include "nvd.h" /* delay 0.1 second to enable gfx off feature */ @@ -1994,15 +1995,25 @@ int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, bool use_mmio) { struct amdgpu_device *adev = ring->adev; + bool reinit_queue; int r; + if ((ring->funcs->type == AMDGPU_RING_TYPE_COMPUTE) && + adev->mes.compute_pipe_reset_enabled) + reinit_queue = true; + else if ((ring->funcs->type == AMDGPU_RING_TYPE_GFX) && + adev->mes.gfx_pipe_reset_enabled) + reinit_queue = true; + else + reinit_queue = use_mmio; + amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); if (r) return r; - if (use_mmio) { + if (reinit_queue) { r = amdgpu_mes_unmap_legacy_queue(adev, ring, RESET_QUEUES, 0, 0, 0); if (r) @@ -2177,6 +2188,133 @@ void amdgpu_gfx_sysfs_fini(struct amdgpu_device *adev) } } +static void amdgpu_gfx_reset_start_compute_scheds(struct amdgpu_device *adev, + struct amdgpu_ring *guilty_ring) +{ + struct amdgpu_ring *ring; + int i; + + for (i = 0; i < adev->gfx.num_compute_rings; i++) { + ring = &adev->gfx.compute_ring[i]; + if (ring == guilty_ring) + continue; + drm_sched_wqueue_start(&ring->sched); + } +} + +static void amdgpu_gfx_reset_stop_compute_scheds(struct amdgpu_device *adev, + struct amdgpu_ring *guilty_ring) +{ + struct amdgpu_ring *ring; + int i; + + for (i = 0; i < adev->gfx.num_compute_rings; i++) { + ring = &adev->gfx.compute_ring[i]; + if (ring == guilty_ring) + continue; + drm_sched_wqueue_stop(&ring->sched); + } +} + +static int amdgpu_gfx_reset_mes_kcq(struct amdgpu_device *adev, + struct amdgpu_ring *guilty_ring, + unsigned int db) +{ + bool use_mmio = adev->gfx.mec.use_mmio_for_reset; + struct amdgpu_fence *fence; + struct amdgpu_ring *ring; + int i, r; + + for (i = 0; i < adev->gfx.num_compute_rings; i++) { + ring = &adev->gfx.compute_ring[i]; + if (ring == guilty_ring) + continue; + if (ring->doorbell_index == db) { + fence = amdgpu_ring_find_guilty_fence(ring); + r = amdgpu_gfx_mes_reset_queue(ring, 0, fence, use_mmio); + if (r) + return r; + break; + } + } + return 0; +} + +int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, + struct amdgpu_ring *ring, + struct amdgpu_fence *guilty_fence, + struct amdgpu_usermode_queue *uq, + unsigned int *hung_queue_count) +{ + struct amdgpu_mes_hung_queue_hqd_info *hqd_info = + (struct amdgpu_mes_hung_queue_hqd_info *) + &adev->gfx.mec.mes_hung_db_array[adev->mes.hung_queue_hqd_info_offset]; + int i, r, pipe, queue, queue_type; + unsigned int num_hung = 0; + bool use_mmio = adev->gfx.mec.use_mmio_for_reset; + + guard(mutex)(&adev->gfx.mec.reset_mutex); + /* stop the drm schedulers for all compute queues */ + amdgpu_gfx_reset_stop_compute_scheds(adev, ring); + /* suspend all will determine which queues are hung. + * reset detect will return the array of bad queue doorbells + */ + r = amdgpu_mes_suspend(adev, 0); + /* if suspend all success, it should no hang queue */ + if (!r) + /* always reset the KCQ/userq since we need to signal the fence + * and we could be stuck in a loop which is preemptable. + */ + goto fence_reset; + r = amdgpu_mes_detect_and_reset_hung_queues(adev, AMDGPU_RING_TYPE_COMPUTE, + true, &num_hung, adev->gfx.mec.mes_hung_db_array, 0); + if (r) + goto out; + if (hung_queue_count) + *hung_queue_count = num_hung; + +fence_reset: + /* reset the queue this came from if specified */ + if (ring) { + r = amdgpu_gfx_mes_reset_queue(ring, 0, guilty_fence, use_mmio); + if (r) + goto out; + } + if (uq) { + r = mes_userq_reset(uq); + if (r) + goto out; + } + for (i = 0; i < num_hung; i++) { + pipe = hqd_info[i].pipe_index; + queue = hqd_info[i].queue_index; + queue_type = hqd_info[i].queue_type; + + /* reset any KCQs */ + r = amdgpu_gfx_reset_mes_kcq(adev, ring, + adev->gfx.mec.mes_hung_db_array[i]); + if (r) + goto out; + /* reset any KFD queues */ + r = amdgpu_amdkfd_reset_mes_queue(adev, 0, queue_type, pipe, queue, + adev->gfx.mec.mes_hung_db_array[i]); + if (r) + goto out; + /* reset KGD user queues */ + r = mes_userq_reset_queue(adev, uq, queue_type, pipe, queue, + adev->gfx.mec.mes_hung_db_array[i]); + if (r) + goto out; + } +out: + /* resume all will enable the non-hung queues */ + amdgpu_mes_resume(adev, 0); + if (!r) + amdgpu_gfx_reset_start_compute_scheds(adev, ring); + + return r; +} + int amdgpu_gfx_cleaner_shader_sw_init(struct amdgpu_device *adev, unsigned int cleaner_shader_size) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index 59fae26ef050..d40bc86a6178 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -36,6 +36,8 @@ #include "amdgpu_ring_mux.h" #include "amdgpu_xcp.h" +struct amdgpu_usermode_queue; + /* GFX current status */ #define AMDGPU_GFX_NORMAL_MODE 0x00000000L #define AMDGPU_GFX_SAFE_MODE 0x00000001L @@ -117,6 +119,8 @@ struct amdgpu_mec { u32 num_queue_per_pipe; void *mqd_backup[AMDGPU_MAX_COMPUTE_RINGS * AMDGPU_MAX_GC_INSTANCES]; bool use_mmio_for_reset; + u32 *mes_hung_db_array; + struct mutex reset_mutex; }; struct amdgpu_mec_bitmap { @@ -643,6 +647,11 @@ int amdgpu_gfx_poison_consumption_handler(struct amdgpu_device *adev, bool amdgpu_gfx_is_master_xcc(struct amdgpu_device *adev, int xcc_id); int amdgpu_gfx_sysfs_init(struct amdgpu_device *adev); void amdgpu_gfx_sysfs_fini(struct amdgpu_device *adev); +int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, + struct amdgpu_ring *ring, + struct amdgpu_fence *guilty_fence, + struct amdgpu_usermode_queue *uq, + unsigned int *hung_queue_count); void amdgpu_gfx_ras_error_func(struct amdgpu_device *adev, void *ras_error_status, void (*func)(struct amdgpu_device *adev, void *ras_error_status, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index ae45d840a066..b1b7f69bcff3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -252,6 +252,10 @@ int amdgpu_mes_init(struct amdgpu_device *adev) } } + adev->gfx.mec.mes_hung_db_array = + kcalloc(amdgpu_mes_get_hung_queue_db_array_size(adev), + sizeof(u32), GFP_KERNEL); + return 0; error_doorbell: @@ -279,6 +283,8 @@ void amdgpu_mes_fini(struct amdgpu_device *adev) int i; int num_xcc = adev->gfx.xcc_mask ? NUM_XCC(adev->gfx.xcc_mask) : 1; + kfree(adev->gfx.mec.mes_hung_db_array); + amdgpu_bo_free_kernel(&adev->mes.event_log_gpu_obj, &adev->mes.event_log_gpu_addr, &adev->mes.event_log_cpu_addr); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 1701a4acbde1..f5840358460d 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1914,6 +1914,8 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; + mutex_init(&adev->gfx.mec.reset_mutex); + return 0; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 5c846fcd3f83..f222deef4047 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1606,6 +1606,8 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; + mutex_init(&adev->gfx.mec.reset_mutex); + return 0; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index 61c3577f829f..b4382b751614 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -1287,6 +1287,8 @@ static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; + mutex_init(&adev->gfx.mec.reset_mutex); + return 0; } -- cgit v1.2.3 From f94bbd648bb499a96aab6fd90d44fb4b1ddcd9e3 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 19 May 2026 18:34:00 -0400 Subject: drm/amdgpu: use a single entry point for mes compute reset When we reset MES queues we need to coordinate across KGD and KFD. Use a single function to handle the queue resets across KFD and KGD. v2: squash in fixes for userqs Co-developed-by: Jesse Zhang Co-developed-by: Amber Lin Signed-off-by: Amber Lin Signed-off-by: Jesse Zhang Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 7 +- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 3 +- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 3 +- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 2 +- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 75 ++++------------------ 5 files changed, 22 insertions(+), 68 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index c29d97b786b9..5f0f8a5e3b7d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -138,7 +138,12 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) if (amdgpu_userq_is_reset_type_supported(adev, queue->queue_type, AMDGPU_RESET_TYPE_PER_QUEUE)) { - int r = userq_funcs->reset(queue); + int r; + + if (queue->queue_type == AMDGPU_HW_IP_COMPUTE) + r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, NULL); + else + r = userq_funcs->reset(queue); if (r) gpu_reset = true; } else { diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index f5840358460d..244c51c70c7e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6834,9 +6834,8 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; - bool use_mmio = adev->gfx.mec.use_mmio_for_reset; - return amdgpu_gfx_mes_reset_queue(ring, vmid, timedout_fence, use_mmio); + return amdgpu_gfx_reset_mes_compute(adev, ring, timedout_fence, NULL, NULL); } static void gfx_v11_ip_print(struct amdgpu_ip_block *ip_block, struct drm_printer *p) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index f222deef4047..1334402d211d 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5260,9 +5260,8 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; - bool use_mmio = adev->gfx.mec.use_mmio_for_reset; - return amdgpu_gfx_mes_reset_queue(ring, vmid, timedout_fence, use_mmio); + return amdgpu_gfx_reset_mes_compute(adev, ring, timedout_fence, NULL, NULL); } static void gfx_v12_0_ring_begin_use(struct amdgpu_ring *ring) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 3e5f3ee0a82c..e9bd5ad98265 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -207,7 +207,7 @@ int mes_userq_reset_queue(struct amdgpu_device *adev, unsigned int db) { struct amdgpu_usermode_queue *uq; - bool use_mmio = false; + bool use_mmio = adev->gfx.mec.use_mmio_for_reset; unsigned long uq_id; int r; 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 d2c81a79b614..6054c8e216b8 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -412,7 +412,7 @@ static int reset_queue_mes(struct device_queue_manager *dqm, struct queue *q, { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; struct kfd_process_device *pdd; - bool use_mmio = false; + bool use_mmio = adev->gfx.mec.use_mmio_for_reset; int r; pdd = kfd_get_process_device_data(q->device, q->process); @@ -447,11 +447,8 @@ int kfd_reset_queue_mes(struct device_queue_manager *dqm, int queue_type, static int reset_queues_mes(struct device_queue_manager *dqm) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; - int hqd_info_size = adev->mes.hung_queue_hqd_info_offset; - int num_hung = 0, r = 0, i, pipe, queue, queue_type; - u32 *hung_array = dqm->hung_db_array; - struct amdgpu_mes_hung_queue_hqd_info *hqd_info = dqm->hqd_info; - struct queue *q; + unsigned int num_hung = 0; + int r = 0; if (!amdgpu_mes_queue_reset_by_mes_supported(adev)) { r = -ENOTRECOVERABLE; @@ -467,51 +464,9 @@ static int reset_queues_mes(struct device_queue_manager *dqm) goto fail; } - if (!hung_array || !hqd_info) { - r = -ENOMEM; - goto fail; - } - - memset(hqd_info, 0, hqd_info_size * sizeof(struct amdgpu_mes_hung_queue_hqd_info)); - - /* - * AMDGPU_RING_TYPE_COMPUTE parameter does not matter if called - * post suspend_all as reset & detect will return all hung queue types. - * - * Passed parameter is for targeting queues not scheduled by MES add_queue. - */ - r = amdgpu_mes_detect_and_reset_hung_queues(adev, AMDGPU_RING_TYPE_COMPUTE, - true, &num_hung, hung_array, ffs(dqm->dev->xcc_mask) - 1); - - if (!num_hung || r) { - r = -ENOTRECOVERABLE; + r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung); + if (r) goto fail; - } - - /* MES resets queue/pipe and cleans up internally */ - for (i = 0; i < num_hung; i++) { - hqd_info[i].bit0_31 = hung_array[i + hqd_info_size]; - pipe = hqd_info[i].pipe_index; - queue = hqd_info[i].queue_index; - queue_type = hqd_info[i].queue_type; - - if (queue_type != MES_QUEUE_TYPE_COMPUTE && - queue_type != MES_QUEUE_TYPE_SDMA) { - pr_warn("Unsupported hung queue reset type: %d\n", queue_type); - hung_array[i] = AMDGPU_MES_INVALID_DB_OFFSET; - continue; - } - - q = find_queue_by_doorbell_offset(dqm, hung_array[i]); - /* skip queues not owned by KFD */ - if (!q) { - continue; - } else { - r = reset_queue_mes(dqm, q, queue_type, pipe, queue, hung_array[i]); - if (r) - goto fail; - } - } dqm->detect_hang_count = num_hung; kfd_signal_reset_event(dqm->dev); @@ -529,22 +484,18 @@ static int suspend_all_queues_mes(struct device_queue_manager *dqm) if (!down_read_trylock(&adev->reset_domain->sem)) return -EIO; - r = amdgpu_mes_suspend(adev, ffs(dqm->dev->xcc_mask) - 1); - up_read(&adev->reset_domain->sem); - - if (r) { - if (!reset_queues_mes(dqm)) { - r = 0; - goto out; - } - dev_err(adev->dev, "failed to suspend gangs from MES\n"); - dev_err(adev->dev, "MES might be in unrecoverable state, issue a GPU reset\n"); - kfd_hws_hang(dqm); + if (!reset_queues_mes(dqm)) { + r = 0; + goto out; } + + dev_err(adev->dev, "failed to suspend gangs from MES\n"); + dev_err(adev->dev, "MES might be in unrecoverable state, issue a GPU reset\n"); + kfd_hws_hang(dqm); out: - resume_all_queues_mes(dqm); + up_read(&adev->reset_domain->sem); return r; } -- cgit v1.2.3 From 1e9819678f27417942e4edabb3a4f567ec635e3a Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 14 May 2026 15:29:29 -0400 Subject: drm/amdgpu/mes11: enable compute MMIO pipe reset Enable MMIO pipe reset for compute pipes. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 820ee7a1d0b6..9e27d01cbfa3 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -478,9 +478,6 @@ static int mes_v11_0_reset_compute_pipe_mmio(struct amdgpu_device *adev, uint32_t reset_val, clean_val; int r; - if (!mes_v11_0_pipe_reset_support(adev)) - return -EOPNOTSUPP; - amdgpu_gfx_rlc_enter_safe_mode(adev, 0); mutex_lock(&adev->srbm_mutex); soc21_grbm_select(adev, me, pipe, queue, 0); -- cgit v1.2.3 From 913c0d83be57cfc0bb3359ecceca7109c3c33f1f Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 14 May 2026 15:30:38 -0400 Subject: drm/amdgpu/mes12: enable compute MMIO pipe reset Enable MMIO pipe reset for compute pipes. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 95dd0106e43c..d80a983b1b6c 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -501,9 +501,6 @@ static int mes_v12_0_reset_compute_pipe_mmio(struct amdgpu_device *adev, uint32_t reset_val, clean_val; int r = 0; - if (!mes_v12_0_pipe_reset_support(adev)) - return -EOPNOTSUPP; - amdgpu_gfx_rlc_enter_safe_mode(adev, 0); mutex_lock(&adev->srbm_mutex); soc24_grbm_select(adev, me, pipe, queue, 0); -- cgit v1.2.3 From f401a2633e0243a3ea2f42a0b2806bf62057cb3d Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Fri, 29 May 2026 15:36:52 -0400 Subject: drm/amdgpu: Remove faulty queue before resume When driver already knows a bad queue but MES suspend_all is successful and MES hung queue detection doesn't detect it, remove this queue refore resume_all. Signed-off-by: Amber Lin Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 18 +++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 3 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 2 +- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 2 +- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 2 +- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index d7b595e3f115..ff5a55f5f3c9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -2244,7 +2244,8 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence, struct amdgpu_usermode_queue *uq, - unsigned int *hung_queue_count) + unsigned int *hung_queue_count, + void *faulty_queue_input) { struct amdgpu_mes_hung_queue_hqd_info *hqd_info = (struct amdgpu_mes_hung_queue_hqd_info *) @@ -2252,6 +2253,7 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, int i, r, pipe, queue, queue_type; unsigned int num_hung = 0; bool use_mmio = adev->gfx.mec.use_mmio_for_reset; + struct mes_remove_queue_input *queue_input = (struct mes_remove_queue_input *)faulty_queue_input; guard(mutex)(&adev->gfx.mec.reset_mutex); /* stop the drm schedulers for all compute queues */ @@ -2306,6 +2308,20 @@ fence_reset: if (r) goto out; } + + /* MES doesn't detect any hung queue but we have a known bad queue + * and it is not KCQ + */ + if (!num_hung && queue_input && !ring) { + /* MES suspend_all is successful means this bad queue is + * preempted successfuly. Remove it before resume all so it + * doesn't get mapped back + */ + amdgpu_mes_lock(&adev->mes); + r = adev->mes.funcs->remove_hw_queue(&adev->mes, queue_input); + amdgpu_mes_unlock(&adev->mes); + } + out: /* resume all will enable the non-hung queues */ amdgpu_mes_resume(adev, 0); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index d40bc86a6178..4003360c7d9a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -651,7 +651,8 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence, struct amdgpu_usermode_queue *uq, - unsigned int *hung_queue_count); + unsigned int *hung_queue_count, + void *faulty_queue_input); void amdgpu_gfx_ras_error_func(struct amdgpu_device *adev, void *ras_error_status, void (*func)(struct amdgpu_device *adev, void *ras_error_status, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 5f0f8a5e3b7d..4e3bd505c368 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -141,7 +141,7 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) int r; if (queue->queue_type == AMDGPU_HW_IP_COMPUTE) - r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, NULL); + r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, NULL, NULL); else r = userq_funcs->reset(queue); if (r) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 244c51c70c7e..0bd9d8a21f5e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6835,7 +6835,7 @@ static int gfx_v11_0_reset_kcq(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; - return amdgpu_gfx_reset_mes_compute(adev, ring, timedout_fence, NULL, NULL); + return amdgpu_gfx_reset_mes_compute(adev, ring, timedout_fence, NULL, NULL, NULL); } static void gfx_v11_ip_print(struct amdgpu_ip_block *ip_block, struct drm_printer *p) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 1334402d211d..380ba062134e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5261,7 +5261,7 @@ static int gfx_v12_0_reset_kcq(struct amdgpu_ring *ring, { struct amdgpu_device *adev = ring->adev; - return amdgpu_gfx_reset_mes_compute(adev, ring, timedout_fence, NULL, NULL); + return amdgpu_gfx_reset_mes_compute(adev, ring, timedout_fence, NULL, NULL, NULL); } static void gfx_v12_0_ring_begin_use(struct amdgpu_ring *ring) 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 6054c8e216b8..744b6c65107f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -464,7 +464,7 @@ static int reset_queues_mes(struct device_queue_manager *dqm) goto fail; } - r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung); + r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung, NULL); if (r) goto fail; -- cgit v1.2.3 From c847c557bba84edb3286549aee18cf3a34182e08 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Wed, 6 May 2026 15:02:35 -0400 Subject: drm/amdgpu: Expand MES queue/pipe reset support MES in newer versions on gfx11 and gfx12 can support queue/pipe reset via MES. v2: update the fw version check (Jesse) Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index b1b7f69bcff3..020d9c512306 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -865,7 +865,11 @@ bool amdgpu_mes_suspend_resume_all_supported(struct amdgpu_device *adev) bool amdgpu_mes_queue_reset_by_mes_supported(struct amdgpu_device *adev) { return (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(12, 1, 0) && - (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x73); + (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x73) || + (IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)) == 11 && + (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x8c) || + (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(12, 0, 0) && + (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x8d); } /* Fix me -- node_id is used to identify the correct MES instances in the future */ -- cgit v1.2.3 From a665d09b10af47112747bd42151806fde6cfafd2 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Fri, 29 May 2026 17:02:25 -0400 Subject: drm/amdkfd: Pass known bad queue info to reset suspend_all, resume_all, and remove bad queue has been integrated to a centralized function, amdgpu_gfx_reset_mes_compute. Remove remove_queue and resume_all in KFD and pass the known bad queue information required for remove_queue to amdgpu_gfx_reset_mes_compute. Signed-off-by: Amber Lin Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 744b6c65107f..0d95dd941129 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -72,7 +72,7 @@ static int allocate_sdma_queue(struct device_queue_manager *dqm, static int reset_queues_on_hws_hang(struct device_queue_manager *dqm, bool is_sdma); static int resume_all_queues_mes(struct device_queue_manager *dqm); -static int suspend_all_queues_mes(struct device_queue_manager *dqm); +static int suspend_all_queues_mes(struct device_queue_manager *dqm, struct queue *q); static struct queue *find_queue_by_doorbell_offset(struct device_queue_manager *dqm, u32 doorbell_offset); static void set_queue_as_reset(struct device_queue_manager *dqm, struct queue *q, @@ -312,7 +312,7 @@ static int remove_queue_mes_on_reset_option(struct device_queue_manager *dqm, st return r; if (r) { - if (!suspend_all_queues_mes(dqm)) + if (!suspend_all_queues_mes(dqm, q)) return resume_all_queues_mes(dqm); dev_err(adev->dev, "failed to remove hardware queue from MES, doorbell=0x%x\n", @@ -444,11 +444,12 @@ int kfd_reset_queue_mes(struct device_queue_manager *dqm, int queue_type, return reset_queue_mes(dqm, q, queue_type, pipe, queue, db); } -static int reset_queues_mes(struct device_queue_manager *dqm) +static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; unsigned int num_hung = 0; int r = 0; + struct mes_remove_queue_input queue_input; if (!amdgpu_mes_queue_reset_by_mes_supported(adev)) { r = -ENOTRECOVERABLE; @@ -464,7 +465,13 @@ static int reset_queues_mes(struct device_queue_manager *dqm) goto fail; } - r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung, NULL); + memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); + queue_input.doorbell_offset = q->properties.doorbell_off; + queue_input.gang_context_addr = q->gang_ctx_gpu_addr; + queue_input.remove_queue_after_reset = false; + queue_input.xcc_id = ffs(dqm->dev->xcc_mask) - 1; + /* pass the known bad queue info to the reset function */ + r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung, &queue_input); if (r) goto fail; @@ -476,7 +483,7 @@ fail: return r; } -static int suspend_all_queues_mes(struct device_queue_manager *dqm) +static int suspend_all_queues_mes(struct device_queue_manager *dqm, struct queue *q) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; int r = 0; @@ -485,7 +492,7 @@ static int suspend_all_queues_mes(struct device_queue_manager *dqm) return -EIO; - if (!reset_queues_mes(dqm)) { + if (!reset_queues_mes(dqm, q)) { r = 0; goto out; } @@ -3232,7 +3239,6 @@ int kfd_dqm_suspend_bad_queue_mes(struct kfd_node *knode, u32 pasid, u32 doorbel struct kfd_process_device *pdd = NULL; struct kfd_process *p = kfd_lookup_process_by_pasid(pasid, &pdd); struct device_queue_manager *dqm = knode->dqm; - struct device *dev = dqm->dev->adev->dev; struct qcm_process_device *qpd; struct queue *q = NULL; int ret = 0; @@ -3247,19 +3253,13 @@ int kfd_dqm_suspend_bad_queue_mes(struct kfd_node *knode, u32 pasid, u32 doorbel list_for_each_entry(q, &qpd->queues_list, list) { if (q->doorbell_id == doorbell_id && q->properties.is_active) { - /* suspend all queues will save any good queues and mark the rest as bad */ - suspend_all_queues_mes(dqm); + /* suspend_all handles suspend, remove, resume */ + suspend_all_queues_mes(dqm, q); q->properties.is_evicted = true; q->properties.is_active = false; decrement_queue_count(dqm, qpd, q); - /* this will remove the bad queue and sched a GPU reset if needed */ - ret = remove_queue_mes(dqm, q, qpd); - if (ret) - dev_err(dev, "Removing bad queue failed"); - /* resume the good queues */ - resume_all_queues_mes(dqm); break; } } -- cgit v1.2.3 From 445075e199526096bc6f47dace4391efec88cf7e Mon Sep 17 00:00:00 2001 From: Yifan Zhang Date: Wed, 6 May 2026 21:45:05 +0800 Subject: drm/amdgpu: add ioctl to handle RAS poison error Add a new DRM_IOCTL_AMDGPU_PROC_OPTIONS ioctl with the AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY option, allowing userspace (ROCr) to control per-process SIGBUS delivery. Userspace for this can be found at: https://github.com/ROCm/rocm-systems/pull/6190 Reviewed-by: Lijo Lazar Reviewed-by: Alex Deucher Signed-off-by: Yifan Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 2 + drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 6 +++ drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 27 ++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_events.c | 69 +++++++++++++++++++++++++++++- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 15 +++++++ drivers/gpu/drm/amd/amdkfd/kfd_process.c | 33 ++++++++++++++ include/uapi/drm/amdgpu_drm.h | 21 +++++++++ 8 files changed, 173 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 7b09410d6d8f..5f775c6e9240 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -1468,6 +1468,8 @@ int amdgpu_enable_vblank_kms(struct drm_crtc *crtc); void amdgpu_disable_vblank_kms(struct drm_crtc *crtc); int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp); +int amdgpu_proc_options_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); /* * functions used by amdgpu_encoder.c diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index d403af5fb552..32132be6e683 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -210,6 +210,7 @@ int amdgpu_amdkfd_evict_userptr(struct mmu_interval_notifier *mni, int amdgpu_amdkfd_bo_validate_and_fence(struct amdgpu_bo *bo, uint32_t domain, struct dma_fence *fence); +int amdgpu_amdkfd_set_sigbus_delay(struct task_struct *task, u32 ms); #else static inline bool amdkfd_fence_check_mm(struct dma_fence *f, struct mm_struct *mm) @@ -241,6 +242,11 @@ int amdgpu_amdkfd_bo_validate_and_fence(struct amdgpu_bo *bo, { return 0; } +static inline +int amdgpu_amdkfd_set_sigbus_delay(struct task_struct *task, u32 ms) +{ + return -EOPNOTSUPP; +} #endif /* Shared API */ int amdgpu_amdkfd_alloc_kernel_mem(struct amdgpu_device *adev, size_t size, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index bf4260269681..503bb64c1e55 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -3076,6 +3076,7 @@ const struct drm_ioctl_desc amdgpu_ioctls_kms[] = { DRM_IOCTL_DEF_DRV(AMDGPU_USERQ_SIGNAL, amdgpu_userq_signal_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(AMDGPU_USERQ_WAIT, amdgpu_userq_wait_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(AMDGPU_GEM_LIST_HANDLES, amdgpu_gem_list_handles_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_PROC_OPTIONS, amdgpu_proc_options_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), }; static const struct drm_driver amdgpu_kms_driver = { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 71272f40feef..72b6f55699a4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1423,6 +1423,33 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) return 0; } +/** + * amdgpu_proc_options_ioctl - set per-fd user options + * + * @dev: drm dev pointer + * @data: pointer to struct drm_amdgpu_proc_options + * @filp: drm file + * + * Sets options stored on the per-file amdgpu_fpriv. Currently the only + * supported option is %AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY which + * controls how KFD delivers SIGBUS for poison/RAS events to the calling + * process (immediate, suppressed, or delayed by N milliseconds). + */ +int amdgpu_proc_options_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct drm_amdgpu_proc_options *args = data; + + switch (args->op) { + case AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY: + return amdgpu_amdkfd_set_sigbus_delay(current, + args->kfd_sigbus_delay.value); + default: + DRM_DEBUG_KMS("Invalid user option op %u\n", args->op); + return -EINVAL; + } +} + /** * amdgpu_driver_open_kms - drm callback for open * diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index 81900b49d9d5..71e8f9a23215 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -29,10 +29,12 @@ #include #include #include +#include #include "kfd_priv.h" #include "kfd_events.h" #include "kfd_device_queue_manager.h" #include +#include /* * Wrapper around wait_queue_entry_t @@ -1338,6 +1340,71 @@ void kfd_signal_reset_event(struct kfd_node *dev) srcu_read_unlock(&kfd_processes_srcu, idx); } +/* + * Per-process opt-in for poison-consumption SIGBUS handling. + * + * Default: kernel sends SIGBUS to the process immediately when poison is + * consumed, in addition to delivering the KFD HW/MEMORY exception events. + * + * Userspace (ROCr) can opt-in per-process via the + * DRM_IOCTL_AMDGPU_PROC_OPTIONS / AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY + * option. This lets the app's registered system-event callback handle the + * RAS error first, instead of being killed by SIGBUS. + * + * Encoded value (stored on the kfd_process): + * 0 - default: SIGBUS immediately (no opt-in) + * 0xFFFFFFFF - opt-in, never escalate to SIGBUS + * N (other) - opt-in, escalate to SIGBUS after N ms if app does not + * handle the error in time (safety timeout) + */ + +void kfd_signal_sigbus_delayed_fn(struct work_struct *work) +{ + struct kfd_process *p = container_of(to_delayed_work(work), + struct kfd_process, signal_work); + + if (p->lead_thread) + send_sig(SIGBUS, p->lead_thread, 0); + + kfd_unref_process(p); +} + +static void kfd_signal_sigbus_with_delay(struct kfd_node *dev, + struct kfd_process *p) +{ + u32 delay_ms = atomic_read(&p->kfd_sigbus_delay_ms); + + if (delay_ms == AMDGPU_PROC_OPTIONS_KFD_SIGBUS_DELAY_DISABLED) { + dev_info(dev->adev->dev, + "SIGBUS suppressed for process %s(pid:%d): app opted in to handle RAS error\n", + p->lead_thread->comm, p->lead_thread->pid); + return; + } + + if (delay_ms == 0) + goto send_now; + + /* + * Take an extra reference for the delayed worker. If the work is + * already pending (e.g. another device of this process consumed poison + * just before), drop the reference and skip rescheduling - the process + * only needs to be notified once. + */ + kref_get(&p->ref); + if (!schedule_delayed_work(&p->signal_work, msecs_to_jiffies(delay_ms))) { + kfd_unref_process(p); + return; + } + + dev_info(dev->adev->dev, + "Deferring SIGBUS to process %s(pid:%d) by %u ms (RAS error opt-in safety timeout)\n", + p->lead_thread->comm, p->lead_thread->pid, delay_ms); + return; + +send_now: + send_sig(SIGBUS, p->lead_thread, 0); +} + void kfd_signal_poison_consumed_event(struct kfd_node *dev, u32 pasid) { struct kfd_process *p = kfd_lookup_process_by_pasid(pasid, NULL); @@ -1392,7 +1459,7 @@ void kfd_signal_poison_consumed_event(struct kfd_node *dev, u32 pasid) rcu_read_unlock(); /* user application will handle SIGBUS signal */ - send_sig(SIGBUS, p->lead_thread, 0); + kfd_signal_sigbus_with_delay(dev, p); kfd_unref_process(p); } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index acd0e41e744c..591f41eadae2 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -957,6 +957,20 @@ struct kfd_process { size_t signal_event_count; bool signal_event_limit_reached; + /** + * @kfd_sigbus_delay_ms: Per-process KFD SIGBUS delivery option for + * poison/RAS events (set via DRM_IOCTL_AMDGPU_PROC_OPTIONS / + * AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY). + * + * 0 - send SIGBUS immediately (default) + * 0xFFFFFFFF - suppress SIGBUS delivery + * other - delay SIGBUS delivery by this many milliseconds + */ + atomic_t kfd_sigbus_delay_ms; + + /* Delayed signal delivery to user */ + struct delayed_work signal_work; + /* Information used for memory eviction */ void *kgd_process_info; /* Eviction fence that is attached to all the BOs of this process. The @@ -1554,6 +1568,7 @@ void kfd_signal_vm_fault_event(struct kfd_process_device *pdd, void kfd_signal_reset_event(struct kfd_node *dev); void kfd_signal_poison_consumed_event(struct kfd_node *dev, u32 pasid); +void kfd_signal_sigbus_delayed_fn(struct work_struct *work); void kfd_signal_process_terminate_event(struct kfd_process *p); static inline void kfd_flush_tlb(struct kfd_process_device *pdd) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index 368283d53077..9838954d77da 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -986,6 +986,33 @@ out: return process; } +/** + * amdgpu_amdkfd_set_sigbus_delay - Set per-process KFD SIGBUS delay + * @task: task in the target process + * @ms: encoded delay value (0 = immediate, 0xFFFFFFFF = suppress, + * otherwise delay in milliseconds) + * + * Stores the SIGBUS delivery option on the kfd_process associated with + * @task. If the calling process has not opened /dev/kfd yet (no + * kfd_process exists), this is a no-op - the option only applies to + * processes that actually use KFD. + */ +int amdgpu_amdkfd_set_sigbus_delay(struct task_struct *task, u32 ms) +{ + struct kfd_process *p; + + if (!task->mm) + return -EINVAL; + + p = kfd_lookup_process_by_mm(task->mm); + if (!p) + return 0; + + atomic_set(&p->kfd_sigbus_delay_ms, ms); + kfd_unref_process(p); + return 0; +} + static struct kfd_process *find_process_by_mm(const struct mm_struct *mm) { struct kfd_process *process; @@ -1322,6 +1349,11 @@ void kfd_process_notifier_release_internal(struct kfd_process *p) kfd_process_table_remove(p); cancel_delayed_work_sync(&p->eviction_work); cancel_delayed_work_sync(&p->restore_work); + /* + * If work pending, cancel it and drop the extra ref + */ + if (cancel_delayed_work_sync(&p->signal_work)) + kfd_unref_process(p); /* * Dequeue and destroy user queues, it is not safe for GPU to access @@ -1578,6 +1610,7 @@ struct kfd_process *create_process(const struct task_struct *thread, bool primar INIT_DELAYED_WORK(&process->eviction_work, evict_process_worker); INIT_DELAYED_WORK(&process->restore_work, restore_process_worker); + INIT_DELAYED_WORK(&process->signal_work, kfd_signal_sigbus_delayed_fn); process->last_restore_timestamp = get_jiffies_64(); err = kfd_event_init_process(process); if (err) diff --git a/include/uapi/drm/amdgpu_drm.h b/include/uapi/drm/amdgpu_drm.h index 9f3090db2f16..b32c72a662b6 100644 --- a/include/uapi/drm/amdgpu_drm.h +++ b/include/uapi/drm/amdgpu_drm.h @@ -58,6 +58,7 @@ extern "C" { #define DRM_AMDGPU_USERQ_SIGNAL 0x17 #define DRM_AMDGPU_USERQ_WAIT 0x18 #define DRM_AMDGPU_GEM_LIST_HANDLES 0x19 +#define DRM_AMDGPU_PROC_OPTIONS 0x1A #define DRM_IOCTL_AMDGPU_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_CREATE, union drm_amdgpu_gem_create) #define DRM_IOCTL_AMDGPU_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_MMAP, union drm_amdgpu_gem_mmap) @@ -79,6 +80,7 @@ extern "C" { #define DRM_IOCTL_AMDGPU_USERQ_SIGNAL DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_USERQ_SIGNAL, struct drm_amdgpu_userq_signal) #define DRM_IOCTL_AMDGPU_USERQ_WAIT DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_USERQ_WAIT, struct drm_amdgpu_userq_wait) #define DRM_IOCTL_AMDGPU_GEM_LIST_HANDLES DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_LIST_HANDLES, struct drm_amdgpu_gem_list_handles) +#define DRM_IOCTL_AMDGPU_PROC_OPTIONS DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_PROC_OPTIONS, struct drm_amdgpu_proc_options) /** * DOC: memory domains @@ -1673,6 +1675,25 @@ struct drm_amdgpu_info_uq_metadata { #define AMDGPU_FAMILY_GC_11_5_4 154 /* GC 11.5.4 */ #define AMDGPU_FAMILY_GC_12_0_0 152 /* GC 12.0.0 */ +/* + * Definition of user options + * + * option: AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY + * 0: Disable sigbus delay - SIGBUS will be raised immediately + * 0xFFFFFFFF: SIGBUS will not be raised + * other: Set the sigbus delay in milliseconds + */ +#define AMDGPU_PROC_OPTIONS_OP_KFD_SIGBUS_DELAY 0 + +#define AMDGPU_PROC_OPTIONS_KFD_SIGBUS_DELAY_DISABLED 0xFFFFFFFFu + +struct drm_amdgpu_proc_options { + __u32 op; + struct { + __u32 value; + } kfd_sigbus_delay; +}; + #if defined(__cplusplus) } #endif -- cgit v1.2.3 From 17ac73b24006700f50972d37e297dec1f523c14a Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Wed, 3 Jun 2026 17:30:29 +0800 Subject: drm/amdgpu: Gate debugfs MMIO access on kernel lockdown amdgpu_regs, amdgpu_regs2, and related debugfs nodes allow arbitrary MMIO read/write via RREG32/WREG32 without checking security_locked_down(). On kernel_lockdown=integrity systems this bypasses the same restrictions as /dev/mem and PCI config space sysfs. Check LOCKDOWN_PCI_ACCESS (matching pci-sysfs) at the entry of every debugfs handler that performs direct register access. v2: Use consistent check as per previous check to use LOCKDOWN_DEBUGFS(Lijo) v3: Do not create any entry from amdgpu_debugfs_regs_init() if LOCKDOWN_PCI_ACCESS is active and log once. (Lijo) Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 389bad724273..0455c2cd043f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "amdgpu.h" @@ -1739,6 +1740,12 @@ int amdgpu_debugfs_regs_init(struct amdgpu_device *adev) struct dentry *ent, *root = minor->debugfs_root; unsigned int i; + if (security_locked_down(LOCKDOWN_PCI_ACCESS)) { + drm_info(adev_to_drm(adev), + "amdgpu: HW debugfs nodes disabled (kernel lockdown)\n"); + return 0; + } + for (i = 0; i < ARRAY_SIZE(debugfs_regs); i++) { ent = debugfs_create_file(debugfs_regs_names[i], S_IFREG | 0400, root, -- cgit v1.2.3 From 921926a12e18fc13483062dd57aa3295aa8a82c3 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Tue, 19 May 2026 16:46:34 +0530 Subject: drm/amd/pm: Validate custom profile parameters Add helpers to validate custom profile params against negative/out-of-range values. Use the helpers to validate user passed params. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h | 10 ++++++++++ 7 files changed, 34 insertions(+), 18 deletions(-) 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 54d3dba7d354..06898eaa96b8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c @@ -1466,9 +1466,10 @@ static int arcturus_set_power_profile_mode(struct smu_context *smu, return -ENOMEM; } if (custom_params && custom_params_max_idx) { - if (custom_params_max_idx != ARCTURUS_CUSTOM_PARAMS_COUNT) - return -EINVAL; - if (custom_params[0] >= ARCTURUS_CUSTOM_PARAMS_CLOCK_COUNT) + if (!smu_cmn_custom_params_count_valid(custom_params_max_idx, + ARCTURUS_CUSTOM_PARAMS_COUNT) || + !smu_cmn_custom_params_clock_valid(custom_params[0], + ARCTURUS_CUSTOM_PARAMS_CLOCK_COUNT)) return -EINVAL; idx = custom_params[0] * ARCTURUS_CUSTOM_PARAMS_COUNT; smu->custom_profile_params[idx] = 1; 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 cd0457e13f54..7e7b082fce19 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c @@ -1843,9 +1843,10 @@ static int navi10_set_power_profile_mode(struct smu_context *smu, return -ENOMEM; } if (custom_params && custom_params_max_idx) { - if (custom_params_max_idx != NAVI10_CUSTOM_PARAMS_COUNT) - return -EINVAL; - if (custom_params[0] >= NAVI10_CUSTOM_PARAMS_CLOCKS_COUNT) + if (!smu_cmn_custom_params_count_valid(custom_params_max_idx, + NAVI10_CUSTOM_PARAMS_COUNT) || + !smu_cmn_custom_params_clock_valid(custom_params[0], + NAVI10_CUSTOM_PARAMS_CLOCKS_COUNT)) return -EINVAL; idx = custom_params[0] * NAVI10_CUSTOM_PARAMS_COUNT; smu->custom_profile_params[idx] = 1; 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 f799e489b481..0ac789058d12 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 @@ -1755,9 +1755,10 @@ static int sienna_cichlid_set_power_profile_mode(struct smu_context *smu, return -ENOMEM; } if (custom_params && custom_params_max_idx) { - if (custom_params_max_idx != SIENNA_CICHLID_CUSTOM_PARAMS_COUNT) - return -EINVAL; - if (custom_params[0] >= SIENNA_CICHLID_CUSTOM_PARAMS_CLOCK_COUNT) + if (!smu_cmn_custom_params_count_valid(custom_params_max_idx, + SIENNA_CICHLID_CUSTOM_PARAMS_COUNT) || + !smu_cmn_custom_params_clock_valid(custom_params[0], + SIENNA_CICHLID_CUSTOM_PARAMS_CLOCK_COUNT)) return -EINVAL; idx = custom_params[0] * SIENNA_CICHLID_CUSTOM_PARAMS_COUNT; smu->custom_profile_params[idx] = 1; 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 7f8d4bb47d02..4e1d6a8da8e8 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 @@ -2616,9 +2616,10 @@ static int smu_v13_0_0_set_power_profile_mode(struct smu_context *smu, return -ENOMEM; } if (custom_params && custom_params_max_idx) { - if (custom_params_max_idx != SMU_13_0_0_CUSTOM_PARAMS_COUNT) - return -EINVAL; - if (custom_params[0] >= SMU_13_0_0_CUSTOM_PARAMS_CLOCK_COUNT) + if (!smu_cmn_custom_params_count_valid(custom_params_max_idx, + SMU_13_0_0_CUSTOM_PARAMS_COUNT) || + !smu_cmn_custom_params_clock_valid(custom_params[0], + SMU_13_0_0_CUSTOM_PARAMS_CLOCK_COUNT)) return -EINVAL; idx = custom_params[0] * SMU_13_0_0_CUSTOM_PARAMS_COUNT; smu->custom_profile_params[idx] = 1; 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 0f774b0920ce..81d4ba8013e8 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 @@ -2573,9 +2573,10 @@ static int smu_v13_0_7_set_power_profile_mode(struct smu_context *smu, return -ENOMEM; } if (custom_params && custom_params_max_idx) { - if (custom_params_max_idx != SMU_13_0_7_CUSTOM_PARAMS_COUNT) - return -EINVAL; - if (custom_params[0] >= SMU_13_0_7_CUSTOM_PARAMS_CLOCK_COUNT) + if (!smu_cmn_custom_params_count_valid(custom_params_max_idx, + SMU_13_0_7_CUSTOM_PARAMS_COUNT) || + !smu_cmn_custom_params_clock_valid(custom_params[0], + SMU_13_0_7_CUSTOM_PARAMS_CLOCK_COUNT)) return -EINVAL; idx = custom_params[0] * SMU_13_0_7_CUSTOM_PARAMS_COUNT; smu->custom_profile_params[idx] = 1; 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 fdc1456b885c..1bb418f17025 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 @@ -1828,9 +1828,10 @@ static int smu_v14_0_2_set_power_profile_mode(struct smu_context *smu, return -ENOMEM; } if (custom_params && custom_params_max_idx) { - if (custom_params_max_idx != SMU_14_0_2_CUSTOM_PARAMS_COUNT) - return -EINVAL; - if (custom_params[0] >= SMU_14_0_2_CUSTOM_PARAMS_CLOCK_COUNT) + if (!smu_cmn_custom_params_count_valid(custom_params_max_idx, + SMU_14_0_2_CUSTOM_PARAMS_COUNT) || + !smu_cmn_custom_params_clock_valid(custom_params[0], + SMU_14_0_2_CUSTOM_PARAMS_CLOCK_COUNT)) return -EINVAL; idx = custom_params[0] * SMU_14_0_2_CUSTOM_PARAMS_COUNT; smu->custom_profile_params[idx] = 1; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h index 0e119965ce13..5b7f64b94179 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h @@ -113,6 +113,16 @@ static inline int pcie_gen_to_speed(uint32_t gen) return ((gen == 0) ? link_speed[0] : link_speed[gen - 1]); } +static inline bool smu_cmn_custom_params_count_valid(u32 max_idx, u32 params_count) +{ + return max_idx == params_count; +} + +static inline bool smu_cmn_custom_params_clock_valid(long clock_idx, long clock_count) +{ + return clock_idx >= 0 && clock_idx < clock_count; +} + int smu_cmn_send_smc_msg_with_param(struct smu_context *smu, enum smu_message_type msg, uint32_t param, -- cgit v1.2.3 From b390cb9d776039fc4f0be13b2649299079227d12 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Wed, 3 Jun 2026 02:03:33 +0800 Subject: drm/amd/pm: Validate OD DPM triples before mutating tables vega10_odn_edit_dpm_table() and smu7_odn_edit_dpm_table() could mutate the live ODN table for valid triples, then return 0 after detecting a truncated buffer or out-of-range index. Validate all (index, clock, voltage) triples first and return -EINVAL on any failure; only then apply updates. v2: Use distinct message for different error case, removed unused input_level from validation loop (Lijo) v3: Reject negative level indices, input[] is long but was compared only against unsigned table bounds, so negative values could pass and truncate when assigned to uint32_t input_level. Set DPMTABLE_OD_UPDATE_SCLK/MCLK only after validation passes, so a failed sysfs write does not leave need_update_dpm_table set for a later commit. Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 34 ++++++++++++-------- .../gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 37 ++++++++++++++-------- 2 files changed, 43 insertions(+), 28 deletions(-) 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 95bf187f02a5..39d745f3fb5b 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -5648,23 +5648,29 @@ static int smu7_odn_edit_dpm_table(struct pp_hwmgr *hwmgr, } for (i = 0; i < size; i += 3) { - if (i + 3 > size || input[i] >= podn_dpm_table_in_backend->num_of_pl) { - pr_info("invalid clock voltage input \n"); - return 0; + if (i + 3 > size) { + pr_info("truncated clock/voltage input\n"); + return -EINVAL; } - input_level = input[i]; - input_clk = input[i+1] * 100; - input_vol = input[i+2]; - - if (smu7_check_clk_voltage_valid(hwmgr, type, input_clk, input_vol)) { - podn_dpm_table_in_backend->entries[input_level].clock = input_clk; - podn_vdd_dep_in_backend->entries[input_level].clk = input_clk; - podn_dpm_table_in_backend->entries[input_level].vddc = input_vol; - podn_vdd_dep_in_backend->entries[input_level].vddc = input_vol; - podn_vdd_dep_in_backend->entries[input_level].vddgfx = input_vol; - } else { + if (input[i] < 0 || input[i] >= podn_dpm_table_in_backend->num_of_pl) { + pr_info("invalid clock/voltage level\n"); return -EINVAL; } + input_clk = input[i + 1] * 100; + input_vol = input[i + 2]; + if (!smu7_check_clk_voltage_valid(hwmgr, type, input_clk, input_vol)) + return -EINVAL; + } + + for (i = 0; i < size; i += 3) { + input_level = input[i]; + input_clk = input[i + 1] * 100; + input_vol = input[i + 2]; + podn_dpm_table_in_backend->entries[input_level].clock = input_clk; + podn_vdd_dep_in_backend->entries[input_level].clk = input_clk; + podn_dpm_table_in_backend->entries[input_level].vddc = input_vol; + podn_vdd_dep_in_backend->entries[input_level].vddc = input_vol; + podn_vdd_dep_in_backend->entries[input_level].vddgfx = input_vol; } 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 4b92b52aba2b..a5896ce59097 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -5454,11 +5454,9 @@ static int vega10_odn_edit_dpm_table(struct pp_hwmgr *hwmgr, if (PP_OD_EDIT_SCLK_VDDC_TABLE == type) { dpm_table = &data->dpm_table.gfx_table; podn_vdd_dep_table = &data->odn_dpm_table.vdd_dep_on_sclk; - data->need_update_dpm_table |= DPMTABLE_OD_UPDATE_SCLK; } else if (PP_OD_EDIT_MCLK_VDDC_TABLE == type) { dpm_table = &data->dpm_table.mem_table; podn_vdd_dep_table = &data->odn_dpm_table.vdd_dep_on_mclk; - data->need_update_dpm_table |= DPMTABLE_OD_UPDATE_MCLK; } else if (PP_OD_RESTORE_DEFAULT_TABLE == type) { memcpy(&(data->dpm_table), &(data->golden_dpm_table), sizeof(struct vega10_dpm_table)); vega10_odn_initial_default_setting(hwmgr); @@ -5476,21 +5474,32 @@ static int vega10_odn_edit_dpm_table(struct pp_hwmgr *hwmgr, } for (i = 0; i < size; i += 3) { - if (i + 3 > size || input[i] >= podn_vdd_dep_table->count) { - pr_info("invalid clock voltage input\n"); - return 0; + if (i + 3 > size) { + pr_info("truncated clock/voltage input\n"); + return -EINVAL; } - input_level = input[i]; - input_clk = input[i+1] * 100; - input_vol = input[i+2]; - - if (vega10_check_clk_voltage_valid(hwmgr, type, input_clk, input_vol)) { - dpm_table->dpm_levels[input_level].value = input_clk; - podn_vdd_dep_table->entries[input_level].clk = input_clk; - podn_vdd_dep_table->entries[input_level].vddc = input_vol; - } else { + if (input[i] < 0 || input[i] >= podn_vdd_dep_table->count) { + pr_info("invalid clock/voltage level\n"); return -EINVAL; } + input_clk = input[i + 1] * 100; + input_vol = input[i + 2]; + if (!vega10_check_clk_voltage_valid(hwmgr, type, input_clk, input_vol)) + return -EINVAL; + } + + if (type == PP_OD_EDIT_SCLK_VDDC_TABLE) + data->need_update_dpm_table |= DPMTABLE_OD_UPDATE_SCLK; + else + data->need_update_dpm_table |= DPMTABLE_OD_UPDATE_MCLK; + + for (i = 0; i < size; i += 3) { + input_level = input[i]; + input_clk = input[i + 1] * 100; + input_vol = input[i + 2]; + dpm_table->dpm_levels[input_level].value = input_clk; + podn_vdd_dep_table->entries[input_level].clk = input_clk; + podn_vdd_dep_table->entries[input_level].vddc = input_vol; } vega10_odn_update_soc_table(hwmgr, type); return 0; -- cgit v1.2.3 From 7997cc1f01caa6fdbd17e0db75224cb89f98eef4 Mon Sep 17 00:00:00 2001 From: Victor Skvortsov Date: Thu, 4 Jun 2026 09:46:17 -0400 Subject: drm/amdgpu: Disable ras_check_bad_page_status on VFs Host driver determines the bad_page_status, not VF. VFs do not have access to the EEPROM, and eeprom_init is skipped. However, check_bad_page_status is called outside of the eeprom_init sequence without any is_vf checks. Add a return false in __is_ras_eeprom_supported for VFs, and use that guard in amdgpu_ras_check_bad_page_status to prevent incorrect access to un-initialized eeprom_control object. Signed-off-by: Victor Skvortsov Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index b265b4d9053f..fca2b49bc13b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -159,6 +159,9 @@ static bool __is_ras_eeprom_supported(struct amdgpu_device *adev) { + if (amdgpu_sriov_vf(adev)) + return false; + switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) { case IP_VERSION(11, 0, 2): /* VEGA20 and ARCTURUS */ case IP_VERSION(11, 0, 7): /* Sienna cichlid */ @@ -1973,7 +1976,7 @@ void amdgpu_ras_check_bad_page_status(struct amdgpu_device *adev) struct amdgpu_ras *ras = amdgpu_ras_get_context(adev); struct amdgpu_ras_eeprom_control *control = ras ? &ras->eeprom_control : NULL; - if (!control || amdgpu_bad_page_threshold == 0) + if (!__is_ras_eeprom_supported(adev) || !control || amdgpu_bad_page_threshold == 0) return; if (control->ras_num_bad_pages > ras->bad_page_cnt_threshold) { -- cgit v1.2.3 From b789664e3e307f98782d45f8b320683333a66042 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Fri, 29 May 2026 22:25:32 -0400 Subject: drm/amdkfd: Clean up suspend_all and resume_all mes Compute user bad/hung queue recovery was handled by KFD using suspend_all_queues_mes, remove_queue(or reset_queue), and resume_all_queues_mes. Since now those steps are centralized to amdgpu_gfx_reset_mes_compute function to sync up with KCQ and KGD user queues, clean up redundant code and rename the function to match its functionality. Signed-off-by: Amber Lin Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 53 ++++------------------ 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 0d95dd941129..14159a682823 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -71,8 +71,7 @@ static int allocate_sdma_queue(struct device_queue_manager *dqm, struct queue *q, const uint32_t *restore_sdma_id); static int reset_queues_on_hws_hang(struct device_queue_manager *dqm, bool is_sdma); -static int resume_all_queues_mes(struct device_queue_manager *dqm); -static int suspend_all_queues_mes(struct device_queue_manager *dqm, struct queue *q); +static int recover_bad_queue_mes(struct device_queue_manager *dqm, struct queue *q); static struct queue *find_queue_by_doorbell_offset(struct device_queue_manager *dqm, u32 doorbell_offset); static void set_queue_as_reset(struct device_queue_manager *dqm, struct queue *q, @@ -308,14 +307,13 @@ static int remove_queue_mes_on_reset_option(struct device_queue_manager *dqm, st amdgpu_mes_unlock(&adev->mes); up_read(&adev->reset_domain->sem); - if (is_for_reset) + if (!r || is_for_reset) return r; - if (r) { - if (!suspend_all_queues_mes(dqm, q)) - return resume_all_queues_mes(dqm); - - dev_err(adev->dev, "failed to remove hardware queue from MES, doorbell=0x%x\n", + /* remove_hw_queue failed. try to recover */ + r = recover_bad_queue_mes(dqm, q); + if (r && amdgpu_gpu_recovery) { + dev_err(adev->dev, "failed to remove queue from MES, doorbell=0x%x\n", q->properties.doorbell_off); dev_err(adev->dev, "MES might be in unrecoverable state, issue a GPU reset\n"); kfd_hws_hang(dqm); @@ -483,7 +481,7 @@ fail: return r; } -static int suspend_all_queues_mes(struct device_queue_manager *dqm, struct queue *q) +static int recover_bad_queue_mes(struct device_queue_manager *dqm, struct queue *q) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; int r = 0; @@ -491,41 +489,12 @@ static int suspend_all_queues_mes(struct device_queue_manager *dqm, struct queue if (!down_read_trylock(&adev->reset_domain->sem)) return -EIO; - - if (!reset_queues_mes(dqm, q)) { - r = 0; - goto out; - } - - dev_err(adev->dev, "failed to suspend gangs from MES\n"); - dev_err(adev->dev, "MES might be in unrecoverable state, issue a GPU reset\n"); - kfd_hws_hang(dqm); -out: + r = reset_queues_mes(dqm, q); up_read(&adev->reset_domain->sem); return r; } -static int resume_all_queues_mes(struct device_queue_manager *dqm) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; - int r = 0; - - if (!down_read_trylock(&adev->reset_domain->sem)) - return -EIO; - - r = amdgpu_mes_resume(adev, ffs(dqm->dev->xcc_mask) - 1); - up_read(&adev->reset_domain->sem); - - if (r) { - dev_err(adev->dev, "failed to resume gangs from MES\n"); - dev_err(adev->dev, "MES might be in unrecoverable state, issue a GPU reset\n"); - kfd_hws_hang(dqm); - } - - return r; -} - static void increment_queue_count(struct device_queue_manager *dqm, struct qcm_process_device *qpd, struct queue *q) @@ -3234,6 +3203,7 @@ void device_queue_manager_uninit(struct device_queue_manager *dqm) kfree(dqm); } +/* bad queue notified by interrupt from CP */ int kfd_dqm_suspend_bad_queue_mes(struct kfd_node *knode, u32 pasid, u32 doorbell_id) { struct kfd_process_device *pdd = NULL; @@ -3253,13 +3223,10 @@ int kfd_dqm_suspend_bad_queue_mes(struct kfd_node *knode, u32 pasid, u32 doorbel list_for_each_entry(q, &qpd->queues_list, list) { if (q->doorbell_id == doorbell_id && q->properties.is_active) { - /* suspend_all handles suspend, remove, resume */ - suspend_all_queues_mes(dqm, q); - + recover_bad_queue_mes(dqm, q); q->properties.is_evicted = true; q->properties.is_active = false; decrement_queue_count(dqm, qpd, q); - break; } } -- cgit v1.2.3 From 927c5b2defb9b09856444d94bebfd056a002bd75 Mon Sep 17 00:00:00 2001 From: Yunxiang Li Date: Thu, 4 Jun 2026 12:59:11 -0400 Subject: drm/amdkfd: Avoid double-unpin of DOORBELL/MMIO BOs on free amdgpu_amdkfd_gpuvm_free_memory_of_gpu() unpinned DOORBELL and MMIO remap BOs (which are pinned at allocation time) before checking whether the BO is still mapped to the GPU. When the BO is still mapped, the function returns -EBUSY and leaves the BO alive, but it has already been unpinned. The BO is then unpinned again when it is finally freed during process teardown, triggering a ttm_bo_unpin() underflow warning: WARNING: CPU: 18 PID: 15066 at ttm/ttm_bo.c:650 amdttm_bo_unpin+0x6d/0x80 [amdttm] Workqueue: kfd_process_wq kfd_process_wq_release [amdgpu] RIP: 0010:amdttm_bo_unpin+0x6d/0x80 [amdttm] Call Trace: amdgpu_bo_unpin+0x1a/0x90 [amdgpu] amdgpu_amdkfd_gpuvm_unpin_bo+0x31/0xb0 [amdgpu] amdgpu_amdkfd_gpuvm_free_memory_of_gpu+0x3bf/0x460 [amdgpu] kfd_process_free_outstanding_kfd_bos+0xd4/0x170 [amdgpu] kfd_process_wq_release+0x109/0x1b0 [amdgpu] process_one_work+0x1e2/0x3b0 worker_thread+0x50/0x3a0 kthread+0xdd/0x100 ret_from_fork+0x29/0x50 Move the unpin after the mapped_to_gpu_memory check so it only happens once we are committed to freeing the BO. Fixes: d25e35bc26c3 ("drm/amdgpu: Pin MMIO/DOORBELL BO's in GTT domain") Signed-off-by: Yunxiang Li Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index d54794e5b18b..35fe2c974699 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -1914,13 +1914,6 @@ int amdgpu_amdkfd_gpuvm_free_memory_of_gpu( mutex_lock(&mem->lock); - /* Unpin MMIO/DOORBELL BO's that were pinned during allocation */ - if (mem->alloc_flags & - (KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL | - KFD_IOC_ALLOC_MEM_FLAGS_MMIO_REMAP)) { - amdgpu_amdkfd_gpuvm_unpin_bo(mem->bo); - } - mapped_to_gpu_memory = mem->mapped_to_gpu_memory; is_imported = mem->is_imported; mutex_unlock(&mem->lock); @@ -1934,6 +1927,15 @@ int amdgpu_amdkfd_gpuvm_free_memory_of_gpu( return -EBUSY; } + /* At this point the BO is guaranteed to be freed, so unpin the + * MMIO/DOORBELL BOs that were pinned during allocation. + */ + if (mem->alloc_flags & + (KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL | + KFD_IOC_ALLOC_MEM_FLAGS_MMIO_REMAP)) { + amdgpu_amdkfd_gpuvm_unpin_bo(mem->bo); + } + /* Make sure restore workers don't access the BO any more */ mutex_lock(&process_info->lock); if (!list_empty(&mem->validate_list)) -- cgit v1.2.3 From cd6397b7af8262a380e188dc32e9de11ff897ed2 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 26 May 2026 14:45:48 +0800 Subject: drm/amdgpu: initialize iter.start in amdgpu_devcoredump_format This fixes read /sys/class/drm/cardN/device/devcoredump/data return empty content sometimes. amdgpu_devcoredump_format() leaves struct drm_print_iterator's .start field uninitialized on the stack before passing it to drm_coredump_printer(). __drm_puts_coredump() compares the running .offset against .start to decide whether to skip or copy each chunk: if (iterator->offset < iterator->start) { if (iterator->offset + len <= iterator->start) { iterator->offset += len; return; } ... } Fixes: 4bbba79a7f1d ("drm/amdgpu: move devcoredump generation to a worker") Acked-by: Alex Deucher Signed-off-by: Qiang Yu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 27830518a230..bed68f0c3080 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -229,6 +229,7 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf sizing_pass = buffer == NULL; iter.data = buffer; + iter.start = 0; iter.offset = 0; iter.remain = count; -- cgit v1.2.3 From 7fa88ae2f44f7a84a34fe470580d0329ecdb760d Mon Sep 17 00:00:00 2001 From: Candice Li Date: Fri, 29 May 2026 12:29:52 +0800 Subject: drm/amd/ras: sleep on PMFW EEPROM busy in bad page count query Use usleep_range() instead of mdelay() when ras_fw_get_badpage_count() retries on -EBUSY so the driver yields the CPU while waiting for PMFW EEPROM to become ready. Signed-off-by: Candice Li Reviewed-by: Yang Wang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_eeprom_fw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom_fw.c b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom_fw.c index f5fa80db91fb..59e195652e42 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom_fw.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom_fw.c @@ -72,7 +72,7 @@ int ras_fw_get_badpage_count(struct ras_core_context *ras_core, if (ret != -EBUSY) return ret; - mdelay(10); + usleep_range(10000, 15000); now = (uint64_t)ktime_to_ms(ktime_get()); } while (now < end); -- cgit v1.2.3 From 09774af2a7591baf49d18818cf5b33e7ac63fc39 Mon Sep 17 00:00:00 2001 From: Candice Li Date: Wed, 3 Jun 2026 09:53:01 +0800 Subject: drm/amd/pm: sleep on PMFW EEPROM busy in bad page count query Use usleep_range() instead of mdelay() to match the behavior of ras_fw_get_badpage_count() in rascore path. Signed-off-by: Candice Li Reviewed-by: Yang Wang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fe929bd89058..688b863672bb 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 @@ -1042,7 +1042,7 @@ static int smu_v13_0_12_get_badpage_count(struct amdgpu_device *adev, uint32_t * /* eeprom is not ready */ if (ret != -EBUSY) return ret; - mdelay(10); + usleep_range(10000, 15000); now = (uint64_t)ktime_to_ms(ktime_get()); } while (now < end); -- cgit v1.2.3 From 9667dc9f1c390627d204510768b8f0ed0a318631 Mon Sep 17 00:00:00 2001 From: Jeevana Muthyala Date: Mon, 25 May 2026 11:43:40 +0530 Subject: drm/amdgpu/vcn4.0: enable secure submission on unified ring Set secure_submission_supported = true for the VCN unified ring funcs in vcn_v4_0.c so secure IBs are allowed on the unified ring. Without this, protected decode submissions are blocked by the common IB gate and can fail playback for secure content. For vcn_v4_0.c, the secure ring funcs are selected for the secure-capable IP version. This change only advertises existing hardware/firmware capability; non-secure decode paths are unaffected. Signed-off-by: Jeevana Muthyala Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c | 45 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c index ff7269bafae8..4389f8e9e40c 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c @@ -1992,7 +1992,7 @@ static int vcn_v4_0_ring_reset(struct amdgpu_ring *ring, return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -static struct amdgpu_ring_funcs vcn_v4_0_unified_ring_vm_funcs = { +static const struct amdgpu_ring_funcs vcn_v4_0_unified_ring_vm_funcs = { .type = AMDGPU_RING_TYPE_VCN_ENC, .align_mask = 0x3f, .nop = VCN_ENC_CMD_NO_OP, @@ -2025,6 +2025,40 @@ static struct amdgpu_ring_funcs vcn_v4_0_unified_ring_vm_funcs = { .reset = vcn_v4_0_ring_reset, }; +static const struct amdgpu_ring_funcs vcn_v4_0_unified_ring_vm_funcs_secure = { + .type = AMDGPU_RING_TYPE_VCN_ENC, + .align_mask = 0x3f, + .nop = VCN_ENC_CMD_NO_OP, + .secure_submission_supported = true, + .no_user_fence = true, + .extra_bytes = sizeof(struct amdgpu_vcn_rb_metadata), + .get_rptr = vcn_v4_0_unified_ring_get_rptr, + .get_wptr = vcn_v4_0_unified_ring_get_wptr, + .set_wptr = vcn_v4_0_unified_ring_set_wptr, + .patch_cs_in_place = vcn_v4_0_ring_patch_cs_in_place, + .emit_frame_size = + SOC15_FLUSH_GPU_TLB_NUM_WREG * 3 + + SOC15_FLUSH_GPU_TLB_NUM_REG_WAIT * 4 + + 4 + /* vcn_v2_0_enc_ring_emit_vm_flush */ + 5 + 5 + /* vcn_v2_0_enc_ring_emit_fence x2 vm fence */ + 1, /* vcn_v2_0_enc_ring_insert_end */ + .emit_ib_size = 5, /* vcn_v2_0_enc_ring_emit_ib */ + .emit_ib = vcn_v2_0_enc_ring_emit_ib, + .emit_fence = vcn_v2_0_enc_ring_emit_fence, + .emit_vm_flush = vcn_v2_0_enc_ring_emit_vm_flush, + .test_ring = amdgpu_vcn_enc_ring_test_ring, + .test_ib = amdgpu_vcn_unified_ring_test_ib, + .insert_nop = amdgpu_ring_insert_nop, + .insert_end = vcn_v2_0_enc_ring_insert_end, + .pad_ib = amdgpu_ring_generic_pad_ib, + .begin_use = amdgpu_vcn_ring_begin_use, + .end_use = amdgpu_vcn_ring_end_use, + .emit_wreg = vcn_v2_0_enc_ring_emit_wreg, + .emit_reg_wait = vcn_v2_0_enc_ring_emit_reg_wait, + .emit_reg_write_reg_wait = amdgpu_ring_emit_reg_write_reg_wait_helper, + .reset = vcn_v4_0_ring_reset, +}; + /** * vcn_v4_0_set_unified_ring_funcs - set unified ring functions * @@ -2041,10 +2075,11 @@ static void vcn_v4_0_set_unified_ring_funcs(struct amdgpu_device *adev) continue; if (amdgpu_ip_version(adev, VCN_HWIP, 0) == IP_VERSION(4, 0, 2)) - vcn_v4_0_unified_ring_vm_funcs.secure_submission_supported = true; - - adev->vcn.inst[i].ring_enc[0].funcs = - (const struct amdgpu_ring_funcs *)&vcn_v4_0_unified_ring_vm_funcs; + adev->vcn.inst[i].ring_enc[0].funcs = + &vcn_v4_0_unified_ring_vm_funcs_secure; + else + adev->vcn.inst[i].ring_enc[0].funcs = + &vcn_v4_0_unified_ring_vm_funcs; adev->vcn.inst[i].ring_enc[0].me = i; } } -- cgit v1.2.3 From 44d1cb67f6c4f542367ec05f47e7f5843a1a75c7 Mon Sep 17 00:00:00 2001 From: Jeevana Muthyala Date: Mon, 25 May 2026 11:49:24 +0530 Subject: drm/amdgpu/vcn4.0.5: enable secure submission on unified ring Set secure_submission_supported = true for the VCN unified ring funcs in vcn_v4_0_5.c so secure IBs are allowed on the unifiedring. Without this, protected decode submissions are blocked by the common IB gate and can fail playback for secure content. For vcn_v4_0_5.c (fixed STX VCN version), secure submission is enabled directly in the ring funcs definition. This change only advertises existing hardware/firmware capability; non-secure decode paths are unaffected. Signed-off-by: Jeevana Muthyala Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c index 1571cc5a148c..c8879a6e5297 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c @@ -1479,10 +1479,11 @@ static int vcn_v4_0_5_ring_reset(struct amdgpu_ring *ring, return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -static struct amdgpu_ring_funcs vcn_v4_0_5_unified_ring_vm_funcs = { +static const struct amdgpu_ring_funcs vcn_v4_0_5_unified_ring_vm_funcs = { .type = AMDGPU_RING_TYPE_VCN_ENC, .align_mask = 0x3f, .nop = VCN_ENC_CMD_NO_OP, + .secure_submission_supported = true, .no_user_fence = true, .get_rptr = vcn_v4_0_5_unified_ring_get_rptr, .get_wptr = vcn_v4_0_5_unified_ring_get_wptr, @@ -1525,9 +1526,6 @@ static void vcn_v4_0_5_set_unified_ring_funcs(struct amdgpu_device *adev) if (adev->vcn.harvest_config & (1 << i)) continue; - if (amdgpu_ip_version(adev, VCN_HWIP, 0) == IP_VERSION(4, 0, 5)) - vcn_v4_0_5_unified_ring_vm_funcs.secure_submission_supported = true; - adev->vcn.inst[i].ring_enc[0].funcs = &vcn_v4_0_5_unified_ring_vm_funcs; adev->vcn.inst[i].ring_enc[0].me = i; } -- cgit v1.2.3 From 840a3c5aeae779a3bc75d7f747c3ed18b1af6507 Mon Sep 17 00:00:00 2001 From: Shubhankar Milind Sardeshpande Date: Thu, 21 May 2026 10:55:18 +0530 Subject: drm/amd/pm: re-enable MC access after PrepareMp1ForUnload on SMU V15 APUs During smu_v15_0_0_system_features_control(), the driver sends a PrepareMp1ForUnload message to PMFW. PMFW then performs nBIF and SYSHUB function-level resets (FLR), disabling PCIe CFG space reset, which clears the framebuffer enable bit to zero and disables MC (memory controller) access from the host. Re-enable MC access via the nbio mc_access_enable callback right after PrepareMp1ForUnload completes in smu_v15_0_0_system_features_control(). Signed-off-by: Shubhankar Milind Sardeshpande Signed-off-by: Suresh Guttula Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 fb1145691410..a214ddbd4c86 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 @@ -227,9 +227,14 @@ static int smu_v15_0_0_system_features_control(struct smu_context *smu, bool en) struct amdgpu_device *adev = smu->adev; int ret = 0; - if (!en && !adev->in_s0ix) + if (!en && !adev->in_s0ix) { ret = smu_cmn_send_smc_msg(smu, SMU_MSG_PrepareMp1ForUnload, NULL); + /* SMU resets BIF_FB_EN to zero, re-enable MC access on APUs with SMU V15 */ + if (!ret && adev->nbio.funcs && adev->nbio.funcs->mc_access_enable) + adev->nbio.funcs->mc_access_enable(adev, true); + } + return ret; } -- cgit v1.2.3 From 88ed96abbbe27b70193544fbc1ee06448c274714 Mon Sep 17 00:00:00 2001 From: David Francis Date: Thu, 4 Jun 2026 15:04:03 -0400 Subject: drm/amdkfd: Properly acquire queue buffers in CRIU restore When kfd_queue_acquire_buffers() was split off from set_queue_properties_from_user(), set_queue_properties_from_criu() was missed. Thus, set_queue_properties_from_criu() is not filling out the buffer fields of queue_properties, which can come up when subsequent code expects them to be non-null. Add the proper call to kfd_queue_acquire_buffers(), and also use the right cast types in set_queue_properties_from_criu() (which were missed at the same time) Signed-off-by: David Francis Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 44e39ce222b7..0ac35789b239 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c @@ -962,8 +962,8 @@ static void set_queue_properties_from_criu(struct queue_properties *qp, qp->priority = q_data->priority; qp->queue_address = q_data->q_address; qp->queue_size = q_data->q_size; - qp->read_ptr = (uint32_t *) q_data->read_ptr_addr; - qp->write_ptr = (uint32_t *) q_data->write_ptr_addr; + qp->read_ptr = (void __user *)q_data->read_ptr_addr; + qp->write_ptr = (void __user *)q_data->write_ptr_addr; qp->eop_ring_buffer_address = q_data->eop_ring_buffer_address; qp->eop_ring_buffer_size = q_data->eop_ring_buffer_size; qp->ctx_save_restore_area_address = q_data->ctx_save_restore_area_address; @@ -1042,10 +1042,18 @@ int kfd_criu_restore_queue(struct kfd_process *p, memset(&qp, 0, sizeof(qp)); set_queue_properties_from_criu(&qp, q_data, NUM_XCC(pdd->dev->adev->gfx.xcc_mask)); + ret = kfd_queue_acquire_buffers(pdd, &qp); + if (ret) { + pr_debug("failed to acquire user queue buffers for CRIU\n"); + goto exit; + } + print_queue_properties(&qp); ret = pqm_create_queue(&p->pqm, pdd->dev, &qp, &queue_id, q_data, mqd, ctl_stack, NULL); if (ret) { + kfd_queue_unref_bo_vas(pdd, &qp); + kfd_queue_release_buffers(pdd, &qp); pr_err("Failed to create new queue err:%d\n", ret); goto exit; } -- cgit v1.2.3 From 698684953ef5583622676cdfe6bcd3e4d1325a1a Mon Sep 17 00:00:00 2001 From: Kent Russell Date: Mon, 20 Apr 2026 11:19:16 -0400 Subject: drm/amdkfd: Move mqd_on_vram out of v9 mqd manager This will allow it to be used outside of gfx9 Signed-off-by: Kent Russell Reviewed-by: David Francis Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c | 14 ++++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h | 2 ++ drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 14 -------------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c index 723b725d20b8..859e51de0d8c 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c @@ -315,3 +315,17 @@ bool kfd_check_hiq_mqd_doorbell_id(struct kfd_node *node, uint32_t doorbell_id, return false; } + +bool mqd_on_vram(struct amdgpu_device *adev) +{ + if (adev->apu_prefer_gtt) + return false; + + switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { + case IP_VERSION(9, 4, 3): + case IP_VERSION(9, 5, 0): + return true; + default: + return false; + } +} diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h index 06ca6235ff1b..140ee1fc5d81 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h @@ -201,4 +201,6 @@ uint64_t kfd_mqd_stride(struct mqd_manager *mm, struct queue_properties *q); bool kfd_check_hiq_mqd_doorbell_id(struct kfd_node *node, uint32_t doorbell_id, uint32_t inst); +bool mqd_on_vram(struct amdgpu_device *adev); + #endif /* KFD_MQD_MANAGER_H_ */ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index 17bfb419b202..9a1edd5b2c69 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -115,20 +115,6 @@ static void set_priority(struct v9_mqd *m, struct queue_properties *q) m->cp_hqd_pipe_priority = pipe_priority_map[q->priority]; } -static bool mqd_on_vram(struct amdgpu_device *adev) -{ - if (adev->apu_prefer_gtt) - return false; - - switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { - case IP_VERSION(9, 4, 3): - case IP_VERSION(9, 5, 0): - return true; - default: - return false; - } -} - static struct kfd_mem_obj *allocate_mqd(struct mqd_manager *mm, struct queue_properties *q) { -- cgit v1.2.3 From f44f2af13c418969be358b15743f939d705de998 Mon Sep 17 00:00:00 2001 From: Yunxiang Li Date: Fri, 5 Jun 2026 08:59:34 -0400 Subject: drm/amdgpu: skip already suspended IP blocks in ip_suspend_phase2 The GPU reload test (S3 / mode1 reset / module reload) triggers a WARN_ON in amdgpu_irq_put() on gfx10 when unloading amdgpu: WARNING: CPU: 0 PID: 2314 at amd/amdgpu/amdgpu_irq.c:676 amdgpu_irq_put+0xc3/0xe0 [amdgpu] Call Trace: gfx_v10_0_hw_fini+0x41/0x150 [amdgpu] amdgpu_ip_block_hw_fini+0x29/0xc0 [amdgpu] amdgpu_device_fini_hw+0x315/0x610 [amdgpu] amdgpu_driver_unload_kms+0x7c/0x90 [amdgpu] amdgpu_pci_remove+0x51/0x90 [amdgpu] amdgpu_device_ip_resume_phase2() skips IP blocks whose status.hw is already set, but amdgpu_device_ip_suspend_phase2() never had the matching guard, so a block can be suspended twice (e.g. a reset or recovery issued while the device is already suspended). The second suspend runs hw_fini again, which now releases the gfx fault IRQs unconditionally, dropping a refcount that is already zero and tripping the WARN_ON in amdgpu_irq_put(). The fault/EOP IRQ get/put were balanced through late_init/hw_fini before, which masked the double-suspend; moving the get into hw_init made the suspend/resume asymmetry visible as an IRQ refcount underflow. Honor status.hw in ip_suspend_phase2() so suspend mirrors resume and a block is only torn down once. Fixes: 9117d8be850b ("drm/amdgpu/gfx: move fault and EOP IRQ get/put to hw_init/hw_fini") Fixes: 482f0e538580 ("drm/amdgpu: fix double ucode load by PSP(v3)") Signed-off-by: Yunxiang Li Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 942f0251c748..0fa2ce36c2ea 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3043,7 +3043,7 @@ static int amdgpu_device_ip_suspend_phase2(struct amdgpu_device *adev) amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D3Entry); for (i = adev->num_ip_blocks - 1; i >= 0; i--) { - if (!adev->ip_blocks[i].status.valid) + if (!adev->ip_blocks[i].status.valid || !adev->ip_blocks[i].status.hw) continue; /* displays are handled in phase1 */ if (adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_DCE) -- cgit v1.2.3 From cb35001b403992a041bf847072bdd23f20cbfbc6 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 4 Jun 2026 16:51:48 -0400 Subject: drm/amdgpu: remove spurious line in amdgpu_ring_find_guilty_fence() Copy-paste error. Fixes: 36ed61b1c01a ("drm/amdgpu/fence: add helper to extract the guilty fence") Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 8569c1c637a2..3043ad041bb4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -842,7 +842,6 @@ amdgpu_ring_find_guilty_fence(struct amdgpu_ring *ring) last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; - ring->ring_backup_entries_to_copy = 0; do { last_seq++; -- cgit v1.2.3 From 85b176185c6589ffe9927b1f99d60b3f308d008e Mon Sep 17 00:00:00 2001 From: Kent Russell Date: Fri, 8 May 2026 17:12:17 -0400 Subject: drm/amdkfd: Extend MQDs in HBM to gfx942 This has proven stable and performant on gfx943 and gfx950, so extend it to the Aldebaran/gfx942 series Signed-off-by: Kent Russell Reviewed-by: David Francis Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c index 859e51de0d8c..f3b73f416c60 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c @@ -322,6 +322,7 @@ bool mqd_on_vram(struct amdgpu_device *adev) return false; switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { + case IP_VERSION(9, 4, 2): case IP_VERSION(9, 4, 3): case IP_VERSION(9, 5, 0): return true; -- cgit v1.2.3 From 01fcac6b50d06d1395de8a6dc66f15f5c5196bee Mon Sep 17 00:00:00 2001 From: Kent Russell Date: Fri, 8 May 2026 17:12:26 -0400 Subject: drm/amdkfd: Extend MQDs in HBM to gfx944 This has proven stable and performant on gfx943 and gfx950, so extend it to gfx944 as well Signed-off-by: Kent Russell Reviewed-by: David Francis Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c index f3b73f416c60..9b7859a77950 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c @@ -324,6 +324,7 @@ bool mqd_on_vram(struct amdgpu_device *adev) switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { case IP_VERSION(9, 4, 2): case IP_VERSION(9, 4, 3): + case IP_VERSION(9, 4, 4): case IP_VERSION(9, 5, 0): return true; default: -- cgit v1.2.3 From 144169e7be0831e09958a906d08d1856751aa6c6 Mon Sep 17 00:00:00 2001 From: Roman Li Date: Wed, 20 May 2026 16:50:34 -0400 Subject: drm/amd/display: Skip PHY SSC reduction on some 8K panels [Why] Some 8K displays cannot tolerate the reduced phy ssc value at high link utilization and show corruption or black screen. [How] Add an EDID panel-id quirk to utilize existing skip_phy_ssc_reduction flag. To pass the link into the quirk handler, change the signature of apply_edid_quirks() to take link as an argument. The dev local in dm_helpers_parse_edid_caps() becomes unused and is removed. Fixes: 5fa62c87cffd ("drm/amd/display: Add option to disable PHY SSC reduction on transmitter enable") Reviewed-by: Alex Hung Signed-off-by: Roman Li Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 f257ea91a34d..c6f94eb71ffa 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 @@ -95,8 +95,11 @@ static u32 edid_extract_panel_id(struct edid *edid) (u32)EDID_PRODUCT_ID(edid); } -static void apply_edid_quirks(struct drm_device *dev, struct edid *edid, struct dc_edid_caps *edid_caps) +static void apply_edid_quirks(struct dc_link *link, struct edid *edid, + struct dc_edid_caps *edid_caps) { + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_device *dev = aconnector->base.dev; uint32_t panel_id = edid_extract_panel_id(edid); switch (panel_id) { @@ -126,6 +129,11 @@ static void apply_edid_quirks(struct drm_device *dev, struct edid *edid, struct drm_dbg_driver(dev, "Disabling VSC on monitor with panel id %X\n", panel_id); edid_caps->panel_patch.disable_colorimetry = true; break; + /* Workaround for monitors that get corrupted by the PHY SSC reduction */ + case drm_edid_encode_panel_id('D', 'E', 'L', 0x4147): + drm_dbg_driver(dev, "Skip PHY SSC reduction on panel id %X\n", panel_id); + link->wa_flags.skip_phy_ssc_reduction = true; + break; default: return; } @@ -147,7 +155,6 @@ enum dc_edid_status dm_helpers_parse_edid_caps( { struct amdgpu_dm_connector *aconnector = link->priv; struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; struct edid *edid_buf = edid ? (struct edid *) edid->raw_edid : NULL; struct cea_sad *sads; int sad_count = -1; @@ -188,7 +195,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes); } - apply_edid_quirks(dev, edid_buf, edid_caps); + apply_edid_quirks(link, edid_buf, edid_caps); sad_count = drm_edid_to_sad((struct edid *) edid->raw_edid, &sads); if (sad_count <= 0) -- cgit v1.2.3 From 5836e669784a52adedcb7b52d9330e526224e387 Mon Sep 17 00:00:00 2001 From: ChunTao Tso Date: Mon, 23 Mar 2026 13:53:26 +0800 Subject: drm/amd/display: TEST_HARNESS FSN could be 0 The frame skipping number could be 0 if needed. Reviewed-by: Robin Chen Signed-off-by: ChunTao Tso Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/modules/power/power_replay.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 983be9759e74..e782501442c4 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_replay.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_replay.c @@ -175,11 +175,10 @@ static bool mod_power_update_replay_active_status(unsigned int active_replay_eve if (link->replay_settings.coasting_vtotal_table[PR_COASTING_TYPE_TEST_HARNESS]) *coasting_vtotal = link->replay_settings.coasting_vtotal_table[PR_COASTING_TYPE_TEST_HARNESS]; - if (link->replay_settings.frame_skip_number_table[PR_COASTING_TYPE_TEST_HARNESS]) { - ASSERT(link->replay_settings.frame_skip_number_table[PR_COASTING_TYPE_TEST_HARNESS] <= 0xFFFF); - *frame_skip_number = - (uint16_t)link->replay_settings.frame_skip_number_table[PR_COASTING_TYPE_TEST_HARNESS]; - } + + ASSERT(link->replay_settings.frame_skip_number_table[PR_COASTING_TYPE_TEST_HARNESS] <= 0xFFFF); + *frame_skip_number = + (uint16_t)link->replay_settings.frame_skip_number_table[PR_COASTING_TYPE_TEST_HARNESS]; /* During the ultra sleep mode testing, disable the timing sync in short vblank mode */ if (active_replay_events & (replay_event_test_harness_enable_replay)) { -- cgit v1.2.3 From 0fde96e06f1cd66d9850488095cc65f5dca5b6b2 Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Tue, 12 May 2026 16:20:54 -0400 Subject: drm/amd/display: Deprecate DMUB register offload functionality [Why] The DMUB register offload feature should no longer be used. This was originally a debug feature for DCN21. No longer applicable to the DMUB programming model. [How] Remove DMUB register offload infrastructure including helper functions, structures, debug options, and register sequence macros. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Austin Zheng Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 - drivers/gpu/drm/amd/display/dc/dc.h | 3 - drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c | 12 -- drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h | 9 - drivers/gpu/drm/amd/display/dc/dc_helper.c | 226 --------------------- drivers/gpu/drm/amd/display/dc/dm_services.h | 4 - .../drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c | 5 - .../drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c | 3 - drivers/gpu/drm/amd/display/dc/inc/reg_helper.h | 19 -- .../gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c | 4 - .../gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c | 5 - .../drm/amd/display/dc/optc/dcn314/dcn314_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c | 5 - .../drm/amd/display/dc/optc/dcn401/dcn401_optc.c | 5 - .../amd/display/dc/resource/dcn35/dcn35_resource.c | 1 - .../display/dc/resource/dcn351/dcn351_resource.c | 1 - .../amd/display/dc/resource/dcn36/dcn36_resource.c | 1 - 20 files changed, 330 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 1ed697a3a453..40f32c8024a0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -13915,13 +13915,6 @@ uint32_t dm_read_reg_func(const struct dc_context *ctx, uint32_t address, } #endif - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress && - !ctx->dmub_srv->reg_helper_offload.should_burst_write) { - ASSERT(false); - return 0; - } - amdgpu_dm_exit_ips_for_hw_access(ctx->dc); value = cgs_read_register(ctx->cgs_device, address); diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 82d02ebbd829..d5d9d56fbcb8 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1129,8 +1129,6 @@ struct dc_debug_options { unsigned int force_fclk_khz; bool enable_tri_buf; bool ips_disallow_entry; - bool dmub_offload_enabled; - bool dmcub_emulation; bool disable_idle_power_optimizations; unsigned int mall_size_override; unsigned int mall_additional_timer_percent; @@ -1332,7 +1330,6 @@ struct dc_init_data { enum dce_environment dce_environment; struct dmub_offload_funcs *dmub_if; - struct dc_reg_helper_state *dmub_offload; struct dc_config flags; uint64_t log_mask; diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c index 0ee5c0c5545c..4c81989898e2 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c @@ -518,9 +518,6 @@ void dc_dmub_srv_query_caps_cmd(struct dc_dmub_srv *dc_dmub_srv) { union dmub_rb_cmd cmd = { 0 }; - if (dc_dmub_srv->ctx->dc->debug.dmcub_emulation) - return; - memset(&cmd, 0, sizeof(cmd)); /* Prepare fw command */ @@ -1302,9 +1299,6 @@ bool dc_dmub_srv_is_hw_pwr_up(struct dc_dmub_srv *dc_dmub_srv, bool wait) if (!dc_dmub_srv || !dc_dmub_srv->dmub) return true; - if (dc_dmub_srv->ctx->dc->debug.dmcub_emulation) - return true; - dc_ctx = dc_dmub_srv->ctx; if (wait) { @@ -1345,9 +1339,6 @@ static void dc_dmub_srv_notify_idle(const struct dc *dc, bool allow_idle) struct dc_dmub_srv *dc_dmub_srv; union dmub_rb_cmd cmd = {0}; - if (dc->debug.dmcub_emulation) - return; - if (!dc->ctx->dmub_srv || !dc->ctx->dmub_srv->dmub) return; @@ -1466,9 +1457,6 @@ static void dc_dmub_srv_exit_low_power_state(const struct dc *dc) struct dc_dmub_srv *dc_dmub_srv; uint32_t rcg_exit_count = 0, ips1_exit_count = 0, ips2_exit_count = 0, ips1z8_exit_count = 0; - if (dc->debug.dmcub_emulation) - return; - if (!dc->ctx->dmub_srv || !dc->ctx->dmub_srv->dmub) return; diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h index ebcaf49e5961..5d399e6a8345 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h @@ -37,17 +37,8 @@ struct dc_crtc_timing; struct dc_state; struct dc_surface_update; -struct dc_reg_helper_state { - bool gather_in_progress; - uint32_t same_addr_count; - bool should_burst_write; - union dmub_rb_cmd cmd_data; - unsigned int reg_seq_count; -}; - struct dc_dmub_srv { struct dmub_srv *dmub; - struct dc_reg_helper_state reg_helper_offload; struct dc_context *ctx; void *dm; diff --git a/drivers/gpu/drm/amd/display/dc/dc_helper.c b/drivers/gpu/drm/amd/display/dc/dc_helper.c index 0e0165764a57..cc7fea613d9e 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dc_helper.c @@ -39,53 +39,6 @@ #define DC_LOGGER \ ctx->logger -static inline void submit_dmub_read_modify_write( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - struct dmub_rb_cmd_read_modify_write *cmd_buf = &offload->cmd_data.read_modify_write; - - offload->should_burst_write = - (offload->same_addr_count == (DMUB_READ_MODIFY_WRITE_SEQ__MAX - 1)); - cmd_buf->header.payload_bytes = - sizeof(struct dmub_cmd_read_modify_write_sequence) * offload->reg_seq_count; - - dc_wake_and_execute_dmub_cmd(ctx, &offload->cmd_data, DM_DMUB_WAIT_TYPE_NO_WAIT); - - memset(cmd_buf, 0, sizeof(*cmd_buf)); - - offload->reg_seq_count = 0; - offload->same_addr_count = 0; -} - -static inline void submit_dmub_burst_write( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - struct dmub_rb_cmd_burst_write *cmd_buf = &offload->cmd_data.burst_write; - - cmd_buf->header.payload_bytes = - sizeof(uint32_t) * offload->reg_seq_count; - - dc_wake_and_execute_dmub_cmd(ctx, &offload->cmd_data, DM_DMUB_WAIT_TYPE_NO_WAIT); - - memset(cmd_buf, 0, sizeof(*cmd_buf)); - - offload->reg_seq_count = 0; -} - -static inline void submit_dmub_reg_wait( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - struct dmub_rb_cmd_reg_wait *cmd_buf = &offload->cmd_data.reg_wait; - - dc_wake_and_execute_dmub_cmd(ctx, &offload->cmd_data, DM_DMUB_WAIT_TYPE_NO_WAIT); - - memset(cmd_buf, 0, sizeof(*cmd_buf)); - offload->reg_seq_count = 0; -} - struct dc_reg_value_masks { uint32_t value; uint32_t mask; @@ -127,98 +80,6 @@ static void set_reg_field_values(struct dc_reg_value_masks *field_value_mask, } } -static void dmub_flush_buffer_execute( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - submit_dmub_read_modify_write(offload, ctx); -} - -static void dmub_flush_burst_write_buffer_execute( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - submit_dmub_burst_write(offload, ctx); -} - -static bool dmub_reg_value_burst_set_pack(const struct dc_context *ctx, uint32_t addr, - uint32_t reg_val) -{ - struct dc_reg_helper_state *offload = &ctx->dmub_srv->reg_helper_offload; - struct dmub_rb_cmd_burst_write *cmd_buf = &offload->cmd_data.burst_write; - - /* flush command if buffer is full */ - if (offload->reg_seq_count == DMUB_BURST_WRITE_VALUES__MAX) - dmub_flush_burst_write_buffer_execute(offload, ctx); - - if (offload->cmd_data.cmd_common.header.type == DMUB_CMD__REG_SEQ_BURST_WRITE && - addr != cmd_buf->addr) { - dmub_flush_burst_write_buffer_execute(offload, ctx); - return false; - } - - cmd_buf->header.type = DMUB_CMD__REG_SEQ_BURST_WRITE; - cmd_buf->header.sub_type = 0; - cmd_buf->addr = addr; - cmd_buf->write_values[offload->reg_seq_count] = reg_val; - offload->reg_seq_count++; - - return true; -} - -static uint32_t dmub_reg_value_pack(const struct dc_context *ctx, uint32_t addr, - struct dc_reg_value_masks *field_value_mask) -{ - struct dc_reg_helper_state *offload = &ctx->dmub_srv->reg_helper_offload; - struct dmub_rb_cmd_read_modify_write *cmd_buf = &offload->cmd_data.read_modify_write; - struct dmub_cmd_read_modify_write_sequence *seq; - - /* flush command if buffer is full */ - if (offload->cmd_data.cmd_common.header.type != DMUB_CMD__REG_SEQ_BURST_WRITE && - offload->reg_seq_count == DMUB_READ_MODIFY_WRITE_SEQ__MAX) - dmub_flush_buffer_execute(offload, ctx); - - if (offload->should_burst_write) { - if (dmub_reg_value_burst_set_pack(ctx, addr, field_value_mask->value)) - return field_value_mask->value; - else - offload->should_burst_write = false; - } - - /* pack commands */ - cmd_buf->header.type = DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE; - cmd_buf->header.sub_type = 0; - seq = &cmd_buf->seq[offload->reg_seq_count]; - - if (offload->reg_seq_count) { - if (cmd_buf->seq[offload->reg_seq_count - 1].addr == addr) - offload->same_addr_count++; - else - offload->same_addr_count = 0; - } - - seq->addr = addr; - seq->modify_mask = field_value_mask->mask; - seq->modify_value = field_value_mask->value; - offload->reg_seq_count++; - - return field_value_mask->value; -} - -static void dmub_reg_wait_done_pack(const struct dc_context *ctx, uint32_t addr, - uint32_t mask, uint32_t shift, uint32_t condition_value, uint32_t time_out_us) -{ - struct dc_reg_helper_state *offload = &ctx->dmub_srv->reg_helper_offload; - struct dmub_rb_cmd_reg_wait *cmd_buf = &offload->cmd_data.reg_wait; - - cmd_buf->header.type = DMUB_CMD__REG_REG_WAIT; - cmd_buf->header.sub_type = 0; - cmd_buf->reg_wait.addr = addr; - cmd_buf->reg_wait.condition_field_value = mask & (condition_value << shift); - cmd_buf->reg_wait.mask = mask; - cmd_buf->reg_wait.time_out_us = time_out_us; -} - uint32_t generic_reg_update_ex(const struct dc_context *ctx, uint32_t addr, int n, uint8_t shift1, uint32_t mask1, uint32_t field_value1, @@ -235,11 +96,6 @@ uint32_t generic_reg_update_ex(const struct dc_context *ctx, va_end(ap); - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) - return dmub_reg_value_pack(ctx, addr, &field_value_mask); - /* todo: return void so we can decouple code running in driver from register states */ - /* mmio write directly */ reg_val = dm_read_reg(ctx, addr); reg_val = (reg_val & ~field_value_mask.mask) | field_value_mask.value; @@ -265,12 +121,6 @@ uint32_t generic_reg_set_ex(const struct dc_context *ctx, /* mmio write directly */ reg_val = (reg_val & ~field_value_mask.mask) | field_value_mask.value; - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) { - return dmub_reg_value_burst_set_pack(ctx, addr, reg_val); - /* todo: return void so we can decouple code running in driver from register states */ - } - dm_write_reg(ctx, addr, reg_val); return reg_val; } @@ -434,13 +284,6 @@ void generic_reg_wait(const struct dc_context *ctx, uint32_t reg_val; unsigned int i; - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) { - dmub_reg_wait_done_pack(ctx, addr, mask, shift, condition_value, - delay_between_poll_us * time_out_num_tries); - return; - } - /* * Something is terribly wrong if time out is > 3000ms. * 3000ms is the maximum time needed for SMU to pass values back. @@ -491,12 +334,6 @@ uint32_t generic_read_indirect_reg(const struct dc_context *ctx, { uint32_t value = 0; - // when reg read, there should not be any offload. - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) { - ASSERT(false); - } - dm_write_reg(ctx, addr_index, index); value = dm_read_reg(ctx, addr_data); @@ -624,69 +461,6 @@ uint32_t generic_indirect_reg_get_sync(const struct dc_context *ctx, return value; } -void reg_sequence_start_gather(const struct dc_context *ctx) -{ - /* if reg sequence is supported and enabled, set flag to - * indicate we want to have REG_SET, REG_UPDATE macro build - * reg sequence command buffer rather than MMIO directly. - */ - - if (ctx->dmub_srv && ctx->dc->debug.dmub_offload_enabled) { - struct dc_reg_helper_state *offload = - &ctx->dmub_srv->reg_helper_offload; - - /* caller sequence mismatch. need to debug caller. offload will not work!!! */ - ASSERT(!offload->gather_in_progress); - - offload->gather_in_progress = true; - } -} - -void reg_sequence_start_execute(const struct dc_context *ctx) -{ - struct dc_reg_helper_state *offload; - - if (!ctx->dmub_srv) - return; - - offload = &ctx->dmub_srv->reg_helper_offload; - - if (offload && offload->gather_in_progress) { - offload->gather_in_progress = false; - offload->should_burst_write = false; - switch (offload->cmd_data.cmd_common.header.type) { - case DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE: - submit_dmub_read_modify_write(offload, ctx); - break; - case DMUB_CMD__REG_REG_WAIT: - submit_dmub_reg_wait(offload, ctx); - break; - case DMUB_CMD__REG_SEQ_BURST_WRITE: - submit_dmub_burst_write(offload, ctx); - break; - default: - return; - } - } -} - -void reg_sequence_wait_done(const struct dc_context *ctx) -{ - /* callback to DM to poll for last submission done*/ - struct dc_reg_helper_state *offload; - - if (!ctx->dmub_srv) - return; - - offload = &ctx->dmub_srv->reg_helper_offload; - - if (offload && - ctx->dc->debug.dmub_offload_enabled && - !ctx->dc->debug.dmcub_emulation) { - dc_dmub_srv_wait_for_idle(ctx->dmub_srv, DM_DMUB_WAIT_TYPE_WAIT, NULL); - } -} - char *dce_version_to_string(const int version) { switch (version) { diff --git a/drivers/gpu/drm/amd/display/dc/dm_services.h b/drivers/gpu/drm/amd/display/dc/dm_services.h index 8b062b011fc6..2cf4bcb03cb0 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services.h @@ -127,10 +127,6 @@ uint32_t generic_reg_update_ex(const struct dc_context *ctx, struct dc_dmub_srv *dc_dmub_srv_create(struct dc *dc, struct dmub_srv *dmub); void dc_dmub_srv_destroy(struct dc_dmub_srv **dmub_srv); -void reg_sequence_start_gather(const struct dc_context *ctx); -void reg_sequence_start_execute(const struct dc_context *ctx); -void reg_sequence_wait_done(const struct dc_context *ctx); - #define FD(reg_field) reg_field ## __SHIFT, \ reg_field ## _MASK 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 53b21adc6267..9788628cf0ad 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 @@ -397,8 +397,6 @@ void dpp1_cm_program_regamma_lut(struct dpp *dpp_base, uint32_t i; struct dcn10_dpp *dpp = TO_DCN10_DPP(dpp_base); - REG_SEQ_START(); - for (i = 0 ; i < num; i++) { REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].red_reg); REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].green_reg); @@ -408,9 +406,6 @@ void dpp1_cm_program_regamma_lut(struct dpp *dpp_base, REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].delta_green_reg); REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].delta_blue_reg); } - - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); } void dpp1_cm_configure_regamma_lut( 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 8f9038fec0f7..01027d120cb0 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 @@ -581,9 +581,6 @@ void dcn35_power_down_on_boot(struct dc *dc) bool dcn35_apply_idle_power_optimizations(struct dc *dc, bool enable) { - if (dc->debug.dmcub_emulation) - return true; - if (enable) { uint32_t num_active_edp = 0; int i; diff --git a/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h b/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h index 7a1ecb8d986f..6d15ccdc7f87 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h +++ b/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h @@ -536,23 +536,4 @@ uint32_t generic_indirect_reg_update_ex_sync(const struct dc_context *ctx, uint8_t shift1, uint32_t mask1, uint32_t field_value1, ...); -/* register offload macros - * - * instead of MMIO to register directly, in some cases we want - * to gather register sequence and execute the register sequence - * from another thread so we optimize time required for lengthy ops - */ - -/* start gathering register sequence */ -#define REG_SEQ_START() \ - reg_sequence_start_gather(CTX) - -/* start execution of register sequence gathered since REG_SEQ_START */ -#define REG_SEQ_SUBMIT() \ - reg_sequence_start_execute(CTX) - -/* wait for the last REG_SEQ_SUBMIT to finish */ -#define REG_SEQ_WAIT_DONE() \ - reg_sequence_wait_done(CTX) - #endif /* DRIVERS_GPU_DRM_AMD_DC_DEV_DC_INC_REG_HELPER_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c index fa600593f4c1..0e09d073ab29 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c @@ -380,7 +380,6 @@ static void mpc20_program_ogam_pwl( struct dcn20_mpc *mpc20 = TO_DCN20_MPC(mpc); PERF_TRACE(); - REG_SEQ_START(); for (i = 0 ; i < num; i++) { REG_SET(MPCC_OGAM_LUT_DATA[mpcc_id], 0, MPCC_OGAM_LUT_DATA, rgb[i].red_reg); @@ -395,9 +394,6 @@ static void mpc20_program_ogam_pwl( MPCC_OGAM_LUT_DATA, rgb[i].delta_blue_reg); } - REG_SEQ_SUBMIT(); - PERF_TRACE(); - REG_SEQ_WAIT_DONE(); PERF_TRACE(); } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c index e6426ccee2d8..cf8e22289d6a 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c @@ -539,16 +539,11 @@ static bool optc1_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 3, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c index c558b1d633f3..73cc8a713556 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c @@ -63,16 +63,11 @@ bool optc2_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 3, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c index 98aaa22ce81c..3ace83e1b50f 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c @@ -105,16 +105,11 @@ static bool optc31_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c index a7cf34937b2f..7250478a5092 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c @@ -115,16 +115,11 @@ static bool optc314_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c index 07895d5f4dfa..f9e05efcad98 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c @@ -155,16 +155,11 @@ static bool optc32_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c index 62f45c156c32..9b7f9d5bbfb3 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c @@ -122,16 +122,11 @@ static bool optc35_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c index a6d76f451cf8..5fcdd74eb4a0 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c @@ -189,16 +189,11 @@ bool optc401_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return 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 53596e790eb4..a5ed62db1de8 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 @@ -819,7 +819,6 @@ static const struct dc_debug_options debug_defaults_drv = { .enable_hpo_pg_support = false, .enable_single_display_2to1_odm_policy = true, .disable_idle_power_optimizations = false, - .dmcub_emulation = false, .disable_boot_optimizations = false, .disable_unbounded_requesting = false, .disable_mem_low_power = false, 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 3e2c9cfd555d..9c1d65c2d4ab 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 @@ -799,7 +799,6 @@ static const struct dc_debug_options debug_defaults_drv = { .enable_hpo_pg_support = false, .enable_single_display_2to1_odm_policy = true, .disable_idle_power_optimizations = false, - .dmcub_emulation = false, .disable_boot_optimizations = false, .disable_unbounded_requesting = false, .disable_mem_low_power = false, 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 9e795130eb89..8041e035f226 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 @@ -806,7 +806,6 @@ static const struct dc_debug_options debug_defaults_drv = { .enable_hpo_pg_support = false, .enable_single_display_2to1_odm_policy = true, .disable_idle_power_optimizations = false, - .dmcub_emulation = false, .disable_boot_optimizations = false, .disable_unbounded_requesting = false, .disable_mem_low_power = false, -- cgit v1.2.3 From ec9b4b2629c4f0754f011bf9e9283af940e80f3e Mon Sep 17 00:00:00 2001 From: Ovidiu Bunea Date: Thu, 21 May 2026 15:27:11 -0400 Subject: drm/amd/display: Temp disable repeater FGCG as workaround [why & how] There is an issue that is seemingly limited to DCN42 where systems with IOMMU enabled will hang during reboot stress testing. The hang happens shortly after DCN PG exit happens and HUBP is programmed for the first flip, but before the first surface address is latched. Testing has shown that disabling DCCG_GLOBAL_FGCG_REP_DIS, HUBP_FGCG_REP_DIS, and DCFCLK_GATE_DIS can mask this issue. Disable FGCG for these three repeater bits to avoid issue while debug is on-going. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Ovidiu Bunea Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 6 +++++- drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c | 6 ++++++ drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 9 ++++++++- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 1 + 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index d5d9d56fbcb8..d74776802418 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1288,6 +1288,7 @@ struct dc_debug_options { unsigned int min_deep_sleep_dcfclk_khz; unsigned int force_odm2to1_for_edp_pixclk_mhz; bool enable_replay_esd_recovery; + uint8_t iommu_mismatch_temp_wka; }; diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index e57242f8bc12..adc453c81831 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -81,8 +81,12 @@ void dccg42_enable_global_fgcg(struct dccg *dccg, bool value) { struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); - if (dccg->ctx->dc->debug.disable_clock_gate) + /* Temporary workaround for IOMMU mismatch issue. + * Fine grain control via bit2 of debug flag. + */ + if (dccg->ctx->dc->debug.disable_clock_gate || (dccg->ctx->dc->debug.iommu_mismatch_temp_wka & 0x4)) value = false; + REG_UPDATE(DCCG_GLOBAL_FGCG_REP_CNTL, DCCG_GLOBAL_FGCG_REP_DIS, !value); } diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c index e4602c3ddc66..57de98444f6c 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c @@ -20,6 +20,12 @@ static void hubp42_set_fgcg(struct hubp *hubp, bool enable) { struct dcn20_hubp *hubp2 = TO_DCN20_HUBP(hubp); + /* Temporary workaround for IOMMU mismatch issue. + * Fine grain control via bit1 of debug flag. + */ + if (hubp->ctx->dc->debug.iommu_mismatch_temp_wka & 0x2) + enable = false; + REG_UPDATE(HUBP_CLK_CNTL, HUBP_FGCG_REP_DIS, !enable); } 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 664004cadf10..96e0133880e1 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 @@ -70,6 +70,7 @@ void dcn42_init_hw(struct dc *dc) uint32_t user_level = MAX_BACKLIGHT_LEVEL; bool dchub_ref_freq_changed; int current_dchub_ref_freq = 0; + uint8_t dcfclk_gate_dis_value = 0; if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->init_clocks) { dc->clk_mgr->funcs->init_clocks(dc->clk_mgr); @@ -243,7 +244,13 @@ void dcn42_init_hw(struct dc *dc) /* enable all DCN clock gating */ REG_WRITE(DCCG_GATE_DISABLE_CNTL, 0); - REG_UPDATE(DCFCLK_CNTL, DCFCLK_GATE_DIS, 0); + /* Temporary workaround for IOMMU mismatch issue. + * Fine grain control via bit0 of debug flag. + */ + if (dc->debug.iommu_mismatch_temp_wka & 0x1) + dcfclk_gate_dis_value = 1; + + REG_UPDATE(DCFCLK_CNTL, DCFCLK_GATE_DIS, dcfclk_gate_dis_value); } dcn401_setup_hpo_hw_control(hws, 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 7de12b16d7ad..eb7fe5d70264 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 @@ -801,6 +801,7 @@ static const struct dc_debug_options debug_defaults_drv = { .replay_skip_crtc_disabled = true, .psr_skip_crtc_disable = true, .force_odm2to1_for_edp_pixclk_mhz = 0, // disable the policy for now + .iommu_mismatch_temp_wka = 0x7, }; static const struct dc_check_config config_defaults = { -- cgit v1.2.3 From 042b0a39806cf6019cc47047424868c2d130567d Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 12:08:20 -0600 Subject: drm/amd/display: Fix writeback format loop and variable init [WHAT] 1. Use ARRAY_SIZE() instead of manual sizeof division for the format array iteration. Add a break statement to exit the loop early once a matching format is found. 2. Remove redundant zero initialization of res since all paths assign before use. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 110f0173eee6..ead3d0bb052f 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 @@ -59,9 +59,11 @@ static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, return -EINVAL; } - for (i = 0; i < sizeof(amdgpu_dm_wb_formats) / sizeof(u32); i++) { - if (fb->format->format == amdgpu_dm_wb_formats[i]) + for (i = 0; i < ARRAY_SIZE(amdgpu_dm_wb_formats); i++) { + if (fb->format->format == amdgpu_dm_wb_formats[i]) { found = true; + break; + } } if (!found) { @@ -187,7 +189,7 @@ int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm, { struct dc *dc = dm->dc; struct dc_link *link = dc_get_link_at_index(dc, link_index); - int res = 0; + int res; wbcon->link = link; -- cgit v1.2.3 From 948739e14f3cc33679601e444c97f6704f1d327d Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 12:08:49 -0600 Subject: drm/amd/display: Add KUnit tests for writeback connector [WHAT] Add KUnit tests for amdgpu_dm_wb_encoder_atomic_check() and amdgpu_dm_wb_connector_get_modes(). Tests cover null job, null fb, size mismatch, format validation, and mode count bounds using DRM KUnit mock devices. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 7 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h | 13 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 336 +++++++++++++++++++++ 4 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c 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 ead3d0bb052f..058d478a073d 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,6 +29,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_display.h" #include "dc.h" @@ -40,7 +41,7 @@ static const u32 amdgpu_dm_wb_formats[] = { DRM_FORMAT_XRGB2101010, }; -static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, +STATIC_IFN_KUNIT int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { @@ -74,13 +75,15 @@ static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_wb_encoder_atomic_check); -static int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector) +STATIC_IFN_KUNIT int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector) { /* Maximum resolution supported by DWB */ return drm_add_modes_noedid(connector, 3840, 2160); } +EXPORT_IF_KUNIT(amdgpu_dm_wb_connector_get_modes); static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, struct drm_writeback_job *job) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h index 13d31c857dee..7e9fd7a036fa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h @@ -29,8 +29,21 @@ #include +struct amdgpu_display_manager; +struct amdgpu_dm_wb_connector; + int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm, struct amdgpu_dm_wb_connector *dm_wbcon, uint32_t link_index); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#include +#include + +int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); +int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector); +#endif + #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 768f9bbc50e1..ce1e46acb7af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o 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 new file mode 100644 index 000000000000..b8ad4b87163a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_wb.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amdgpu_dm_wb.h" + + +/* Helper functions */ + +static struct drm_crtc_state *alloc_test_crtc_state(struct kunit *test, + int hdisplay, int vdisplay) +{ + struct drm_crtc_state *crtc_state; + + crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc_state); + + crtc_state->mode.hdisplay = hdisplay; + crtc_state->mode.vdisplay = vdisplay; + + return crtc_state; +} + +static struct drm_connector_state *alloc_test_conn_state(struct kunit *test, + int fb_width, + int fb_height, + u32 format) +{ + struct drm_connector_state *conn_state; + struct drm_writeback_job *job; + struct drm_framebuffer *fb; + struct drm_format_info *fmt_info; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fb); + + fmt_info = kunit_kzalloc(test, sizeof(*fmt_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fmt_info); + + fb->width = fb_width; + fb->height = fb_height; + fmt_info->format = format; + fb->format = fmt_info; + + job->fb = fb; + conn_state->writeback_job = job; + + return conn_state; +} + +/* Tests for amdgpu_dm_wb_encoder_atomic_check */ + +/** + * dm_test_wb_atomic_check_no_job - Verify early return when no writeback job + * @test: KUnit test context + * + * When conn_state->writeback_job is NULL, no writeback is requested and the + * function should return 0 without further validation. + */ +static void dm_test_wb_atomic_check_no_job(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + /* No writeback_job — should return 0 */ + conn_state->writeback_job = NULL; + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_atomic_check_no_fb - Verify early return when job has no framebuffer + * @test: KUnit test context + * + * When a writeback job exists but job->fb is NULL, the function should return 0 + * without validating dimensions or pixel format. + */ +static void dm_test_wb_atomic_check_no_fb(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_writeback_job *job; + int ret; + + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + /* writeback_job exists but no fb — should return 0 */ + job->fb = NULL; + conn_state->writeback_job = job; + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_atomic_check_valid - Verify success with matching size and supported format + * @test: KUnit test context + * + * When the framebuffer dimensions match the CRTC mode and the pixel format is + * in the supported formats list, the function should return 0. + */ +static void dm_test_wb_atomic_check_valid(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1920, 1080, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_atomic_check_size_mismatch - Verify rejection when both dimensions differ + * @test: KUnit test context + * + * When both framebuffer width and height differ from the CRTC mode, the + * function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_size_mismatch(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* FB is 3840x2160 but mode is 1920x1080 */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 3840, 2160, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_wb_atomic_check_width_mismatch - Verify rejection when width alone differs + * @test: KUnit test context + * + * When only the framebuffer width differs from the CRTC mode hdisplay, the + * function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_width_mismatch(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* Width doesn't match */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1280, 1080, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_wb_atomic_check_height_mismatch - Verify rejection when height alone differs + * @test: KUnit test context + * + * When only the framebuffer height differs from the CRTC mode vdisplay, the + * function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_height_mismatch(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* Height doesn't match */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1920, 720, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_wb_atomic_check_invalid_format - Verify rejection of unsupported pixel format + * @test: KUnit test context + * + * When the framebuffer dimensions match but the pixel format is not in + * amdgpu_dm_wb_formats[], the function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_invalid_format(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* Correct size but unsupported format */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1920, 1080, + DRM_FORMAT_XRGB8888); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* Tests for amdgpu_dm_wb_connector_get_modes using DRM mock */ + +static const struct drm_connector_funcs dm_wb_test_connector_funcs = { + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .reset = drm_atomic_helper_connector_reset, +}; + +/** + * dm_test_wb_get_modes_returns_modes - Verify at least one mode is returned + * @test: KUnit test context + * + * Uses a DRM mock connector to verify that amdgpu_dm_wb_connector_get_modes() + * populates the connector with at least one display mode. + */ +static void dm_test_wb_get_modes_returns_modes(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + int count; + + 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(*drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + + count = amdgpu_dm_wb_connector_get_modes(connector); + + /* drm_add_modes_noedid should return at least one mode */ + KUNIT_EXPECT_GT(test, count, 0); +} + +/** + * dm_test_wb_get_modes_bounded_by_max - Verify all modes are within max resolution + * @test: KUnit test context + * + * Uses a DRM mock connector to verify that all modes returned by + * amdgpu_dm_wb_connector_get_modes() have hdisplay <= 3840 and + * vdisplay <= 2160, matching the DWB hardware maximum. + */ +static void dm_test_wb_get_modes_bounded_by_max(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct drm_display_mode *mode; + + 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(*drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + + amdgpu_dm_wb_connector_get_modes(connector); + + /* All modes must fit within 3840x2160 */ + list_for_each_entry(mode, &connector->probed_modes, head) { + KUNIT_EXPECT_LE(test, mode->hdisplay, 3840); + KUNIT_EXPECT_LE(test, mode->vdisplay, 2160); + } +} + +static struct kunit_case dm_wb_test_cases[] = { + /* amdgpu_dm_wb_encoder_atomic_check */ + KUNIT_CASE(dm_test_wb_atomic_check_no_job), + KUNIT_CASE(dm_test_wb_atomic_check_no_fb), + KUNIT_CASE(dm_test_wb_atomic_check_valid), + KUNIT_CASE(dm_test_wb_atomic_check_size_mismatch), + KUNIT_CASE(dm_test_wb_atomic_check_width_mismatch), + KUNIT_CASE(dm_test_wb_atomic_check_height_mismatch), + KUNIT_CASE(dm_test_wb_atomic_check_invalid_format), + /* amdgpu_dm_wb_connector_get_modes */ + KUNIT_CASE(dm_test_wb_get_modes_returns_modes), + KUNIT_CASE(dm_test_wb_get_modes_bounded_by_max), + {} +}; + +static struct kunit_suite dm_wb_test_suite = { + .name = "amdgpu_dm_wb", + .test_cases = dm_wb_test_cases, +}; + +kunit_test_suite(dm_wb_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_wb"); -- cgit v1.2.3 From f48124ff35357d05a98d9cd5cb7982e7e5509003 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Thu, 21 May 2026 20:36:01 -0400 Subject: drm/amd/display: fix max dispclk_khz/dppclk_khz double 1000 [why] Fix regresson caused by double roundup and index out of range Reviewed-by: Dillon Varone Reviewed-by: Dmytro Laktyushkin Signed-off-by: Charlene Liu Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 de40d7bae252..11fc0b1cd152 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 @@ -118,16 +118,16 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta context->bw_ctx.bw.dcn.clk.bw_dispclk_khz = context->bw_ctx.bw.dcn.clk.dispclk_khz; if (in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.num_clk_values > 1) { context->bw_ctx.bw.dcn.clk.max_supported_dispclk_khz = - in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.clk_values_khz[in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.num_clk_values] * 1000; + in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.clk_values_khz[in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.num_clk_values - 1]; } else { - context->bw_ctx.bw.dcn.clk.max_supported_dispclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.clk_values_khz[0] * 1000; + context->bw_ctx.bw.dcn.clk.max_supported_dispclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dispclk.clk_values_khz[0]; } if (in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.num_clk_values > 1) { context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz = - in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.num_clk_values] * 1000; + in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.num_clk_values - 1]; } else { - context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[0] * 1000; + context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[0]; } /* get global mall allocation */ -- cgit v1.2.3 From 828a1a67e15e234f0ae59dc735350e525aa7dd66 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 27 May 2026 16:20:29 -0600 Subject: drm/amd/display: remove redundant code in amdgpu_dm_replay [WHAT] In amdgpu_dm_link_setup_replay(), nom_coasting_vtotal was used only once immediately after in set_replay_coasting_vtotal(). Inline the value directly to remove the no-op alias. In amdgpu_dm_set_replay_caps(), replace link->ctx->dc->debug with dc->debug since dc is already assigned as link->ctx->dc, eliminating a redundant pointer round-trip. Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Ray Wu Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 22aa4305d2af..f3cea2aba901 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 @@ -121,8 +121,7 @@ bool amdgpu_dm_set_replay_caps(struct dc_link *link, struct amdgpu_dm_connector debug_flags = (union replay_debug_flags *)&pr_config.debug_flags; debug_flags->u32All = 0; - debug_flags->bitfields.visual_confirm = - link->ctx->dc->debug.visual_confirm == VISUAL_CONFIRM_REPLAY; + debug_flags->bitfields.visual_confirm = dc->debug.visual_confirm == VISUAL_CONFIRM_REPLAY; debug_flags->bitfields.skip_crtc_disabled = dc->debug.replay_skip_crtc_disabled; init_replay_config(link, &pr_config); @@ -144,7 +143,6 @@ bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream, { struct dc_link *link; unsigned int static_coasting_vtotal; - unsigned int nom_coasting_vtotal; if (!stream || !stream->link || !vrr_params) return false; @@ -159,12 +157,11 @@ bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream, calculate_replay_link_off_frame_count(link, stream->timing.v_total, stream->timing.h_total); - nom_coasting_vtotal = stream->timing.v_total; static_coasting_vtotal = mod_freesync_calc_v_total_from_refresh(stream, vrr_params->min_refresh_in_uhz); set_replay_coasting_vtotal(link, PR_COASTING_TYPE_NOM, - nom_coasting_vtotal); + stream->timing.v_total); set_replay_coasting_vtotal(link, PR_COASTING_TYPE_STATIC, static_coasting_vtotal); return true; -- cgit v1.2.3 From 2985b49ed6f51cc3982beb9ab2171d2c8a33296e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 27 May 2026 17:35:47 -0600 Subject: drm/amd/display: Enable warnings as errors for KUnit tests [WHAT] Add CONFIG_WERROR=y to .kunitconfig to treat compiler warnings as errors during KUnit builds, ensuring warnings are caught early. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Ray Wu Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig index bd1bf8d959f9..1e93bd8b44ce 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig @@ -15,6 +15,9 @@ CONFIG_I2C=y CONFIG_POWER_SUPPLY=y CONFIG_CRC16=y +# Treat warnings as errors +CONFIG_WERROR=y + # GCOV Coverage - see tools/testing/kunit/configs/coverage_uml.config CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_INFO=y -- cgit v1.2.3 From 7c030f8df237740607c4d92d201516125a71f2f8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 13:12:16 -0600 Subject: drm/amd/display: Remove dead code in dm_dp_mst_get_modes [WHAT] Remove unreachable null check on aconnector after container_of, and redundant dc_sink checks where dc_sink is guaranteed non-NULL after earlier null-check with early return. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) 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 b3af7445b457..99b78dd50caf 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 @@ -363,9 +363,6 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); int ret = 0; - if (!aconnector) - return drm_add_edid_modes(connector, NULL); - if (!aconnector->drm_edid) { const struct drm_edid *drm_edid; @@ -456,7 +453,7 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) * plugged back with same display index, its hdcp properties * will be retrieved from hdcp_work within dm_dp_mst_get_modes */ - if (aconnector->dc_sink && connector->state) { + if (connector->state) { struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); @@ -472,20 +469,18 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) } } - if (aconnector->dc_sink) { - amdgpu_dm_update_freesync_caps( - connector, aconnector->drm_edid, true); + amdgpu_dm_update_freesync_caps( + connector, aconnector->drm_edid, true); #if defined(CONFIG_DRM_AMD_DC_FP) - if (!validate_dsc_caps_on_connector(aconnector)) - memset(&aconnector->dc_sink->dsc_caps, - 0, sizeof(aconnector->dc_sink->dsc_caps)); + if (!validate_dsc_caps_on_connector(aconnector)) + memset(&aconnector->dc_sink->dsc_caps, + 0, sizeof(aconnector->dc_sink->dsc_caps)); #endif - if (!retrieve_downstream_port_device(aconnector)) - memset(&aconnector->mst_downstream_port_present, - 0, sizeof(aconnector->mst_downstream_port_present)); - } + if (!retrieve_downstream_port_device(aconnector)) + memset(&aconnector->mst_downstream_port_present, + 0, sizeof(aconnector->mst_downstream_port_present)); } drm_edid_connector_update(&aconnector->base, aconnector->drm_edid); -- cgit v1.2.3 From 1c37d1b6c74116f2e6adcb675426eb36e60ae4d1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 14:48:34 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_mst_types [WHAT] Add KUnit test coverage for needs_dsc_aux_workaround() in amdgpu_dm_mst_types.c. Tests verify the function correctly identifies links requiring the DSC AUX workaround based on branch device ID, DPCD revision, and sink count. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 + .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 6 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 124 +++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c 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 99b78dd50caf..ff3afeb0ec07 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 @@ -39,6 +39,7 @@ #include "dc.h" #include "dm_helpers.h" +#include "amdgpu_dm_kunit_helpers.h" #include "ddc_service_types.h" #include "dpcd_defs.h" @@ -248,6 +249,7 @@ bool needs_dsc_aux_workaround(struct dc_link *link) return false; } +EXPORT_IF_KUNIT(needs_dsc_aux_workaround); #if defined(CONFIG_DRM_AMD_DC_FP) static bool is_synaptics_cascaded_panamera(struct dc_link *link, struct drm_dp_mst_port *port) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index 0e8eef5bdb74..208629ca3721 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -57,8 +57,14 @@ enum mst_msg_ready_type { DOWN_OR_UP_MSG_RDY_EVENT = 3 }; +struct amdgpu_device; struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct dc_state; +struct dc_stream_state; +struct dm_atomic_state; +struct drm_atomic_state; +struct drm_dp_mst_topology_mgr; uint32_t dm_mst_get_pbn_divider(struct dc_link *link); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index ce1e46acb7af..fe9f32c9bdde 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c new file mode 100644 index 000000000000..e21386819ea1 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_mst_types.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "dpcd_defs.h" +#include "amdgpu_dm_mst_types.h" + +/* Tests for needs_dsc_aux_workaround */ + +/** + * dm_mst_test_needs_dsc_aux_workaround_match - Test workaround triggers for matching device + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns true when the link has + * the specific branch device ID, DPCD rev 1.4, and sink count >= 2. + */ +static void dm_mst_test_needs_dsc_aux_workaround_match(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_rev12 - Test workaround triggers for DPCD rev 1.2 + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns true when the link has + * the specific branch device ID, DPCD rev 1.2, and sink count >= 2. + */ +static void dm_mst_test_needs_dsc_aux_workaround_rev12(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_12; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 3; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id - Test workaround skipped for wrong device + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the branch + * device ID does not match DP_BRANCH_DEVICE_ID_90CC24. + */ +static void dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = 0x123456; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_wrong_rev - Test workaround skipped for unsupported rev + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the DPCD + * revision is neither 1.2 nor 1.4. + */ +static void dm_mst_test_needs_dsc_aux_workaround_wrong_rev(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = 0x11; /* DPCD 1.1 */ + link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_low_sink_count - Test workaround skipped for single sink + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the sink + * count is less than 2, even if device ID and DPCD rev match. + */ +static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 1; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +static struct kunit_case dm_mst_types_test_cases[] = { + /* needs_dsc_aux_workaround tests */ + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_rev12), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_rev), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_low_sink_count), + {} +}; + +static struct kunit_suite dm_mst_types_test_suite = { + .name = "amdgpu_dm_mst_types", + .test_cases = dm_mst_types_test_cases, +}; + +kunit_test_suite(dm_mst_types_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_mst_types"); -- cgit v1.2.3 From e561531f2fca3ff4346b791dcbf7801aa1e172e8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 28 May 2026 11:48:11 -0600 Subject: drm/amd/display: Fix incorrect logic in CRC source handling [WHAT] Fix three issues amdgpu_dm_crc.c: - Use cur_crc_src instead of source when deciding whether to call drm_dp_stop_crc() in the disable path of set_crc_source(). When disabling CRC, source is always NONE so dm_is_crc_source_dprx(source) was always false, meaning drm_dp_stop_crc() was never called when stopping a DPRX CRC source. Use cur_crc_src to check what was previously active instead. - Replace fragile 'source < 0' comparisons in verify_crc_source() and set_crc_source() with AMDGPU_DM_PIPE_CRC_SOURCE_INVALID. and avoiding signed/unsigned enum comparison concerns. - Remove redundant NULL initializations for drm_dev and acrtc in handle_crc_irq(). Both variables are unconditionally assigned right after. Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 88f7cfea5624..daf50ec6bc80 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 @@ -496,7 +496,7 @@ amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc, const char *src_name, { enum amdgpu_dm_pipe_crc_source source = dm_parse_crc_source(src_name); - if (source < 0) { + if (source == AMDGPU_DM_PIPE_CRC_SOURCE_INVALID) { DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n", src_name, crtc->index); return -EINVAL; @@ -595,7 +595,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) bool enabled = false; int ret = 0; - if (source < 0) { + if (source == AMDGPU_DM_PIPE_CRC_SOURCE_INVALID) { DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n", src_name, crtc->index); return -EINVAL; @@ -724,7 +724,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) } } else if (enabled && !enable) { drm_crtc_vblank_put(crtc); - if (dm_is_crc_source_dprx(source)) { + if (dm_is_crc_source_dprx(cur_crc_src)) { if (drm_dp_stop_crc(aux)) { DRM_DEBUG_DRIVER("dp stop crc failed\n"); ret = -EINVAL; @@ -767,9 +767,9 @@ void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc) { struct dm_crtc_state *crtc_state; struct dc_stream_state *stream_state; - struct drm_device *drm_dev = NULL; + struct drm_device *drm_dev; enum amdgpu_dm_pipe_crc_source cur_crc_src; - struct amdgpu_crtc *acrtc = NULL; + struct amdgpu_crtc *acrtc; uint32_t crcs[3]; unsigned long flags; -- cgit v1.2.3 From 29757b93d796558bc8eababb1bd2f77b0c32e349 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 28 May 2026 14:01:08 -0600 Subject: drm/amd/display: Extract DPRX CRC transition helpers for KUnit testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract three pure predicate functions from amdgpu_dm_crtc_set_crc_source(): - dm_need_dp_aux - dm_crc_source_should_start_dprx - dm_crc_source_should_stop_dprx Refactor set_crc_source() to use these helpers, replacing the nested if/else if structure with flat, mutually-exclusive branches driven by the new predicates. Add KUnit test cases covering all relevant source combinations for each helper, including the regression case where DPRX→NONE must trigger drm_dp_stop_crc(). Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 92 +++++++++++++--- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h | 6 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 122 +++++++++++++++++++++ 3 files changed, 203 insertions(+), 17 deletions(-) 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 daf50ec6bc80..54d3c5c9e652 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 @@ -87,6 +87,65 @@ bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src) } EXPORT_IF_KUNIT(dm_need_crc_dither); +/** + * dm_need_dp_aux() - Does this source transition require the DP AUX handle? + * @source: Requested CRC source. + * @cur_crc_src: Current CRC source. + * + * Returns true when either the new source is DPRX-based (starting DPRX CRC), + * or the current source is DPRX-based and the new source is NONE (stopping it). + * + * Return: true if the DP AUX handle is needed, false otherwise. + */ +STATIC_IFN_KUNIT +bool dm_need_dp_aux(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src) +{ + return dm_is_crc_source_dprx(source) || + (source == AMDGPU_DM_PIPE_CRC_SOURCE_NONE && dm_is_crc_source_dprx(cur_crc_src)); +} +EXPORT_IF_KUNIT(dm_need_dp_aux); + +/** + * dm_crc_source_should_start_dprx() - Should drm_dp_start_crc() be called? + * @source: Requested CRC source. + * @cur_crc_src: Current CRC source. + * + * True when CRC is transitioning from off to a DPRX source + * (!enabled && enable && is_dprx(@source)). + * + * Return: true if drm_dp_start_crc() should be called, false otherwise. + */ +STATIC_IFN_KUNIT +bool dm_crc_source_should_start_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src) +{ + return !amdgpu_dm_is_valid_crc_source(cur_crc_src) && + amdgpu_dm_is_valid_crc_source(source) && + dm_is_crc_source_dprx(source); +} +EXPORT_IF_KUNIT(dm_crc_source_should_start_dprx); + +/** + * dm_crc_source_should_stop_dprx() - Should drm_dp_stop_crc() be called? + * @source: Requested CRC source. + * @cur_crc_src: Current CRC source. + * + * True when CRC is transitioning from a DPRX source to off + * (enabled && !enable && is_dprx(@cur_crc_src)). + * + * Return: true if drm_dp_stop_crc() should be called, false otherwise. + */ +STATIC_IFN_KUNIT +bool dm_crc_source_should_stop_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src) +{ + return amdgpu_dm_is_valid_crc_source(cur_crc_src) && + !amdgpu_dm_is_valid_crc_source(source) && + dm_is_crc_source_dprx(cur_crc_src); +} +EXPORT_IF_KUNIT(dm_crc_source_should_stop_dprx); + const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc, size_t *count) { @@ -650,9 +709,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) * CRTC DITHER | XXXX | Enable CRTC CRC, set dither * DPRX DITHER | XXXX | Enable DPRX CRC, need 'aux', set dither */ - if (dm_is_crc_source_dprx(source) || - (source == AMDGPU_DM_PIPE_CRC_SOURCE_NONE && - dm_is_crc_source_dprx(cur_crc_src))) { + if (dm_need_dp_aux(source, cur_crc_src)) { struct amdgpu_dm_connector *aconn = NULL; struct drm_connector *connector; struct drm_connector_list_iter conn_iter; @@ -714,23 +771,24 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) goto cleanup; } - if (!enabled && enable) { - if (dm_is_crc_source_dprx(source)) { - if (drm_dp_start_crc(aux, crtc)) { - DRM_DEBUG_DRIVER("dp start crc failed\n"); - ret = -EINVAL; - goto cleanup; - } + if (dm_crc_source_should_start_dprx(source, cur_crc_src)) { + /* !enabled && enable && is_dprx(source): CRC off → DPRX on */ + if (drm_dp_start_crc(aux, crtc)) { + DRM_DEBUG_DRIVER("dp start crc failed\n"); + ret = -EINVAL; + goto cleanup; } - } else if (enabled && !enable) { + } else if (dm_crc_source_should_stop_dprx(source, cur_crc_src)) { + /* enabled && !enable && is_dprx(cur_crc_src): DPRX on → CRC off */ drm_crtc_vblank_put(crtc); - if (dm_is_crc_source_dprx(cur_crc_src)) { - if (drm_dp_stop_crc(aux)) { - DRM_DEBUG_DRIVER("dp stop crc failed\n"); - ret = -EINVAL; - goto cleanup; - } + if (drm_dp_stop_crc(aux)) { + DRM_DEBUG_DRIVER("dp stop crc failed\n"); + ret = -EINVAL; + goto cleanup; } + } else if (enabled && !enable) { + /* Non-DPRX source (e.g. CRTC) turning off: release vblank ref */ + drm_crtc_vblank_put(crtc); } spin_lock_irq(&drm_dev->event_lock); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h index c9aa0c82038f..8bb8a6f6c148 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h @@ -156,6 +156,12 @@ enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source); bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src); bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src); bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src); +bool dm_need_dp_aux(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src); +bool dm_crc_source_should_start_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src); +bool dm_crc_source_should_stop_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src); #endif #endif /* AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ */ 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 bba8b1a8fa1c..a6fd3a6fd803 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,17 +95,139 @@ 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_need_dp_aux() - Test dm_need_dp_aux(). + * @test: KUnit test context. + * + * Verifies that dm_need_dp_aux() returns true when the transition starts or + * stops a DPRX CRC source (requiring the DP AUX handle), and false for + * non-DPRX transitions such as CRTC or NONE→NONE. + */ +static void dm_test_need_dp_aux(struct kunit *test) +{ + /* Starting a DPRX source always needs AUX, regardless of current source */ + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* Stopping a DPRX source (NONE requested, DPRX was active) needs AUX */ + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + /* CRTC transitions do not need AUX */ + KUNIT_EXPECT_FALSE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); +} + +/** + * dm_test_crc_source_should_start_dprx() - Test dm_crc_source_should_start_dprx(). + * @test: KUnit test context. + * + * Verifies that dm_crc_source_should_start_dprx() returns true only when CRC + * is transitioning from off (!enabled) to a DPRX source (enable && + * is_dprx(source)), and false for all other combinations including + * already-enabled or non-DPRX targets. + */ +static void dm_test_crc_source_should_start_dprx(struct kunit *test) +{ + /* CRC off → DPRX: should start */ + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* CRC already on (any source) → DPRX: should NOT start (already enabled) */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + + /* CRC off → CRTC: not a DPRX start */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* Disabling: should not start */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); +} + +/** + * dm_test_crc_source_should_stop_dprx() - Test dm_crc_source_should_stop_dprx(). + * @test: KUnit test context. + * + * Verifies that dm_crc_source_should_stop_dprx() returns true only when CRC + * is transitioning from a DPRX source (enabled && is_dprx(cur_crc_src)) to + * off (!enable), and false for non-DPRX disables, DPRX starts, and no-op + * transitions. + */ +static void dm_test_crc_source_should_stop_dprx(struct kunit *test) +{ + /* DPRX → off: should stop */ + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + /* CRTC → off: not a DPRX stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + + /* off → DPRX: not a stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* DPRX → DPRX: no transition, not a stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + + /* off → off: not a stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); +} + static struct kunit_case dm_crc_test_cases[] = { + /* dm_parse_crc_source() */ KUNIT_CASE(dm_test_parse_crc_source_none), KUNIT_CASE(dm_test_parse_crc_source_crtc), KUNIT_CASE(dm_test_parse_crc_source_dprx), KUNIT_CASE(dm_test_parse_crc_source_crtc_dither), KUNIT_CASE(dm_test_parse_crc_source_dprx_dither), KUNIT_CASE(dm_test_parse_crc_source_invalid), + /* dm_is_crc_source_crtc() */ KUNIT_CASE(dm_test_is_crc_source_crtc), + /* dm_is_crc_source_dprx() */ KUNIT_CASE(dm_test_is_crc_source_dprx), + /* dm_need_crc_dither() */ KUNIT_CASE(dm_test_need_crc_dither), + /* amdgpu_dm_is_valid_crc_source() */ KUNIT_CASE(dm_test_is_valid_crc_source), + /* dm_need_dp_aux() */ + KUNIT_CASE(dm_test_need_dp_aux), + /* dm_crc_source_should_start_dprx() */ + KUNIT_CASE(dm_test_crc_source_should_start_dprx), + /* dm_crc_source_should_stop_dprx() */ + KUNIT_CASE(dm_test_crc_source_should_stop_dprx), {} }; -- cgit v1.2.3 From 569cc68d6a7b82fa971153f08347712844db469c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 27 May 2026 17:01:13 -0600 Subject: drm/amd/display: Extract HDCP testable helpers for KUnit [WHAT] Extract hdcp_get_content_protection_from_status() and hdcp_get_link_display_adjustments() from event_property_update() and hdcp_update_display() so the pure decision logic can be KUnit-tested. Also update function comments to kernel-doc formats. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 115 +++++--- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 12 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 297 +++++++++++++++++++-- 3 files changed, 370 insertions(+), 54 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 4c164ae4a4f9..5dbeb1e017d4 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 @@ -182,6 +182,70 @@ void process_output(struct hdcp_workqueue *hdcp_work) } EXPORT_IF_KUNIT(process_output); +STATIC_IFN_KUNIT +bool hdcp_get_content_protection_from_status( + unsigned int hdcp_content_type, + enum mod_hdcp_encryption_status encryption_status, + unsigned int *content_protection) +{ + if (encryption_status == MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF) { + *content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + return true; + } + + if (hdcp_content_type == DRM_MODE_HDCP_CONTENT_TYPE0 && + encryption_status <= MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON) { + *content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + return true; + } + + if (hdcp_content_type == DRM_MODE_HDCP_CONTENT_TYPE1 && + encryption_status == MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON) { + *content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + return true; + } + + return false; +} +EXPORT_IF_KUNIT(hdcp_get_content_protection_from_status); + +STATIC_IFN_KUNIT +void hdcp_get_link_display_adjustments( + bool enable_encryption, + u8 content_type, + bool fused_io_supported, + bool hdcp_lc_force_fw_enable, + bool hdcp_lc_enable_sw_fallback, + struct mod_hdcp_link_adjustment *link_adjust, + struct mod_hdcp_display_adjustment *display_adjust) +{ + memset(link_adjust, 0, sizeof(*link_adjust)); + memset(display_adjust, 0, sizeof(*display_adjust)); + + if (!enable_encryption) { + display_adjust->disable = + MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; + return; + } + + display_adjust->disable = MOD_HDCP_DISPLAY_NOT_DISABLE; + link_adjust->auth_delay = 2; + link_adjust->retry_limit = MAX_NUM_OF_ATTEMPTS; + + if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0) { + link_adjust->hdcp2.force_type = MOD_HDCP_FORCE_TYPE_0; + } else if (content_type == DRM_MODE_HDCP_CONTENT_TYPE1) { + link_adjust->hdcp1.disable = 1; + link_adjust->hdcp2.force_type = MOD_HDCP_FORCE_TYPE_1; + } + + link_adjust->hdcp2.use_fw_locality_check = + fused_io_supported || hdcp_lc_force_fw_enable; + link_adjust->hdcp2.use_sw_locality_fallback = + hdcp_lc_enable_sw_fallback; +} +EXPORT_IF_KUNIT(hdcp_get_link_display_adjustments); + static void link_lock(struct hdcp_workqueue *work, bool lock) { int i = 0; @@ -212,8 +276,11 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, drm_connector_put(&hdcp_w->aconnector[conn_index]->base); hdcp_w->aconnector[conn_index] = aconnector; - memset(&link_adjust, 0, sizeof(link_adjust)); - memset(&display_adjust, 0, sizeof(display_adjust)); + hdcp_get_link_display_adjustments(enable_encryption, content_type, + dc->caps.fused_io_supported, + dc->debug.hdcp_lc_force_fw_enable, + dc->debug.hdcp_lc_enable_sw_fallback, + &link_adjust, &display_adjust); if (enable_encryption) { /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp @@ -224,25 +291,9 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, hdcp_work->srm_size, &hdcp_work->srm_version); - display_adjust.disable = MOD_HDCP_DISPLAY_NOT_DISABLE; - - link_adjust.auth_delay = 2; - link_adjust.retry_limit = MAX_NUM_OF_ATTEMPTS; - - if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0) { - link_adjust.hdcp2.force_type = MOD_HDCP_FORCE_TYPE_0; - } else if (content_type == DRM_MODE_HDCP_CONTENT_TYPE1) { - link_adjust.hdcp1.disable = 1; - link_adjust.hdcp2.force_type = MOD_HDCP_FORCE_TYPE_1; - } - link_adjust.hdcp2.use_fw_locality_check = - (dc->caps.fused_io_supported || dc->debug.hdcp_lc_force_fw_enable); - link_adjust.hdcp2.use_sw_locality_fallback = dc->debug.hdcp_lc_enable_sw_fallback; - schedule_delayed_work(&hdcp_w->property_validate_dwork, msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); } else { - display_adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; cancel_delayed_work(&hdcp_w->property_validate_dwork); } @@ -336,6 +387,7 @@ static void event_property_update(struct work_struct *work) property_update_work); struct amdgpu_dm_connector *aconnector = NULL; struct drm_device *dev; + unsigned int content_protection; long ret; unsigned int conn_index; struct drm_connector *connector; @@ -375,26 +427,15 @@ static void event_property_update(struct work_struct *work) MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; } } - if (hdcp_work->encryption_status[conn_index] != - MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF) { - if (conn_state->hdcp_content_type == - DRM_MODE_HDCP_CONTENT_TYPE0 && - hdcp_work->encryption_status[conn_index] <= - MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON) { + if (hdcp_get_content_protection_from_status(conn_state->hdcp_content_type, + hdcp_work->encryption_status[conn_index], + &content_protection)) { + if (content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_ENABLED\n"); - drm_hdcp_update_content_protection(connector, - DRM_MODE_CONTENT_PROTECTION_ENABLED); - } else if (conn_state->hdcp_content_type == - DRM_MODE_HDCP_CONTENT_TYPE1 && - hdcp_work->encryption_status[conn_index] == - MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON) { - drm_hdcp_update_content_protection(connector, - DRM_MODE_CONTENT_PROTECTION_ENABLED); - } - } else { - DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_DESIRED\n"); - drm_hdcp_update_content_protection(connector, - DRM_MODE_CONTENT_PROTECTION_DESIRED); + else + DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_DESIRED\n"); + + drm_hdcp_update_content_protection(connector, content_protection); } drm_modeset_unlock(&dev->mode_config.connection_mutex); } 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 90b18c450ca6..3ba5823aed9f 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 @@ -96,6 +96,18 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void process_output(struct hdcp_workqueue *hdcp_work); +bool hdcp_get_content_protection_from_status( + unsigned int hdcp_content_type, + enum mod_hdcp_encryption_status encryption_status, + unsigned int *content_protection); +void hdcp_get_link_display_adjustments( + bool enable_encryption, + u8 content_type, + bool fused_io_supported, + bool hdcp_lc_force_fw_enable, + bool hdcp_lc_enable_sw_fallback, + struct mod_hdcp_link_adjustment *link_adjust, + struct mod_hdcp_display_adjustment *display_adjust); #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 d03b606d27bc..619b4a80c82b 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 @@ -12,11 +12,241 @@ static void dummy_work_fn(struct work_struct *work) {} +/* Tests for hdcp_get_content_protection_from_status() */ + +/** + * dm_test_hdcp_get_cp_disabled_returns_desired - HDCP off maps to DESIRED + * @test: KUnit test context + * + * When encryption status is HDCP_OFF, content_protection should be set + * to DESIRED and the function should return true to indicate an update. + */ +static void dm_test_hdcp_get_cp_disabled_returns_desired(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF, + &content_protection); + + KUNIT_EXPECT_TRUE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_hdcp_get_cp_type0_returns_enabled - TYPE0 with TYPE0_ON maps to ENABLED + * @test: KUnit test context + * + * When content type is TYPE0 and encryption status is at or below + * HDCP2_TYPE0_ON, content_protection should be set to ENABLED. + */ +static void dm_test_hdcp_get_cp_type0_returns_enabled(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON, + &content_protection); + + KUNIT_EXPECT_TRUE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_ENABLED); +} + +/** + * dm_test_hdcp_get_cp_type1_returns_enabled - TYPE1 with TYPE1_ON maps to ENABLED + * @test: KUnit test context + * + * When content type is TYPE1 and encryption status is exactly + * HDCP2_TYPE1_ON, content_protection should be set to ENABLED. + */ +static void dm_test_hdcp_get_cp_type1_returns_enabled(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE1, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON, + &content_protection); + + KUNIT_EXPECT_TRUE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_ENABLED); +} + +/** + * dm_test_hdcp_get_cp_type1_rejects_type0_status - TYPE1 rejects TYPE0_ON + * @test: KUnit test context + * + * When content type is TYPE1 but encryption status is only TYPE0_ON, + * the function should return false and leave content_protection unchanged. + */ +static void dm_test_hdcp_get_cp_type1_rejects_type0_status(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE1, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON, + &content_protection); + + KUNIT_EXPECT_FALSE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_UNDESIRED); +} + +/** + * dm_test_hdcp_get_cp_type0_rejects_type1_status - TYPE0 rejects TYPE1_ON + * @test: KUnit test context + * + * When content type is TYPE0 but encryption status exceeds the TYPE0_ON + * boundary (TYPE1_ON), the function should return false. + */ +static void dm_test_hdcp_get_cp_type0_rejects_type1_status(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON, + &content_protection); + + KUNIT_EXPECT_FALSE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_UNDESIRED); +} + +/* Tests for hdcp_get_link_display_adjustments() */ + +/** + * dm_test_hdcp_get_adjustments_disable_authentication - disable path zeroes adjustments + * @test: KUnit test context + * + * When enable_encryption is false, display_adjust should disable + * authentication and all link_adjust fields should remain zeroed. + */ +static void dm_test_hdcp_get_adjustments_disable_authentication(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + unsigned int disable; + unsigned int hdcp1_disable; + unsigned int force_type; + + hdcp_get_link_display_adjustments(false, DRM_MODE_HDCP_CONTENT_TYPE0, + false, false, false, &link_adjust, &display_adjust); + disable = display_adjust.disable; + hdcp1_disable = link_adjust.hdcp1.disable; + force_type = link_adjust.hdcp2.force_type; + + KUNIT_EXPECT_EQ(test, disable, + MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION); + KUNIT_EXPECT_EQ(test, link_adjust.auth_delay, 0); + KUNIT_EXPECT_EQ(test, link_adjust.retry_limit, 0); + KUNIT_EXPECT_EQ(test, hdcp1_disable, 0); + KUNIT_EXPECT_EQ(test, force_type, 0); +} + +/** + * dm_test_hdcp_get_adjustments_type0_policy - TYPE0 enables HDCP1 and forces TYPE0 + * @test: KUnit test context + * + * When encryption is enabled with content TYPE0, hdcp1 should remain + * enabled, force_type should be TYPE_0, and sw_locality_fallback should + * be propagated from the input parameter. + */ +static void dm_test_hdcp_get_adjustments_type0_policy(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + unsigned int disable; + unsigned int hdcp1_disable; + unsigned int force_type; + + hdcp_get_link_display_adjustments(true, DRM_MODE_HDCP_CONTENT_TYPE0, + false, false, true, &link_adjust, &display_adjust); + disable = display_adjust.disable; + hdcp1_disable = link_adjust.hdcp1.disable; + force_type = link_adjust.hdcp2.force_type; + + KUNIT_EXPECT_EQ(test, disable, + MOD_HDCP_DISPLAY_NOT_DISABLE); + KUNIT_EXPECT_EQ(test, link_adjust.auth_delay, 2); + KUNIT_EXPECT_EQ(test, link_adjust.retry_limit, MAX_NUM_OF_ATTEMPTS); + KUNIT_EXPECT_EQ(test, hdcp1_disable, 0); + KUNIT_EXPECT_EQ(test, force_type, + MOD_HDCP_FORCE_TYPE_0); + KUNIT_EXPECT_FALSE(test, link_adjust.hdcp2.use_fw_locality_check); + KUNIT_EXPECT_TRUE(test, link_adjust.hdcp2.use_sw_locality_fallback); +} + +/** + * dm_test_hdcp_get_adjustments_type1_policy - TYPE1 disables HDCP1 and forces TYPE1 + * @test: KUnit test context + * + * When encryption is enabled with content TYPE1, hdcp1 should be + * disabled, force_type should be TYPE_1, and fw_locality_check should + * be enabled when hdcp_lc_force_fw_enable is set. + */ +static void dm_test_hdcp_get_adjustments_type1_policy(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + unsigned int disable; + unsigned int hdcp1_disable; + unsigned int force_type; + + hdcp_get_link_display_adjustments(true, DRM_MODE_HDCP_CONTENT_TYPE1, + false, true, false, &link_adjust, &display_adjust); + disable = display_adjust.disable; + hdcp1_disable = link_adjust.hdcp1.disable; + force_type = link_adjust.hdcp2.force_type; + + KUNIT_EXPECT_EQ(test, disable, + MOD_HDCP_DISPLAY_NOT_DISABLE); + KUNIT_EXPECT_EQ(test, link_adjust.auth_delay, 2); + KUNIT_EXPECT_EQ(test, link_adjust.retry_limit, MAX_NUM_OF_ATTEMPTS); + KUNIT_EXPECT_EQ(test, hdcp1_disable, 1); + KUNIT_EXPECT_EQ(test, force_type, + MOD_HDCP_FORCE_TYPE_1); + KUNIT_EXPECT_TRUE(test, link_adjust.hdcp2.use_fw_locality_check); + KUNIT_EXPECT_FALSE(test, link_adjust.hdcp2.use_sw_locality_fallback); +} + +/** + * dm_test_hdcp_get_adjustments_fused_io_enables_fw_check - fused_io enables FW locality check + * @test: KUnit test context + * + * When fused_io_supported is true, use_fw_locality_check should be + * enabled regardless of hdcp_lc_force_fw_enable. + */ +static void dm_test_hdcp_get_adjustments_fused_io_enables_fw_check(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + + hdcp_get_link_display_adjustments(true, DRM_MODE_HDCP_CONTENT_TYPE0, + true, false, false, &link_adjust, &display_adjust); + + KUNIT_EXPECT_TRUE(test, link_adjust.hdcp2.use_fw_locality_check); +} + /* Tests for process_output() */ -/* - * Helper: allocate and initialise a minimal hdcp_workqueue sufficient for - * process_output() testing. Only the three delayed works accessed by +/** + * alloc_test_workqueue - allocate a minimal hdcp_workqueue for testing + * @test: KUnit test context for managed allocation + * + * Allocates and initialises a minimal hdcp_workqueue sufficient for + * process_output() testing. Only the three delayed works accessed by * process_output() are initialised; everything else is zeroed. */ static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test) @@ -33,9 +263,12 @@ static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test) return work; } -/* +/** + * dm_test_process_output_property_validate_always_scheduled - validate_dwork always queued + * @test: KUnit test context + * * process_output() always schedules property_validate_dwork with delay=0, - * which queues the work item directly (bypassing the timer). Use + * which queues the work item directly (bypassing the timer). Uses * work_pending() rather than delayed_work_pending() to detect this. */ static void dm_test_process_output_property_validate_always_scheduled(struct kunit *test) @@ -52,8 +285,12 @@ static void dm_test_process_output_property_validate_always_scheduled(struct kun cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.callback_needed=true must schedule callback_dwork. +/** + * dm_test_process_output_callback_needed - callback_needed schedules callback_dwork + * @test: KUnit test context + * + * When output.callback_needed is true, process_output() must schedule + * callback_dwork with the specified delay. */ static void dm_test_process_output_callback_needed(struct kunit *test) { @@ -70,8 +307,12 @@ static void dm_test_process_output_callback_needed(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.callback_stop=true must cancel a previously scheduled callback_dwork. +/** + * dm_test_process_output_callback_stop - callback_stop cancels callback_dwork + * @test: KUnit test context + * + * When output.callback_stop is true, process_output() must cancel a + * previously scheduled callback_dwork. */ static void dm_test_process_output_callback_stop(struct kunit *test) { @@ -90,8 +331,12 @@ static void dm_test_process_output_callback_stop(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.watchdog_timer_needed=true must schedule watchdog_timer_dwork. +/** + * dm_test_process_output_watchdog_needed - watchdog_needed schedules watchdog_dwork + * @test: KUnit test context + * + * When output.watchdog_timer_needed is true, process_output() must + * schedule watchdog_timer_dwork with the specified delay. */ static void dm_test_process_output_watchdog_needed(struct kunit *test) { @@ -108,9 +353,12 @@ static void dm_test_process_output_watchdog_needed(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.watchdog_timer_stop=true must cancel a previously scheduled - * watchdog_timer_dwork. +/** + * dm_test_process_output_watchdog_stop - watchdog_stop cancels watchdog_dwork + * @test: KUnit test context + * + * When output.watchdog_timer_stop is true, process_output() must cancel + * a previously scheduled watchdog_timer_dwork. */ static void dm_test_process_output_watchdog_stop(struct kunit *test) { @@ -129,9 +377,12 @@ static void dm_test_process_output_watchdog_stop(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * Both callback_needed and watchdog_timer_needed set: both dworks are - * scheduled independently. +/** + * dm_test_process_output_callback_and_watchdog_needed - both dworks scheduled independently + * @test: KUnit test context + * + * When both callback_needed and watchdog_timer_needed are set, + * process_output() must schedule both dworks independently. */ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *test) { @@ -154,6 +405,18 @@ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *te /* End of tests for process_output() */ static struct kunit_case dm_hdcp_test_cases[] = { + /* hdcp_get_content_protection_from_status() */ + KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), + KUNIT_CASE(dm_test_hdcp_get_cp_type0_returns_enabled), + KUNIT_CASE(dm_test_hdcp_get_cp_type1_returns_enabled), + KUNIT_CASE(dm_test_hdcp_get_cp_type1_rejects_type0_status), + KUNIT_CASE(dm_test_hdcp_get_cp_type0_rejects_type1_status), + /* hdcp_get_link_display_adjustments() */ + KUNIT_CASE(dm_test_hdcp_get_adjustments_disable_authentication), + KUNIT_CASE(dm_test_hdcp_get_adjustments_type0_policy), + KUNIT_CASE(dm_test_hdcp_get_adjustments_type1_policy), + KUNIT_CASE(dm_test_hdcp_get_adjustments_fused_io_enables_fw_check), + /* process_output() */ KUNIT_CASE(dm_test_process_output_property_validate_always_scheduled), KUNIT_CASE(dm_test_process_output_callback_needed), KUNIT_CASE(dm_test_process_output_callback_stop), -- cgit v1.2.3 From 0c300e6a76916e944b6b18a64c73f7895a0fee87 Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Thu, 28 May 2026 12:28:51 -0400 Subject: drm/amd/display: Restore periodic detection for DCN35 [Why&How] Periodic detection callbacks from DCN35 was removed for higher IPS residency causing some displays to fail to recover after DPMS sleep. The monitors bounces HPD ~1.2s after link training, and without periodic detection the system enters IPS with no mechanism to wake and rediscover the display. Restore the periodic detection calls in dcn35_clk_mgr for now. It should be replaced with a proper IPS-aware solution long term using DMUB. Also remove it from dcn31 and dcn314_clk_mgr.c since they do not have IPS, thus should not affect them. Fixes: 3f6c060846be ("drm/amd/display: Remove periodic detection callbacks from dcn35+") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5318 Reviewed-by: Nicholas Kazlauskas Signed-off-by: Ivan Lipski Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/clk_mgr/dcn31/dcn31_clk_mgr.c | 2 -- drivers/gpu/drm/amd/display/dc/clk_mgr/dcn314/dcn314_clk_mgr.c | 2 -- drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c | 2 ++ 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn31/dcn31_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn31/dcn31_clk_mgr.c index 00c4be7c3aa4..ff47af3854b6 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn31/dcn31_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn31/dcn31_clk_mgr.c @@ -158,7 +158,6 @@ void dcn31_update_clocks(struct clk_mgr *clk_mgr_base, if (new_clocks->zstate_support != DCN_ZSTATE_SUPPORT_DISALLOW && new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) { dcn31_smu_set_zstate_support(clk_mgr, new_clocks->zstate_support); - dm_helpers_enable_periodic_detection(clk_mgr_base->ctx, true); clk_mgr_base->clks.zstate_support = new_clocks->zstate_support; } @@ -184,7 +183,6 @@ void dcn31_update_clocks(struct clk_mgr *clk_mgr_base, if (new_clocks->zstate_support == DCN_ZSTATE_SUPPORT_DISALLOW && new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) { dcn31_smu_set_zstate_support(clk_mgr, DCN_ZSTATE_SUPPORT_DISALLOW); - dm_helpers_enable_periodic_detection(clk_mgr_base->ctx, false); clk_mgr_base->clks.zstate_support = new_clocks->zstate_support; } diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn314/dcn314_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn314/dcn314_clk_mgr.c index dd6f11ecb9c9..24f6304011ae 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn314/dcn314_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn314/dcn314_clk_mgr.c @@ -230,7 +230,6 @@ void dcn314_update_clocks(struct clk_mgr *clk_mgr_base, if (new_clocks->zstate_support != DCN_ZSTATE_SUPPORT_DISALLOW && new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) { dcn314_smu_set_zstate_support(clk_mgr, new_clocks->zstate_support); - dm_helpers_enable_periodic_detection(clk_mgr_base->ctx, true); clk_mgr_base->clks.zstate_support = new_clocks->zstate_support; } @@ -255,7 +254,6 @@ void dcn314_update_clocks(struct clk_mgr *clk_mgr_base, if (new_clocks->zstate_support == DCN_ZSTATE_SUPPORT_DISALLOW && new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) { dcn314_smu_set_zstate_support(clk_mgr, DCN_ZSTATE_SUPPORT_DISALLOW); - dm_helpers_enable_periodic_detection(clk_mgr_base->ctx, false); clk_mgr_base->clks.zstate_support = new_clocks->zstate_support; } diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c index 103013e2a0de..a69824e1eb26 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c @@ -419,6 +419,7 @@ void dcn35_update_clocks(struct clk_mgr *clk_mgr_base, if (new_clocks->zstate_support != DCN_ZSTATE_SUPPORT_DISALLOW && new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) { dcn35_smu_set_zstate_support(clk_mgr, new_clocks->zstate_support); + dm_helpers_enable_periodic_detection(clk_mgr_base->ctx, true); clk_mgr_base->clks.zstate_support = new_clocks->zstate_support; } @@ -438,6 +439,7 @@ void dcn35_update_clocks(struct clk_mgr *clk_mgr_base, if (new_clocks->zstate_support == DCN_ZSTATE_SUPPORT_DISALLOW && new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) { dcn35_smu_set_zstate_support(clk_mgr, DCN_ZSTATE_SUPPORT_DISALLOW); + dm_helpers_enable_periodic_detection(clk_mgr_base->ctx, false); clk_mgr_base->clks.zstate_support = new_clocks->zstate_support; } -- cgit v1.2.3 From dfbb757c4ce6048b67a95c46f73db3ced6fa8541 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 15:27:00 -0600 Subject: drm/amd/display: Remove duplicate pp_rn_set_wm_ranges [WHAT] Remove pp_rn_set_wm_ranges and reuse the identical pp_nv_set_wm_ranges for the DCN_VERSION_2_1 case instead. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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 2cdb8fea504a..2fda6fbed88f 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 @@ -686,17 +686,6 @@ static enum pp_smu_status pp_rn_get_dpm_clock_table( return PP_SMU_RESULT_OK; } -static enum pp_smu_status pp_rn_set_wm_ranges(struct pp_smu *pp, - struct pp_smu_wm_range_sets *ranges) -{ - const struct dc_context *ctx = pp->dm; - struct amdgpu_device *adev = ctx->driver_context; - - amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, ranges); - - return PP_SMU_RESULT_OK; -} - void dm_pp_get_funcs( struct dc_context *ctx, struct pp_smu_funcs *funcs) @@ -743,7 +732,7 @@ void dm_pp_get_funcs( case DCN_VERSION_2_1: funcs->ctx.ver = PP_SMU_VER_RN; funcs->rn_funcs.pp_smu.dm = ctx; - funcs->rn_funcs.set_wm_ranges = pp_rn_set_wm_ranges; + funcs->rn_funcs.set_wm_ranges = pp_nv_set_wm_ranges; funcs->rn_funcs.get_dpm_clock_table = pp_rn_get_dpm_clock_table; break; default: -- cgit v1.2.3 From bb2baf1dc9e68a68dc76740dcf857106380abd24 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 29 May 2026 10:06:22 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_pp_smu [WHAT] Add KUnit tests for two functions in amdgpu_dm_pp_smu.c: get_default_clock_levels and dc_to_pp_clock_type. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 8 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 16 ++ .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 241 +++++++++++++++++++++ 4 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c 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 2fda6fbed88f..ca7141dbdf6a 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,6 +33,8 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "dm_pp_smu.h" +#include "amdgpu_dm_kunit_helpers.h" +#include "amdgpu_dm_pp_smu.h" bool dm_pp_apply_display_requirements( const struct dc_context *ctx, @@ -109,7 +111,7 @@ bool dm_pp_apply_display_requirements( return true; } -static void get_default_clock_levels( +STATIC_IFN_KUNIT void get_default_clock_levels( enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks) { @@ -140,8 +142,9 @@ static void get_default_clock_levels( break; } } +EXPORT_IF_KUNIT(get_default_clock_levels); -static enum amd_pp_clock_type dc_to_pp_clock_type( +STATIC_IFN_KUNIT enum amd_pp_clock_type dc_to_pp_clock_type( enum dm_pp_clock_type dm_pp_clk_type) { enum amd_pp_clock_type amd_pp_clk_type = 0; @@ -182,6 +185,7 @@ static enum amd_pp_clock_type dc_to_pp_clock_type( return amd_pp_clk_type; } +EXPORT_IF_KUNIT(dc_to_pp_clock_type); static void pp_to_dc_clock_levels( const struct amd_pp_clocks *pp_clks, 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 new file mode 100644 index 000000000000..827b60d5affe --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef __AMDGPU_DM_PP_SMU_H__ +#define __AMDGPU_DM_PP_SMU_H__ + +#include "dm_pp_interface.h" + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void get_default_clock_levels(enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks); +enum amd_pp_clock_type dc_to_pp_clock_type(enum dm_pp_clock_type dm_pp_clk_type); +#endif + +#endif /* __AMDGPU_DM_PP_SMU_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index fe9f32c9bdde..4d2eb301c2af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -8,6 +8,7 @@ ccflags-y += -I$(src)/../../include ccflags-y += -I$(src)/../../modules/inc ccflags-y += -I$(src)/../../dc ccflags-y += -I$(src)/../../../amdgpu +ccflags-y += -I$(src)/../../../include obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o @@ -18,3 +19,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_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 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 new file mode 100644 index 000000000000..556473f55ebe --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_pp_smu.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_pp_smu.h" + +/* ---- Tests for get_default_clock_levels ---- */ + +/** + * dm_test_default_clock_levels_display - Test display clock default levels + * @test: KUnit test context + * + * Verify that get_default_clock_levels populates 6 display clock levels + * with the expected frequencies in kHz. + */ +static void dm_test_default_clock_levels_display(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + uint32_t expected[] = { 300000, 400000, 496560, 626090, 685720, 757900 }; + int i; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_DISPLAY_CLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 6U); + for (i = 0; i < 6; i++) + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[i], expected[i]); +} + +/** + * dm_test_default_clock_levels_engine - Test engine clock default levels + * @test: KUnit test context + * + * Verify that get_default_clock_levels populates 6 engine clock levels + * with the expected frequencies in kHz. + */ +static void dm_test_default_clock_levels_engine(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + uint32_t expected[] = { 300000, 360000, 423530, 514290, 626090, 720000 }; + int i; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_ENGINE_CLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 6U); + for (i = 0; i < 6; i++) + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[i], expected[i]); +} + +/** + * dm_test_default_clock_levels_memory - Test memory clock default levels + * @test: KUnit test context + * + * Verify that get_default_clock_levels populates 2 memory clock levels + * with the expected frequencies in kHz. + */ +static void dm_test_default_clock_levels_memory(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_MEMORY_CLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[0], 333000U); + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[1], 800000U); +} + +/** + * dm_test_default_clock_levels_unknown - Test unknown clock type default + * @test: KUnit test context + * + * Verify that get_default_clock_levels sets num_levels to 0 for an + * unrecognized clock type. + */ +static void dm_test_default_clock_levels_unknown(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_FCLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 0U); +} + +/* ---- Tests for dc_to_pp_clock_type ---- */ + +/** + * dm_test_dc_to_pp_clock_type_display - Test display clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DISPLAY_CLK maps to amd_pp_disp_clock. + */ +static void dm_test_dc_to_pp_clock_type_display(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DISPLAY_CLK), + (int)amd_pp_disp_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_engine - Test engine clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_ENGINE_CLK maps to amd_pp_sys_clock. + */ +static void dm_test_dc_to_pp_clock_type_engine(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_ENGINE_CLK), + (int)amd_pp_sys_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_memory - Test memory clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_MEMORY_CLK maps to amd_pp_mem_clock. + */ +static void dm_test_dc_to_pp_clock_type_memory(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_MEMORY_CLK), + (int)amd_pp_mem_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_dcefclk - Test DCEF clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DCEFCLK maps to amd_pp_dcef_clock. + */ +static void dm_test_dc_to_pp_clock_type_dcefclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DCEFCLK), + (int)amd_pp_dcef_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_dcfclk - Test DCF clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DCFCLK maps to amd_pp_dcf_clock. + */ +static void dm_test_dc_to_pp_clock_type_dcfclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DCFCLK), + (int)amd_pp_dcf_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_pixelclk - Test pixel clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_PIXELCLK maps to amd_pp_pixel_clock. + */ +static void dm_test_dc_to_pp_clock_type_pixelclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_PIXELCLK), + (int)amd_pp_pixel_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_fclk - Test FCLK type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_FCLK maps to amd_pp_f_clock. + */ +static void dm_test_dc_to_pp_clock_type_fclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_FCLK), + (int)amd_pp_f_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_phyclk - Test display PHY clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DISPLAYPHYCLK maps to amd_pp_phy_clock. + */ +static void dm_test_dc_to_pp_clock_type_phyclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DISPLAYPHYCLK), + (int)amd_pp_phy_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_dppclk - Test DPP clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DPPCLK maps to amd_pp_dpp_clock. + */ +static void dm_test_dc_to_pp_clock_type_dppclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DPPCLK), + (int)amd_pp_dpp_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_invalid - Test invalid clock type mapping + * @test: KUnit test context + * + * Verify that an invalid clock type value maps to 0. + */ +static void dm_test_dc_to_pp_clock_type_invalid(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(0), 0); +} + +static struct kunit_case dm_pp_smu_test_cases[] = { + /* get_default_clock_levels */ + KUNIT_CASE(dm_test_default_clock_levels_display), + KUNIT_CASE(dm_test_default_clock_levels_engine), + KUNIT_CASE(dm_test_default_clock_levels_memory), + KUNIT_CASE(dm_test_default_clock_levels_unknown), + /* dc_to_pp_clock_type */ + KUNIT_CASE(dm_test_dc_to_pp_clock_type_display), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_engine), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_memory), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_dcefclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_dcfclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_pixelclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_fclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_phyclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_dppclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_invalid), + {} +}; + +static struct kunit_suite dm_pp_smu_test_suite = { + .name = "amdgpu_dm_pp_smu", + .test_cases = dm_pp_smu_test_cases, +}; + +kunit_test_suite(dm_pp_smu_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_pp_smu"); -- cgit v1.2.3 From a4e4d945cba8a2fdbe2d964d37eba1f5b5c51365 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Fri, 5 Jun 2026 16:28:47 +0800 Subject: drm/amdgpu/gfx: defer per-queue helper_end until after MES resume amdgpu_gfx_reset_mes_compute() runs amdgpu_mes_suspend(adev, 0) to quiesce all gangs, resets the offending queue(s), then resumes. The existing amdgpu_gfx_mes_reset_queue() called amdgpu_ring_reset_helper_end() right after unmap/restore/map of the reset queue, which re-emits backed-up commands and rings the doorbell. That doorbell hits a still-suspended CP: on the subsequent resume the queue partially wedges -- the first new IB after the reset may execute but later submissions stall, which surfaces as repeated timeouts on the same ring under concurrent workloads. Split out amdgpu_gfx_mes_reset_queue_start() (backup + MES reset + unmap/restore/map only) and defer helper_end. amdgpu_gfx_reset_mes_compute() collects the (ring, fence) pair for every queue it resets and runs helper_end on each after amdgpu_mes_resume(), so the re-emit doorbells land on a running CP. amdgpu_gfx_reset_mes_kcq() now reports the matched ring/fence back to the caller for the same reason. Reviewed-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 68 +++++++++++++++++++++++++++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 5 +++ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index ff5a55f5f3c9..59f35a310253 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -1989,10 +1989,10 @@ static ssize_t amdgpu_gfx_get_compute_reset_mask(struct device *dev, return amdgpu_show_reset_mask(buf, adev->gfx.compute_supported_reset); } -int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, - unsigned int vmid, - struct amdgpu_fence *timedout_fence, - bool use_mmio) +static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, + unsigned int vmid, + struct amdgpu_fence *timedout_fence, + bool use_mmio) { struct amdgpu_device *adev = ring->adev; bool reinit_queue; @@ -2026,7 +2026,20 @@ int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, return r; } } + return 0; +} +int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, + unsigned int vmid, + struct amdgpu_fence *timedout_fence, + bool use_mmio) +{ + int r; + + r = amdgpu_gfx_mes_reset_queue_start(ring, vmid, timedout_fence, + use_mmio); + if (r) + return r; return amdgpu_ring_reset_helper_end(ring, timedout_fence); } @@ -2216,24 +2229,37 @@ static void amdgpu_gfx_reset_stop_compute_scheds(struct amdgpu_device *adev, } } +/* + * Match the MES-reported hung doorbell against a compute ring and run + * the reset. On hit, the matched ring and its guilty fence are returned + * via *out_ring / *out_fence so the caller can defer reset end until + * after MES has resumed all gangs. + */ static int amdgpu_gfx_reset_mes_kcq(struct amdgpu_device *adev, struct amdgpu_ring *guilty_ring, - unsigned int db) + unsigned int db, + struct amdgpu_ring **out_ring, + struct amdgpu_fence **out_fence) { bool use_mmio = adev->gfx.mec.use_mmio_for_reset; struct amdgpu_fence *fence; struct amdgpu_ring *ring; int i, r; + *out_ring = NULL; + *out_fence = NULL; for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; if (ring == guilty_ring) continue; if (ring->doorbell_index == db) { fence = amdgpu_ring_find_guilty_fence(ring); - r = amdgpu_gfx_mes_reset_queue(ring, 0, fence, use_mmio); + r = amdgpu_gfx_mes_reset_queue_start(ring, 0, fence, + use_mmio); if (r) return r; + *out_ring = ring; + *out_fence = fence; break; } } @@ -2254,6 +2280,8 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, unsigned int num_hung = 0; bool use_mmio = adev->gfx.mec.use_mmio_for_reset; struct mes_remove_queue_input *queue_input = (struct mes_remove_queue_input *)faulty_queue_input; + struct amdgpu_gfx_deferred_entry deferred_end[AMDGPU_MAX_COMPUTE_RINGS + 1]; + int n_deferred = 0; guard(mutex)(&adev->gfx.mec.reset_mutex); /* stop the drm schedulers for all compute queues */ @@ -2278,9 +2306,13 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, fence_reset: /* reset the queue this came from if specified */ if (ring) { - r = amdgpu_gfx_mes_reset_queue(ring, 0, guilty_fence, use_mmio); + r = amdgpu_gfx_mes_reset_queue_start(ring, 0, guilty_fence, + use_mmio); if (r) goto out; + deferred_end[n_deferred].ring = ring; + deferred_end[n_deferred].fence = guilty_fence; + n_deferred++; } if (uq) { r = mes_userq_reset(uq); @@ -2288,15 +2320,24 @@ fence_reset: goto out; } for (i = 0; i < num_hung; i++) { + struct amdgpu_ring *hr = NULL; + struct amdgpu_fence *hf = NULL; + pipe = hqd_info[i].pipe_index; queue = hqd_info[i].queue_index; queue_type = hqd_info[i].queue_type; /* reset any KCQs */ r = amdgpu_gfx_reset_mes_kcq(adev, ring, - adev->gfx.mec.mes_hung_db_array[i]); + adev->gfx.mec.mes_hung_db_array[i], + &hr, &hf); if (r) goto out; + if (hr) { + deferred_end[n_deferred].ring = hr; + deferred_end[n_deferred].fence = hf; + n_deferred++; + } /* reset any KFD queues */ r = amdgpu_amdkfd_reset_mes_queue(adev, 0, queue_type, pipe, queue, adev->gfx.mec.mes_hung_db_array[i]); @@ -2325,6 +2366,17 @@ fence_reset: out: /* resume all will enable the non-hung queues */ amdgpu_mes_resume(adev, 0); + + /* Now CP is running again — replay backed-up commands and ring + * doorbells on each reset queue. + */ + for (i = 0; i < n_deferred; i++) { + int er = amdgpu_ring_reset_helper_end(deferred_end[i].ring, + deferred_end[i].fence); + if (er && !r) + r = er; + } + if (!r) amdgpu_gfx_reset_start_compute_scheds(adev, ring); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index 4003360c7d9a..381fc17274b9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -549,6 +549,11 @@ struct amdgpu_gfx { bool disable_uq; }; +struct amdgpu_gfx_deferred_entry { + struct amdgpu_ring *ring; + struct amdgpu_fence *fence; +}; + struct amdgpu_gfx_ras_reg_entry { struct amdgpu_ras_err_status_reg_entry reg_entry; enum amdgpu_gfx_ras_mem_id_type mem_id_type; -- cgit v1.2.3 From b7e70a466b3d022c7b8f830a2235961a957b3663 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Sun, 31 May 2026 12:57:40 +0200 Subject: drm/amd/display: Add detect reason to handle_hpd_irq_helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it possible to reuse the function for other purposes in the next few commits, such as HPD RX. Signed-off-by: Timur Kristóf Signed-off-by: Aurabindo Pillai Reviewed-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 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 40f32c8024a0..22cbfc159cfa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -242,7 +242,8 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector); +static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason); static void handle_hpd_rx_irq(void *param); static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, @@ -892,7 +893,7 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, if (notify->type == DMUB_NOTIFICATION_HPD) { if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); - handle_hpd_irq_helper(hpd_aconnector); + handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { handle_hpd_rx_irq(hpd_aconnector); } @@ -4357,7 +4358,8 @@ static void hdmi_hpd_debounce_work(struct work_struct *work) } } -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) +static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason) { struct drm_connector *connector = &aconnector->base; struct drm_device *dev = connector->dev; @@ -4404,7 +4406,8 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev); - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) drm_kms_helper_connector_hotplug_event(connector); } else if (debounce_required) { /* @@ -4436,7 +4439,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) scoped_guard(mutex, &adev->dm.dc_lock) { dc_exit_ips_for_hw_access(dc); - ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); + ret = dc_link_detect(aconnector->dc_link, reason); } if (ret) { /* w/a delay for certain panels */ @@ -4447,7 +4450,8 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev); - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) drm_kms_helper_connector_hotplug_event(connector); } } @@ -4457,7 +4461,7 @@ static void handle_hpd_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; - handle_hpd_irq_helper(aconnector); + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); } -- cgit v1.2.3 From 60271ec06e04ba5d69d68714f3abdf637d86c257 Mon Sep 17 00:00:00 2001 From: Andrew Martin Date: Thu, 28 May 2026 10:32:52 -0400 Subject: drm/amdkfd: Fix SMI event PID reporting for containers SMI events were reporting incorrect PIDs in containerized environments, causing test failures where container processes expected to see their namespace-local PIDs but instead received global host PIDs. The issue had two root causes: 1. Event functions were called from kernel context (page fault handlers, migration workers) where 'current' refers to the kernel worker thread, not the userspace GPU process that triggered the event. 2. PID conversion used task_tgid_vnr() which returns the PID in the caller's namespace (init namespace for kernel threads), not the task's own namespace. This patch updates the SMI event interface: - Change 8 event function signatures to accept task_struct pointer instead of pid_t, allowing proper namespace-aware PID conversion - Convert PIDs using task_tgid_nr_ns(task, task_active_pid_ns(task)) which returns the PID as the process sees it via getpid() - Update 10 call sites to pass p->lead_thread (the GPU process) instead of p->lead_thread->pid or current (kernel worker) This ensures SMI events report container-local PIDs, which is critical for containerized GPU workloads to correctly correlate events with their processes. Tested-by: Andrew Martin Assisted-by: Claude:Sonnet 4-5 Signed-off-by: Andrew Martin Reviewed-by: Harish Kasiviswanathan Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 8 +-- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 6 +- drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c | 99 +++++++++++++++++------------ drivers/gpu/drm/amd/amdkfd/kfd_smi_events.h | 14 ++-- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 6 +- 5 files changed, 77 insertions(+), 56 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index 28dc6886c1ff..226e76ae0be7 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -424,7 +424,7 @@ svm_migrate_vma_to_vram(struct kfd_node *node, struct svm_range *prange, migrate.dst = migrate.src + npages; scratch = (dma_addr_t *)(migrate.dst + npages); - kfd_smi_event_migration_start(node, p->lead_thread->pid, + kfd_smi_event_migration_start(node, p->lead_thread, start >> PAGE_SHIFT, end >> PAGE_SHIFT, 0, node->id, prange->prefetch_loc, prange->preferred_loc, trigger); @@ -462,7 +462,7 @@ svm_migrate_vma_to_vram(struct kfd_node *node, struct svm_range *prange, out_free: kvfree(buf); - kfd_smi_event_migration_end(node, p->lead_thread->pid, + kfd_smi_event_migration_end(node, p->lead_thread, start >> PAGE_SHIFT, end >> PAGE_SHIFT, 0, node->id, trigger, r); out: @@ -727,7 +727,7 @@ svm_migrate_vma_to_ram(struct kfd_node *node, struct svm_range *prange, migrate.fault_page = fault_page; scratch = (dma_addr_t *)(migrate.dst + npages); - kfd_smi_event_migration_start(node, p->lead_thread->pid, + kfd_smi_event_migration_start(node, p->lead_thread, start >> PAGE_SHIFT, end >> PAGE_SHIFT, node->id, 0, prange->prefetch_loc, prange->preferred_loc, trigger); @@ -766,7 +766,7 @@ svm_migrate_vma_to_ram(struct kfd_node *node, struct svm_range *prange, out_free: kvfree(buf); - kfd_smi_event_migration_end(node, p->lead_thread->pid, + kfd_smi_event_migration_end(node, p->lead_thread, start >> PAGE_SHIFT, end >> PAGE_SHIFT, node->id, 0, trigger, r); out: diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index 9838954d77da..a7a12fdd2458 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -2002,7 +2002,7 @@ int kfd_process_evict_queues(struct kfd_process *p, uint32_t trigger) struct kfd_process_device *pdd = p->pdds[i]; struct device *dev = pdd->dev->adev->dev; - kfd_smi_event_queue_eviction(pdd->dev, p->lead_thread->pid, + kfd_smi_event_queue_eviction(pdd->dev, p->lead_thread, trigger); r = pdd->dev->dqm->ops.evict_process_queues(pdd->dev->dqm, @@ -2032,7 +2032,7 @@ fail: if (n_evicted == 0) break; - kfd_smi_event_queue_restore(pdd->dev, p->lead_thread->pid); + kfd_smi_event_queue_restore(pdd->dev, p->lead_thread); if (pdd->dev->dqm->ops.restore_process_queues(pdd->dev->dqm, &pdd->qpd)) @@ -2055,7 +2055,7 @@ int kfd_process_restore_queues(struct kfd_process *p) struct kfd_process_device *pdd = p->pdds[i]; struct device *dev = pdd->dev->adev->dev; - kfd_smi_event_queue_restore(pdd->dev, p->lead_thread->pid); + kfd_smi_event_queue_restore(pdd->dev, p->lead_thread); r = pdd->dev->dqm->ops.restore_process_queues(pdd->dev->dqm, &pdd->qpd); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c index dfbde5a571f6..e659cd50eb0b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c @@ -195,17 +195,35 @@ static void add_event_to_kfifo(pid_t pid, struct kfd_node *dev, rcu_read_unlock(); } +/** + * kfd_smi_task_to_pid - Convert task to namespace-aware PID + * @task: task_struct pointer (typically p->lead_thread) + * + * Returns the PID as it appears in the task's own PID namespace. + * For containerized processes, this returns the container-local PID + * (what getpid() returns), not the global host PID. + * + * Returns 0 if task is NULL. + */ +static inline pid_t kfd_smi_task_to_pid(struct task_struct *task) +{ + return task ? task_tgid_nr_ns(task, task_active_pid_ns(task)) : 0; +} + __printf(4, 5) -static void kfd_smi_event_add(pid_t pid, struct kfd_node *dev, +static void kfd_smi_event_add(struct task_struct *task, struct kfd_node *dev, unsigned int event, char *fmt, ...) { char fifo_in[KFD_SMI_EVENT_MSG_SIZE]; int len; va_list args; + pid_t pid; if (list_empty(&dev->smi_clients)) return; + pid = kfd_smi_task_to_pid(task); + len = snprintf(fifo_in, sizeof(fifo_in), "%x ", event); va_start(args, fmt); @@ -234,14 +252,15 @@ void kfd_smi_event_update_gpu_reset(struct kfd_node *dev, bool post_reset, amdgpu_reset_get_desc(reset_context, reset_cause, sizeof(reset_cause)); - kfd_smi_event_add(0, dev, event, KFD_EVENT_FMT_UPDATE_GPU_RESET( + kfd_smi_event_add(NULL, dev, event, KFD_EVENT_FMT_UPDATE_GPU_RESET( dev->reset_seq_num, reset_cause)); } void kfd_smi_event_update_thermal_throttling(struct kfd_node *dev, uint64_t throttle_bitmask) { - kfd_smi_event_add(0, dev, KFD_SMI_EVENT_THERMAL_THROTTLE, KFD_EVENT_FMT_THERMAL_THROTTLING( + kfd_smi_event_add(NULL, dev, KFD_SMI_EVENT_THERMAL_THROTTLE, + KFD_EVENT_FMT_THERMAL_THROTTLING( throttle_bitmask, amdgpu_dpm_get_thermal_throttling_counter(dev->adev))); } @@ -254,67 +273,67 @@ void kfd_smi_event_update_vmfault(struct kfd_node *dev, uint16_t pasid) if (task_info) { /* Report VM faults from user applications, not retry from kernel */ if (task_info->task.pid) - kfd_smi_event_add(task_info->tgid, dev, - KFD_SMI_EVENT_VMFAULT, - KFD_EVENT_FMT_VMFAULT(task_info->task.pid, - task_info->task.comm)); + kfd_smi_event_add(NULL, dev, KFD_SMI_EVENT_VMFAULT, KFD_EVENT_FMT_VMFAULT( + task_info->task.pid, task_info->task.comm)); amdgpu_vm_put_task_info(task_info); } } -void kfd_smi_event_page_fault_start(struct kfd_node *node, pid_t pid, +void kfd_smi_event_page_fault_start(struct kfd_node *node, struct task_struct *task, unsigned long address, bool write_fault, ktime_t ts) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_PAGE_FAULT_START, - KFD_EVENT_FMT_PAGEFAULT_START(ktime_to_ns(ts), pid, - address, node->id, write_fault ? 'W' : 'R')); + kfd_smi_event_add(task, node, KFD_SMI_EVENT_PAGE_FAULT_START, + KFD_EVENT_FMT_PAGEFAULT_START(ktime_to_ns(ts), + kfd_smi_task_to_pid(task), address, node->id, + write_fault ? 'W' : 'R')); } -void kfd_smi_event_page_fault_end(struct kfd_node *node, pid_t pid, +void kfd_smi_event_page_fault_end(struct kfd_node *node, struct task_struct *task, unsigned long address, bool migration) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_PAGE_FAULT_END, + kfd_smi_event_add(task, node, KFD_SMI_EVENT_PAGE_FAULT_END, KFD_EVENT_FMT_PAGEFAULT_END(ktime_get_boottime_ns(), - pid, address, node->id, migration ? 'M' : 'U')); + kfd_smi_task_to_pid(task), address, node->id, + migration ? 'M' : 'U')); } -void kfd_smi_event_migration_start(struct kfd_node *node, pid_t pid, +void kfd_smi_event_migration_start(struct kfd_node *node, struct task_struct *task, unsigned long start, unsigned long end, uint32_t from, uint32_t to, uint32_t prefetch_loc, uint32_t preferred_loc, uint32_t trigger) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_MIGRATE_START, - KFD_EVENT_FMT_MIGRATE_START( - ktime_get_boottime_ns(), pid, start, end - start, - from, to, prefetch_loc, preferred_loc, trigger)); + kfd_smi_event_add(task, node, KFD_SMI_EVENT_MIGRATE_START, + KFD_EVENT_FMT_MIGRATE_START(ktime_get_boottime_ns(), + kfd_smi_task_to_pid(task), start, end - start, from, + to, prefetch_loc, preferred_loc, trigger)); } -void kfd_smi_event_migration_end(struct kfd_node *node, pid_t pid, +void kfd_smi_event_migration_end(struct kfd_node *node, struct task_struct *task, unsigned long start, unsigned long end, uint32_t from, uint32_t to, uint32_t trigger, int error_code) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_MIGRATE_END, - KFD_EVENT_FMT_MIGRATE_END( - ktime_get_boottime_ns(), pid, start, end - start, - from, to, trigger, error_code)); + kfd_smi_event_add(task, node, KFD_SMI_EVENT_MIGRATE_END, + KFD_EVENT_FMT_MIGRATE_END(ktime_get_boottime_ns(), + kfd_smi_task_to_pid(task), start, end - start, from, + to, trigger, error_code)); } -void kfd_smi_event_queue_eviction(struct kfd_node *node, pid_t pid, +void kfd_smi_event_queue_eviction(struct kfd_node *node, struct task_struct *task, uint32_t trigger) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_QUEUE_EVICTION, - KFD_EVENT_FMT_QUEUE_EVICTION(ktime_get_boottime_ns(), pid, - node->id, trigger)); + kfd_smi_event_add(task, node, KFD_SMI_EVENT_QUEUE_EVICTION, + KFD_EVENT_FMT_QUEUE_EVICTION(ktime_get_boottime_ns(), + kfd_smi_task_to_pid(task), node->id, trigger)); } -void kfd_smi_event_queue_restore(struct kfd_node *node, pid_t pid) +void kfd_smi_event_queue_restore(struct kfd_node *node, struct task_struct *task) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_QUEUE_RESTORE, - KFD_EVENT_FMT_QUEUE_RESTORE(ktime_get_boottime_ns(), pid, - node->id, '0')); + kfd_smi_event_add(task, node, KFD_SMI_EVENT_QUEUE_RESTORE, + KFD_EVENT_FMT_QUEUE_RESTORE(ktime_get_boottime_ns(), + kfd_smi_task_to_pid(task), node->id, '0')); } void kfd_smi_event_queue_restore_rescheduled(struct mm_struct *mm) @@ -329,21 +348,23 @@ void kfd_smi_event_queue_restore_rescheduled(struct mm_struct *mm) for (i = 0; i < p->n_pdds; i++) { struct kfd_process_device *pdd = p->pdds[i]; - kfd_smi_event_add(p->lead_thread->pid, pdd->dev, + kfd_smi_event_add(p->lead_thread, pdd->dev, KFD_SMI_EVENT_QUEUE_RESTORE, KFD_EVENT_FMT_QUEUE_RESTORE(ktime_get_boottime_ns(), - p->lead_thread->pid, pdd->dev->id, 'R')); + kfd_smi_task_to_pid(p->lead_thread), + pdd->dev->id, 'R')); } kfd_unref_process(p); } -void kfd_smi_event_unmap_from_gpu(struct kfd_node *node, pid_t pid, +void kfd_smi_event_unmap_from_gpu(struct kfd_node *node, struct task_struct *task, unsigned long address, unsigned long last, uint32_t trigger) { - kfd_smi_event_add(pid, node, KFD_SMI_EVENT_UNMAP_FROM_GPU, + kfd_smi_event_add(task, node, KFD_SMI_EVENT_UNMAP_FROM_GPU, KFD_EVENT_FMT_UNMAP_FROM_GPU(ktime_get_boottime_ns(), - pid, address, last - address + 1, node->id, trigger)); + kfd_smi_task_to_pid(task), address, + last - address + 1, node->id, trigger)); } void kfd_smi_event_process(struct kfd_process_device *pdd, bool start) @@ -358,7 +379,7 @@ void kfd_smi_event_process(struct kfd_process_device *pdd, bool start) task_info = amdgpu_vm_get_task_info_vm(avm); if (task_info) { - kfd_smi_event_add(task_info->tgid, pdd->dev, + kfd_smi_event_add(NULL, pdd->dev, start ? KFD_SMI_EVENT_PROCESS_START : KFD_SMI_EVENT_PROCESS_END, KFD_EVENT_FMT_PROCESS(task_info->task.pid, @@ -387,7 +408,7 @@ int kfd_smi_event_open(struct kfd_node *dev, uint32_t *fd) spin_lock_init(&client->lock); client->events = 0; client->dev = dev; - client->pid = current->tgid; + client->pid = kfd_smi_task_to_pid(current); client->suser = capable(CAP_SYS_ADMIN); spin_lock(&dev->smi_lock); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.h b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.h index bb4d72b57387..afa93d7cfa7f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.h @@ -32,25 +32,25 @@ void kfd_smi_event_update_thermal_throttling(struct kfd_node *dev, uint64_t throttle_bitmask); void kfd_smi_event_update_gpu_reset(struct kfd_node *dev, bool post_reset, struct amdgpu_reset_context *reset_context); -void kfd_smi_event_page_fault_start(struct kfd_node *node, pid_t pid, +void kfd_smi_event_page_fault_start(struct kfd_node *node, struct task_struct *task, unsigned long address, bool write_fault, ktime_t ts); -void kfd_smi_event_page_fault_end(struct kfd_node *node, pid_t pid, +void kfd_smi_event_page_fault_end(struct kfd_node *node, struct task_struct *task, unsigned long address, bool migration); -void kfd_smi_event_migration_start(struct kfd_node *node, pid_t pid, +void kfd_smi_event_migration_start(struct kfd_node *node, struct task_struct *task, unsigned long start, unsigned long end, uint32_t from, uint32_t to, uint32_t prefetch_loc, uint32_t preferred_loc, uint32_t trigger); -void kfd_smi_event_migration_end(struct kfd_node *node, pid_t pid, +void kfd_smi_event_migration_end(struct kfd_node *node, struct task_struct *task, unsigned long start, unsigned long end, uint32_t from, uint32_t to, uint32_t trigger, int error_code); -void kfd_smi_event_queue_eviction(struct kfd_node *node, pid_t pid, +void kfd_smi_event_queue_eviction(struct kfd_node *node, struct task_struct *task, uint32_t trigger); -void kfd_smi_event_queue_restore(struct kfd_node *node, pid_t pid); +void kfd_smi_event_queue_restore(struct kfd_node *node, struct task_struct *task); void kfd_smi_event_queue_restore_rescheduled(struct mm_struct *mm); -void kfd_smi_event_unmap_from_gpu(struct kfd_node *node, pid_t pid, +void kfd_smi_event_unmap_from_gpu(struct kfd_node *node, struct task_struct *task, unsigned long address, unsigned long last, uint32_t trigger); void kfd_smi_event_process(struct kfd_process_device *pdd, bool start); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 3841943da5ec..d64d104783d4 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1408,7 +1408,7 @@ svm_range_unmap_from_gpus(struct svm_range *prange, unsigned long start, return -EINVAL; } - kfd_smi_event_unmap_from_gpu(pdd->dev, p->lead_thread->pid, + kfd_smi_event_unmap_from_gpu(pdd->dev, p->lead_thread, start, last, trigger); r = svm_range_unmap_from_gpu(pdd->dev->adev, @@ -3205,7 +3205,7 @@ retry_write_locked: svms, prange->start, prange->last, best_loc, prange->actual_loc); - kfd_smi_event_page_fault_start(node, p->lead_thread->pid, addr, + kfd_smi_event_page_fault_start(node, p->lead_thread, addr, write_fault, timestamp); /* Align migration range start and size to granularity size */ @@ -3248,7 +3248,7 @@ retry_write_locked: r, svms, start, last); out_migrate_fail: - kfd_smi_event_page_fault_end(node, p->lead_thread->pid, addr, + kfd_smi_event_page_fault_end(node, p->lead_thread, addr, migration); out_unlock_range: -- cgit v1.2.3 From 60597d2cb21990face4ac60bb0f9a642c00ff6d2 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Sun, 31 May 2026 12:57:41 +0200 Subject: drm/amd/display: Use handle_hpd_irq_helper for HPD RX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove duplicated code and just call handle_hpd_irq_helper with the appropriate detect reason. Signed-off-by: Timur Kristóf Signed-off-by: Aurabindo Pillai Reviewed-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 41 +---------------------- 1 file changed, 1 insertion(+), 40 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 22cbfc159cfa..f34f4e65e933 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4492,14 +4492,12 @@ static void handle_hpd_rx_irq(void *param) struct dc_link *dc_link = aconnector->dc_link; bool is_mst_root_connector = aconnector->mst_mgr.mst_state; bool result = false; - enum dc_connection_type new_connection_type = dc_connection_none; struct amdgpu_device *adev = drm_to_adev(dev); union hpd_irq_data hpd_irq_data; bool link_loss = false; bool has_left_work = false; int idx = dc_link->link_index; struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; - struct dc *dc = aconnector->dc_link->ctx->dc; memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); @@ -4568,44 +4566,7 @@ static void handle_hpd_rx_irq(void *param) out: if (result && !is_mst_root_connector) { /* Downstream Port status changed. */ - if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) - drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); - - if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(dc_link); - - if (aconnector->fake_enable) - aconnector->fake_enable = false; - - amdgpu_dm_update_connector_after_detect(aconnector); - - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - drm_kms_helper_connector_hotplug_event(connector); - } else { - bool ret = false; - - mutex_lock(&adev->dm.dc_lock); - dc_exit_ips_for_hw_access(dc); - ret = dc_link_detect(dc_link, DETECT_REASON_HPDRX); - mutex_unlock(&adev->dm.dc_lock); - - if (ret) { - if (aconnector->fake_enable) - aconnector->fake_enable = false; - - amdgpu_dm_update_connector_after_detect(aconnector); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - drm_kms_helper_connector_hotplug_event(connector); - } - } + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); } if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { if (adev->dm.hdcp_workqueue) -- cgit v1.2.3 From 251a01d34b44adfa70e6591619ab96204277133b Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 19 May 2026 15:57:28 +0000 Subject: drm/amd/display: fix compressed buffer config routine waiting time Replace the four open-coded REG_WAIT calls with calls to dcn31_wait_for_det_apply() so the compressed buffer (compbuf) sizing path waits long enough for the DET size update to take effect, and the wait timing stays consistent across the driver. No functional change beyond the corrected timeout. Signed-off-by: Antonio Quartulli Signed-off-by: Aurabindo Pillai Reviewed-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubbub/dcn31/dcn31_hubbub.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn31/dcn31_hubbub.c b/drivers/gpu/drm/amd/display/dc/hubbub/dcn31/dcn31_hubbub.c index 79cb506be5cb..cbcd22789013 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn31/dcn31_hubbub.c +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn31/dcn31_hubbub.c @@ -138,10 +138,10 @@ static void dcn31_program_compbuf_size(struct hubbub *hubbub, unsigned int compb if (safe_to_increase || compbuf_size_segments <= hubbub2->compbuf_size_segments) { if (compbuf_size_segments > hubbub2->compbuf_size_segments) { - REG_WAIT(DCHUBBUB_DET0_CTRL, DET0_SIZE_CURRENT, hubbub2->det0_size, 1, 100); - REG_WAIT(DCHUBBUB_DET1_CTRL, DET1_SIZE_CURRENT, hubbub2->det1_size, 1, 100); - REG_WAIT(DCHUBBUB_DET2_CTRL, DET2_SIZE_CURRENT, hubbub2->det2_size, 1, 100); - REG_WAIT(DCHUBBUB_DET3_CTRL, DET3_SIZE_CURRENT, hubbub2->det3_size, 1, 100); + dcn31_wait_for_det_apply(hubbub, 0); + dcn31_wait_for_det_apply(hubbub, 1); + dcn31_wait_for_det_apply(hubbub, 2); + dcn31_wait_for_det_apply(hubbub, 3); } /* Should never be hit, if it is we have an erroneous hw config*/ ASSERT(hubbub2->det0_size + hubbub2->det1_size + hubbub2->det2_size -- cgit v1.2.3 From 67b111fcf9bea9a27c2ba6db49aa605639d42b5b Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Fri, 29 May 2026 19:40:42 -0500 Subject: drm/amd/display: Promote DC to 3.2.385 Summary: * Display connectivity & HPD: - Retry link detection on resume, boot, and hotplug - Refactor HPD RX to use handle_hpd_irq_helper with detect reason - Always create delayed HPD work queue - Restore periodic detection for DCN35 * DCN42B support: - Fix DCN42B version detection - Add DCN42B to dml21_translation_helper * KUnit testing infrastructure: - Add KUnit tests for amdgpu_dm_pp_smu, amdgpu_dm_mst_types, and writeback connector - Extract HDCP and DPRX CRC transition helpers for KUnit - Export symbols for KUnit test modules - Enable warnings as errors for KUnit tests * Fixes & cleanups: - Fix compressed buffer config routine waiting time - Fix incorrect logic in CRC source handling - Fix writeback format loop and variable init - Fix max dispclk_khz/dppclk_khz double 1000 - Remove duplicate pp_rn_set_wm_ranges - Remove dead code in dm_dp_mst_get_modes - Remove redundant code in amdgpu_dm_replay - Skip PHY SSC reduction on some 8K panels - Temp disable repeater FGCG as workaround - Deprecate DMUB register offload functionality - TEST_HARNESS FSN could be 0 * Firmware: - DMUB FW promotion to 0.1.62.0 Signed-off-by: Taimur Hassan Signed-off-by: Aurabindo Pillai Reviewed-by: Alex Hung 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 d74776802418..b8ac462a676a 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -65,7 +65,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.384" +#define DC_VER "3.2.385" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC -- cgit v1.2.3 From 6b3453ec5c3ae499ee87093b4cf2c8e76513e2d3 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 3 Jun 2026 10:45:48 +0800 Subject: drm/amdgpu/ras: Parse all deferred errors with UMC aca handle We should only increase the deferred errors in UMC block Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 210fbd8851a6..840610538c1f 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 @@ -213,7 +213,7 @@ static int aca_parse_umc_bank(struct ras_core_context *ras_core, struct aca_bank_reg *bank = (struct aca_bank_reg *)data; struct aca_bank_ecc *ecc = (struct aca_bank_ecc *)buf; struct aca_ecc_info bank_info; - uint32_t ext_error_code; + uint32_t ext_error_code, misc0_errcnt; uint64_t status0; status0 = bank->regs[ACA_REG_IDX__STATUS]; @@ -228,15 +228,14 @@ static int aca_parse_umc_bank(struct ras_core_context *ras_core, ecc->bank_info.addr = bank->regs[ACA_REG_IDX__ADDR]; ext_error_code = ACA_REG_STATUS_ERRORCODEEXT(status0); + misc0_errcnt = ACA_REG_MISC0_ERRCNT(bank->regs[ACA_REG_IDX__MISC0]); if (aca_check_umc_de(ras_core, status0)) - ecc->de_count = 1; + ecc->de_count = misc0_errcnt ? misc0_errcnt : 1; else if (aca_check_umc_ue(ras_core, status0)) - ecc->ue_count = ext_error_code ? - 1 : ACA_REG_MISC0_ERRCNT(bank->regs[ACA_REG_IDX__MISC0]); + ecc->ue_count = ext_error_code ? 1 : misc0_errcnt; else if (aca_check_umc_ce(ras_core, status0)) - ecc->ce_count = ext_error_code ? - 1 : ACA_REG_MISC0_ERRCNT(bank->regs[ACA_REG_IDX__MISC0]); + ecc->ce_count = ext_error_code ? 1 : misc0_errcnt; return 0; } @@ -266,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 = 1; + ecc->de_count = 0; } else { if (bank->ecc_type == RAS_ERR_TYPE__UE) ecc->ue_count = 1; -- cgit v1.2.3 From 97ba25c551e2252b93755590a608a21637a1305f Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Sat, 6 Jun 2026 21:15:54 +0800 Subject: drm/amdgpu/ras: added RAS EEPROM device support check Added RAS EEPROM device support check Signed-off-by: Ce Sun Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 a22d1aebbeb9..ee48adb30731 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 @@ -142,6 +142,21 @@ static int amdgpu_ras_mgr_init_eeprom_config(struct amdgpu_device *adev, return 0; } +static bool amdgpu_ras_mgr_eeprom_is_supported(struct amdgpu_device *adev) +{ + if (amdgpu_sriov_vf(adev)) + return false; + + switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) { + case IP_VERSION(13, 0, 6): + case IP_VERSION(13, 0, 12): + case IP_VERSION(13, 0, 14): + return (adev->gmc.is_app_apu) ? false : true; + default: + return false; + } +} + static int amdgpu_ras_mgr_init_mp1_config(struct amdgpu_device *adev, struct ras_core_config *config) { @@ -266,7 +281,8 @@ static struct ras_core_context *amdgpu_ras_mgr_create_ras_core(struct amdgpu_dev init_config.aca_ip_version = IP_VERSION(1, 0, 0); init_config.sys_fn = &amdgpu_ras_sys_fn; - init_config.ras_eeprom_supported = true; + init_config.ras_eeprom_supported = + amdgpu_ras_mgr_eeprom_is_supported(adev); init_config.poison_supported = amdgpu_ras_is_poison_mode_supported(adev); -- cgit v1.2.3 From dbae980eefb2f46f31cee12f1f8540d0d79f61ae Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 5 Jun 2026 15:28:40 +0800 Subject: drm/amdgpu: allocate lockdep mutex on the heap to fix stack overflow Replace the stack-allocated amdgpu_lockdep mutex with a heap allocation via kmalloc to fix a stack overflow caused by the large struct size. Signed-off-by: Prike Liang Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c | 103 ++++++++++++++-------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c index d5d71fd7c70d..61450af539a6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c @@ -16,6 +16,17 @@ #ifdef CONFIG_LOCKDEP +struct amdgpu_lockdep_dummy_locks { + struct mutex reset_lock; + struct mutex userq_sch_mutex; + struct mutex userq_mutex; + struct mutex notifier_lock; + struct mutex vram_lock; + struct mutex srbm_mutex; + struct mutex grbm_idx_mutex; + spinlock_t mmio_idx_lock; +}; + /* Lock class keys for associating with real driver locks */ static struct lock_class_key amdgpu_userq_sch_mutex_key; static struct lock_class_key amdgpu_userq_mutex_key; @@ -84,72 +95,65 @@ void amdgpu_lockdep_set_class(struct amdgpu_device *adev) int amdgpu_lockdep_init(void) { struct amdgpu_reset_domain *reset_domain = NULL; - struct amdgpu_reset_control reset_ctl; - struct mutex userq_sch_mutex; - struct mutex userq_mutex; - struct mutex notifier_lock; - struct mutex vram_lock; - struct mutex srbm_mutex; - struct mutex grbm_idx_mutex; - spinlock_t mmio_idx_lock; + struct amdgpu_lockdep_dummy_locks *locks; unsigned long flags; + locks = kzalloc(sizeof(*locks), GFP_KERNEL); + if (!locks) + return -ENOMEM; + /* * Initialize dummy reset domain */ reset_domain = amdgpu_reset_create_reset_domain(SINGLE_DEVICE, "lockdep_test"); - if (!reset_domain) + if (!reset_domain) { + kfree(locks); return -ENOMEM; - + } /* Initialize dummy locks */ - mutex_init(&userq_sch_mutex); - mutex_init(&userq_mutex); - mutex_init(¬ifier_lock); - mutex_init(&vram_lock); - mutex_init(&reset_ctl.reset_lock); - mutex_init(&srbm_mutex); - mutex_init(&grbm_idx_mutex); - spin_lock_init(&mmio_idx_lock); + mutex_init(&locks->userq_sch_mutex); + mutex_init(&locks->userq_mutex); + mutex_init(&locks->notifier_lock); + mutex_init(&locks->vram_lock); + mutex_init(&locks->reset_lock); + mutex_init(&locks->srbm_mutex); + mutex_init(&locks->grbm_idx_mutex); + spin_lock_init(&locks->mmio_idx_lock); /* * Associate dummy locks with the same class keys used for real * driver locks. This ensures lockdep connects the ordering learned * here with the actual locks used at runtime. */ - lockdep_set_class(&userq_sch_mutex, &amdgpu_userq_sch_mutex_key); - lockdep_set_class(&userq_mutex, &amdgpu_userq_mutex_key); - lockdep_set_class(¬ifier_lock, &amdgpu_notifier_lock_key); - lockdep_set_class(&vram_lock, &amdgpu_vram_lock_key); + lockdep_set_class(&locks->userq_sch_mutex, &amdgpu_userq_sch_mutex_key); + lockdep_set_class(&locks->userq_mutex, &amdgpu_userq_mutex_key); + lockdep_set_class(&locks->notifier_lock, &amdgpu_notifier_lock_key); + lockdep_set_class(&locks->vram_lock, &amdgpu_vram_lock_key); lockdep_set_class(&reset_domain->sem, &amdgpu_reset_sem_key); - lockdep_set_class(&reset_ctl.reset_lock, &amdgpu_reset_lock_key); - lockdep_set_class(&srbm_mutex, &amdgpu_srbm_lock_key); - lockdep_set_class(&grbm_idx_mutex, &amdgpu_grbm_lock_key); - lockdep_set_class(&mmio_idx_lock, &amdgpu_mmio_lock_key); - + lockdep_set_class(&locks->reset_lock, &amdgpu_reset_lock_key); + lockdep_set_class(&locks->srbm_mutex, &amdgpu_srbm_lock_key); + lockdep_set_class(&locks->grbm_idx_mutex, &amdgpu_grbm_lock_key); + lockdep_set_class(&locks->mmio_idx_lock, &amdgpu_mmio_lock_key); /* * Take locks in the correct order to train lockdep. * This establishes the dependency chain. */ /* Level 1: Global userq scheduler mutex (outermost) */ - mutex_lock(&userq_sch_mutex); + mutex_lock(&locks->userq_sch_mutex); /* Level 2: Per-context userq mutex */ - mutex_lock(&userq_mutex); - + mutex_lock(&locks->userq_mutex); /* Level 3: MMU notifier lock */ - mutex_lock(¬ifier_lock); - + mutex_lock(&locks->notifier_lock); /* Level 4: VRAM allocator lock */ - mutex_lock(&vram_lock); - + mutex_lock(&locks->vram_lock); /* Level 5: Reset domain semaphore */ down_read(&reset_domain->sem); /* Level 6: Reset control lock */ - mutex_lock(&reset_ctl.reset_lock); - + mutex_lock(&locks->reset_lock); /* * Mark potential memory reclaim boundary. * GPU operations might trigger memory allocation/reclaim. @@ -157,36 +161,35 @@ int amdgpu_lockdep_init(void) fs_reclaim_acquire(GFP_KERNEL); /* Level 7: SRBM register access */ - mutex_lock(&srbm_mutex); - + mutex_lock(&locks->srbm_mutex); /* Level 8: GRBM index access */ - mutex_lock(&grbm_idx_mutex); + mutex_lock(&locks->grbm_idx_mutex); /* Level 9: MMIO index access (innermost lock, spinlock) */ - spin_lock_irqsave(&mmio_idx_lock, flags); - + spin_lock_irqsave(&locks->mmio_idx_lock, flags); /* * All locks acquired in order. * Lockdep has now learned the valid dependency chain. */ /* Release in reverse order */ - spin_unlock_irqrestore(&mmio_idx_lock, flags); - mutex_unlock(&grbm_idx_mutex); - mutex_unlock(&srbm_mutex); - + spin_unlock_irqrestore(&locks->mmio_idx_lock, flags); + mutex_unlock(&locks->grbm_idx_mutex); + mutex_unlock(&locks->srbm_mutex); fs_reclaim_release(GFP_KERNEL); - mutex_unlock(&reset_ctl.reset_lock); + mutex_unlock(&locks->reset_lock); up_read(&reset_domain->sem); - mutex_unlock(&vram_lock); - mutex_unlock(¬ifier_lock); - mutex_unlock(&userq_mutex); - mutex_unlock(&userq_sch_mutex); + + mutex_unlock(&locks->vram_lock); + mutex_unlock(&locks->notifier_lock); + mutex_unlock(&locks->userq_mutex); + mutex_unlock(&locks->userq_sch_mutex); /* Cleanup */ amdgpu_reset_put_reset_domain(reset_domain); + kfree(locks); pr_info("AMDGPU: Lockdep annotations initialized (9 lock levels)\n"); return 0; -- cgit v1.2.3 From fe29192e1c9cc26da75b9410a1e8159c659b525b Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Sat, 6 Jun 2026 21:20:23 +0800 Subject: drm/amdgpu/ras: Add flag to make VBIOS read optional Add flag to make VBIOS read optional Signed-off-by: Ce Sun Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_eeprom_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_eeprom_i2c.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_eeprom_i2c.c index 3ed3ff42b7e1..9c6d0024210d 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_eeprom_i2c.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_eeprom_i2c.c @@ -67,7 +67,7 @@ static int ras_eeprom_i2c_config(struct ras_core_context *ras_core) struct ras_eeprom_control *control = &ras_core->ras_eeprom; u8 i2c_addr; - if (amdgpu_atomfirmware_ras_rom_addr(adev, &i2c_addr)) { + if (adev->bios && amdgpu_atomfirmware_ras_rom_addr(adev, &i2c_addr)) { /* The address given by VBIOS is an 8-bit, wire-format * address, i.e. the most significant byte. * -- cgit v1.2.3 From bf21af331ebf72d0935fd70c73192414a422c03a Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Fri, 5 Jun 2026 23:44:08 +0800 Subject: drm/amdgpu/gfx: fix cleaner shader IB buffer overflow The cleaner shader sysfs path allocates a 16-dword (64 byte) IB but incorrectly fills (align_mask + 1) dwords. On GFX rings align_mask is 0xff, so the loop wrote 256 dwords into a 64-byte buffer, causing a kernel page fault. The IB only needs to be a minimal NOP shell to schedule the job; the cleaner shader itself is emitted on the ring via emit_cleaner_shader(). Fill 16 dwords to match the allocation. v2: Use ib_size_dw variable (Lijo) Fixes: d361ad5d2fc0 ("drm/amdgpu: Add sysfs interface for running cleaner shader") Suggested-by: Lijo Lazar Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 59f35a310253..0506b90f318e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -1689,12 +1689,13 @@ static int amdgpu_gfx_run_cleaner_shader_job(struct amdgpu_ring *ring) struct amdgpu_device *adev = ring->adev; struct drm_gpu_scheduler *sched = &ring->sched; struct drm_sched_entity entity; + unsigned int ib_size_dw = 16; static atomic_t counter; struct dma_fence *f; struct amdgpu_job *job; struct amdgpu_ib *ib; void *owner; - int i, r; + int r; /* Initialize the scheduler entity */ r = drm_sched_entity_init(&entity, DRM_SCHED_PRIORITY_NORMAL, @@ -1712,7 +1713,7 @@ 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, - 64, 0, &job, + ib_size_dw * sizeof(uint32_t), 0, &job, AMDGPU_KERNEL_JOB_ID_CLEANER_SHADER); if (r) goto err; @@ -1722,9 +1723,8 @@ static int amdgpu_gfx_run_cleaner_shader_job(struct amdgpu_ring *ring) job->run_cleaner_shader = true; ib = &job->ibs[0]; - for (i = 0; i <= ring->funcs->align_mask; ++i) - ib->ptr[i] = ring->funcs->nop; - ib->length_dw = ring->funcs->align_mask + 1; + memset32(ib->ptr, ring->funcs->nop, ib_size_dw); + ib->length_dw = ib_size_dw; f = amdgpu_job_submit(job); -- cgit v1.2.3 From d785df5598fd1d1cc2f2f45c05448271b6d490b7 Mon Sep 17 00:00:00 2001 From: "Uwe Kleine-König (The Capable Hub)" Date: Tue, 28 Apr 2026 16:47:03 +0200 Subject: drm/amdgpu: Don't use UTS_RELEASE directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UTS_RELEASE evaluates to a static string and changes quite easily (e.g. uncommitted changes in the source tree or new commits). So when checking if a patch introduces changes to the resulting binary each usage of UTS_RELEASE is source of annoyance. Instead of using UTS_RELEASE directly use init_utsname()->release which evaluates to the same string but with that a change of UTS_RELEASE doesn't affect amdgpu_dev_coredump.o. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260428144704.1114562-2-u.kleine-koenig@baylibre.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index bed68f0c3080..322c55aaf15f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -22,8 +22,8 @@ * */ -#include #include +#include #include "amdgpu_dev_coredump.h" #include "atom.h" @@ -237,7 +237,7 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf drm_printf(&p, "**** AMDGPU Device Coredump ****\n"); drm_printf(&p, "version: " AMDGPU_COREDUMP_VERSION "\n"); - drm_printf(&p, "kernel: " UTS_RELEASE "\n"); + drm_printf(&p, "kernel: %s\n", init_utsname()->release); drm_printf(&p, "module: " KBUILD_MODNAME "\n"); drm_printf(&p, "time: %ptSp\n", &coredump->reset_time); -- cgit v1.2.3 From 4693ade087f2e04d3c4964f46663a5839d778530 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Tue, 9 Jun 2026 12:33:40 -0400 Subject: drm/amdkfd: Fix reset event signal During the KFD/KCQ coordination rework, bad queues not requiring reset were combined into the rework and generated wrong reset signals to the process. Fix it by adding the reset check. Signed-off-by: Amber Lin Reviewed-by: Shaoyun Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 14159a682823..daef468eba80 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -474,7 +474,11 @@ static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) goto fail; dqm->detect_hang_count = num_hung; - kfd_signal_reset_event(dqm->dev); + /* When MES doesn't detect any queue hang, no reset happens. Don't signal reset + * event. + */ + if (dqm->detect_hang_count) + kfd_signal_reset_event(dqm->dev); fail: dqm->detect_hang_count = 0; -- cgit v1.2.3 From d04560b5f9c29ff4c1787dad3b491fa115fd07cb Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Fri, 5 Jun 2026 18:18:10 -0400 Subject: drm/amdkfd: Add gfx11 queue/pipe reset support to topology Add gfx11 queue/pipe reset support to KFD topology Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 1 + drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index 591f41eadae2..73bf7120d622 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -210,6 +210,7 @@ enum cache_policy { }; #define KFD_GC_VERSION(dev) (amdgpu_ip_version((dev)->adev, GC_HWIP, 0)) +#define KFD_GC_VERSION_MAJ(dev) ((KFD_GC_VERSION(dev) >> 24)) #define KFD_IS_SOC15(dev) ((KFD_GC_VERSION(dev)) >= (IP_VERSION(9, 0, 1))) #define KFD_SUPPORT_XNACK_PER_PROCESS(dev)\ ((KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 2)) || \ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 00517c3d0e6a..4af9b567e499 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2024,6 +2024,9 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_ALU_OPERATIONS_SUPPORTED; + if (KFD_GC_VERSION_MAJ(dev->gpu) == 11) + dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; + if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 1, 0)) { dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_MEMORY_OPERATIONS_SUPPORTED; -- cgit v1.2.3 From 8f09c0ec21cf34d760ae68719b9a581b73771232 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Thu, 4 Jun 2026 09:24:32 -0400 Subject: drm/amdkfd: add sdma queue counter for gfxv9.4.3 since gfx 9.4.3 HW is calculating accumulated activity counter per-queue in register sdmax_rlcx_utilization_hi/lo, CPFW adds it in sdma MQD for save/restore, KFD will read it from there. gfx 9.4.2 will still keep the way to read from memory at rptr+8. v2: read dynamic counter directly from utilization register v3: add CPFW supported version check (Harish) Signed-off-by: Eric Huang Reviewed-by: Harish Kasiviswanathan Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdgpu/amdgpu_amdkfd_gc_9_4_3.c | 63 +++++++++++++++++++++- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 27 ++++++++-- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 14 ++++- .../amd/include/asic_reg/sdma/sdma_4_4_2_offset.h | 4 ++ drivers/gpu/drm/amd/include/kgd_kfd_interface.h | 3 ++ drivers/gpu/drm/amd/include/v9_structs.h | 4 +- 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gc_9_4_3.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gc_9_4_3.c index 6ed399163547..bc079b95fc52 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gc_9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gc_9_4_3.c @@ -530,6 +530,66 @@ static uint32_t kgd_v9_4_3_ptl_ctrl(struct amdgpu_device *adev, ptl_state, fmt1, fmt2); } +static int kgd_gfx_v9_4_3_hqd_sdma_get_counter(struct amdgpu_device *adev, + void *mqd, uint32_t num_sdma_queues_per_eng, + uint64_t *val) +{ + struct v9_sdma_mqd *m = get_sdma_mqd(mqd); + uint32_t sdma_rlc_reg_offset = 0; + uint32_t sdma_rlc_rb_cntl; + uint32_t engine_id, queue_id; + uint32_t engines = adev->sdma.num_instances; + uint32_t sdma_rlcx_rb_base, sdma_rlcx_rb_base_hi; + bool found = false; + + if (!m) + return -EINVAL; + + if (((amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 3) || + amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 4)) && + adev->gfx.mec_fw_version < 194) || + (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 5, 0) && + adev->gfx.mec_fw_version < 44)) { + pr_warn_once("MEC FW doesn't support SDMA counter!\n"); + return -EOPNOTSUPP; + } + + /* SDMA doesn't support over-subscription, there must be + * a HQD associated with a MQD, so found must be true in + * the finding loop. + */ + for (engine_id = 0; engine_id < engines && !found; engine_id++) { + for (queue_id = 0; queue_id < num_sdma_queues_per_eng; queue_id++) { + sdma_rlc_reg_offset = get_sdma_rlc_reg_offset(adev, + engine_id, queue_id); + sdma_rlcx_rb_base = RREG32(sdma_rlc_reg_offset + + regSDMA_RLC0_RB_BASE); + sdma_rlcx_rb_base_hi = RREG32(sdma_rlc_reg_offset + + regSDMA_RLC0_RB_BASE_HI); + + if (m->sdmax_rlcx_rb_base == sdma_rlcx_rb_base && + m->sdmax_rlcx_rb_base_hi == sdma_rlcx_rb_base_hi) { + found = true; + break; + } + } + } + + sdma_rlc_rb_cntl = RREG32(sdma_rlc_reg_offset + regSDMA_RLC0_RB_CNTL); + + /* Read sdma activity counter from utilization register + * if hw queue is enabled, otherwise read from MQD. + */ + if (sdma_rlc_rb_cntl & SDMA_RLC0_RB_CNTL__RB_ENABLE_MASK) + *val = (uint64_t)RREG32(sdma_rlc_reg_offset + regSDMA_RLC0_UTILIZATION_HI) << 32 | + RREG32(sdma_rlc_reg_offset + regSDMA_RLC0_UTILIZATION_LO); + else + *val = (uint64_t)m->sdmax_rlcx_utilization_hi << 32 | + m->sdmax_rlcx_utilization_lo; + + return 0; +} + const struct kfd2kgd_calls gc_9_4_3_kfd2kgd = { .program_sh_mem_settings = kgd_gfx_v9_program_sh_mem_settings, .set_pasid_vmid_mapping = kgd_gfx_v9_4_3_set_pasid_vmid_mapping, @@ -566,5 +626,6 @@ const struct kfd2kgd_calls gc_9_4_3_kfd2kgd = { .hqd_get_pq_addr = kgd_gfx_v9_hqd_get_pq_addr, .hqd_reset = kgd_gfx_v9_hqd_reset, .hqd_sdma_get_doorbell = kgd_gfx_v9_4_3_hqd_sdma_get_doorbell, - .ptl_ctrl = kgd_v9_4_3_ptl_ctrl + .ptl_ctrl = kgd_v9_4_3_ptl_ctrl, + .hqd_sdma_get_counter = kgd_gfx_v9_4_3_hqd_sdma_get_counter }; 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 daef468eba80..4ae7f4c6365e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -1027,8 +1027,17 @@ static int destroy_queue_nocpsch(struct device_queue_manager *dqm, /* Get the SDMA queue stats */ if ((q->properties.type == KFD_QUEUE_TYPE_SDMA) || (q->properties.type == KFD_QUEUE_TYPE_SDMA_XGMI)) { - retval = read_sdma_queue_counter((uint64_t __user *)q->properties.read_ptr, - &sdma_val); + if (KFD_GC_VERSION(dqm->dev) <= IP_VERSION(9, 4, 2)) + retval = read_sdma_queue_counter( + (uint64_t __user *)q->properties.read_ptr, + &sdma_val); + else + retval = dqm->dev->kfd2kgd->hqd_sdma_get_counter ? + dqm->dev->kfd2kgd->hqd_sdma_get_counter( + dqm->dev->adev, q->mqd, + dqm->dev->kfd->device_info.num_sdma_queues_per_engine, + &sdma_val) : + -EOPNOTSUPP; if (retval) dev_err(dev, "Failed to read SDMA queue counter for queue: %d\n", q->properties.queue_id); @@ -2666,8 +2675,18 @@ static int destroy_queue_cpsch(struct device_queue_manager *dqm, /* Get the SDMA queue stats */ if ((q->properties.type == KFD_QUEUE_TYPE_SDMA) || (q->properties.type == KFD_QUEUE_TYPE_SDMA_XGMI)) { - retval = read_sdma_queue_counter((uint64_t __user *)q->properties.read_ptr, - &sdma_val); + if (KFD_GC_VERSION(dqm->dev) <= IP_VERSION(9, 4, 2)) + retval = read_sdma_queue_counter( + (uint64_t __user *)q->properties.read_ptr, + &sdma_val); + else + retval = dqm->dev->kfd2kgd->hqd_sdma_get_counter ? + dqm->dev->kfd2kgd->hqd_sdma_get_counter( + dqm->dev->adev, q->mqd, + dqm->dev->kfd->device_info.num_sdma_queues_per_engine, + &sdma_val) : + -EOPNOTSUPP; + if (retval) dev_err(dev, "Failed to read SDMA queue counter for queue: %d\n", q->properties.queue_id); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index a7a12fdd2458..e58327c08549 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -91,6 +91,7 @@ struct kfd_sdma_activity_handler_workarea { struct temp_sdma_queue_list { uint64_t __user *rptr; + void *mqd; uint64_t sdma_val; unsigned int queue_id; struct list_head list; @@ -161,6 +162,7 @@ static void kfd_sdma_activity_worker(struct work_struct *work) INIT_LIST_HEAD(&sdma_q->list); sdma_q->rptr = (uint64_t __user *)q->properties.read_ptr; + sdma_q->mqd = q->mqd; sdma_q->queue_id = q->properties.queue_id; list_add_tail(&sdma_q->list, &sdma_q_list.list); } @@ -189,7 +191,17 @@ static void kfd_sdma_activity_worker(struct work_struct *work) list_for_each_entry(sdma_q, &sdma_q_list.list, list) { val = 0; - ret = read_sdma_queue_counter(sdma_q->rptr, &val); + + if (KFD_GC_VERSION(dqm->dev) <= IP_VERSION(9, 4, 2)) + ret = read_sdma_queue_counter(sdma_q->rptr, &val); + else + ret = dqm->dev->kfd2kgd->hqd_sdma_get_counter ? + dqm->dev->kfd2kgd->hqd_sdma_get_counter( + dqm->dev->adev, sdma_q->mqd, + dqm->dev->kfd->device_info.num_sdma_queues_per_engine, + &val) : + -EOPNOTSUPP; + if (ret) { pr_debug("Failed to read SDMA queue active counter for queue id: %d", sdma_q->queue_id); diff --git a/drivers/gpu/drm/amd/include/asic_reg/sdma/sdma_4_4_2_offset.h b/drivers/gpu/drm/amd/include/asic_reg/sdma/sdma_4_4_2_offset.h index ead81aeffd67..11c32e4274fa 100644 --- a/drivers/gpu/drm/amd/include/asic_reg/sdma/sdma_4_4_2_offset.h +++ b/drivers/gpu/drm/amd/include/asic_reg/sdma/sdma_4_4_2_offset.h @@ -493,6 +493,10 @@ #define regSDMA_RLC0_MIDCMD_DATA10_BASE_IDX 0 #define regSDMA_RLC0_MIDCMD_CNTL 0x017b #define regSDMA_RLC0_MIDCMD_CNTL_BASE_IDX 0 +#define regSDMA_RLC0_UTILIZATION_LO 0x017c +#define regSDMA_RLC0_UTILIZATION_LO_BASE_IDX 0 +#define regSDMA_RLC0_UTILIZATION_HI 0x017d +#define regSDMA_RLC0_UTILIZATION_HI_BASE_IDX 0 #define regSDMA_RLC1_RB_CNTL 0x0188 #define regSDMA_RLC1_RB_CNTL_BASE_IDX 0 #define regSDMA_RLC1_RB_BASE 0x0189 diff --git a/drivers/gpu/drm/amd/include/kgd_kfd_interface.h b/drivers/gpu/drm/amd/include/kgd_kfd_interface.h index 44e225e097d0..965b50c8ca30 100644 --- a/drivers/gpu/drm/amd/include/kgd_kfd_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_kfd_interface.h @@ -339,6 +339,9 @@ struct kfd2kgd_calls { uint32_t *ptl_state, enum amdgpu_ptl_fmt *fmt1, enum amdgpu_ptl_fmt *fmt2); + int (*hqd_sdma_get_counter)(struct amdgpu_device *adev, + void *mqd, uint32_t num_sdma_queues_per_eng, + uint64_t *val); }; #endif /* KGD_KFD_INTERFACE_H_INCLUDED */ diff --git a/drivers/gpu/drm/amd/include/v9_structs.h b/drivers/gpu/drm/amd/include/v9_structs.h index a2f81b9c38af..e0d387f08576 100644 --- a/drivers/gpu/drm/amd/include/v9_structs.h +++ b/drivers/gpu/drm/amd/include/v9_structs.h @@ -69,8 +69,8 @@ struct v9_sdma_mqd { uint32_t sdmax_rlcx_midcmd_cntl; uint32_t reserved_42; uint32_t reserved_43; - uint32_t reserved_44; - uint32_t reserved_45; + uint32_t sdmax_rlcx_utilization_lo; + uint32_t sdmax_rlcx_utilization_hi; uint32_t reserved_46; uint32_t reserved_47; uint32_t reserved_48; -- cgit v1.2.3 From 50b24a20be8c0d35f89f7d5c39533f69d84fe48e Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Thu, 11 Jun 2026 10:58:05 +0800 Subject: drm/amdgpu: correct reservation fence slots for userq per-vm BOs eviction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It fixes both the move overflow and the eviction fence add for evicting these per-vm BOs. Signed-off-by: Prike Liang Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 4e3bd505c368..3bcde67aa092 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -921,7 +921,8 @@ amdgpu_userq_bo_validate(struct amdgpu_device *adev, struct drm_exec *exec, spin_unlock(&vm->individual_lock); bo = bo_va->base.bo; - ret = drm_exec_prepare_obj(exec, &bo->tbo.base, 2); + ret = drm_exec_prepare_obj(exec, &bo->tbo.base, + TTM_NUM_MOVE_FENCES + 1); if (unlikely(ret)) return ret; -- cgit v1.2.3 From a234d4a543187f2d94a2ecd3369748dc071c655c Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 11 Jun 2026 15:38:46 +0800 Subject: drm/amdgpu/ras: Add address sanity check for uniras Add address sanity check for uniras Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c | 3 --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c | 19 ++++++++++++++ drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h | 3 +++ drivers/gpu/drm/amd/ras/rascore/ras.h | 2 ++ drivers/gpu/drm/amd/ras/rascore/ras_core.c | 10 ++++++++ drivers/gpu/drm/amd/ras/rascore/ras_umc.c | 32 +++++++++++++++++++++--- drivers/gpu/drm/amd/ras/rascore/ras_umc.h | 6 +++++ 7 files changed, 68 insertions(+), 7 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 658bf3fdb66b..bfbfdffbfbe6 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 @@ -30,9 +30,6 @@ #include "amdgpu_ras_mgr.h" #include "amdgpu_virt_ras_cmd.h" -/* inject address is 52 bits */ -#define RAS_UMC_INJECT_ADDR_LIMIT (0x1ULL << 52) - #define AMDGPU_RAS_TYPE_RASCORE 0x1 #define AMDGPU_RAS_TYPE_AMDGPU 0x2 #define AMDGPU_RAS_TYPE_VF 0x3 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 7d728e523604..e4444798bc73 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 @@ -267,6 +267,24 @@ static int amdgpu_ras_sys_put_gpu_mem(struct ras_core_context *ras_core, return 0; } +static int amdgpu_ras_sys_check_address_sanity(struct ras_core_context *ras_core, + uint64_t addr) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)ras_core->dev; + + if ((addr >= adev->gmc.mc_vram_size && + adev->gmc.mc_vram_size) || + (addr >= RAS_UMC_INJECT_ADDR_LIMIT)) + return -EINVAL; + + if (addr >= adev->gmc.real_vram_size) { + RAS_DEV_WARN(ras_core->dev, "Recorded address out of range: 0x%llx!\n", addr); + return -EINVAL; + } + + return 0; +} + const struct ras_sys_func amdgpu_ras_sys_fn = { .ras_notifier = amdgpu_ras_sys_event_notifier, .get_utc_second_timestamp = amdgpu_ras_sys_get_utc_second_timestamp, @@ -277,4 +295,5 @@ const struct ras_sys_func amdgpu_ras_sys_fn = { .detect_ras_interrupt = amdgpu_ras_sys_detect_ras_interrupt, .get_gpu_mem = amdgpu_ras_sys_get_gpu_mem, .put_gpu_mem = amdgpu_ras_sys_put_gpu_mem, + .check_address_sanity = amdgpu_ras_sys_check_address_sanity, }; diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h b/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h index f34dda7ce87b..2775c7bf41b7 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h +++ b/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h @@ -30,6 +30,9 @@ #include #include "amdgpu.h" +/* inject address is 52 bits */ +#define RAS_UMC_INJECT_ADDR_LIMIT (0x1ULL << 52) + #define RAS_DEV_ERR(device, fmt, ...) \ do { \ if (device) \ diff --git a/drivers/gpu/drm/amd/ras/rascore/ras.h b/drivers/gpu/drm/amd/ras/rascore/ras.h index c059fcebaf00..5869bad978b0 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras.h @@ -231,6 +231,7 @@ struct ras_sys_func { enum gpu_mem_type mem_type, struct gpu_mem_block *gpu_mem); int (*put_gpu_mem)(struct ras_core_context *ras_core, enum gpu_mem_type mem_type, struct gpu_mem_block *gpu_mem); + int (*check_address_sanity)(struct ras_core_context *ras_core, uint64_t addr); }; struct ras_ecc_count { @@ -398,4 +399,5 @@ int ras_core_get_device_system_info(struct ras_core_context *ras_core, struct device_system_info *dev_info); int ras_core_convert_soc_pa_to_cur_nps_pages(struct ras_core_context *ras_core, uint64_t soc_pa, uint64_t *page_pfn, uint32_t max_pages); +int ras_core_check_address_sanity(struct ras_core_context *ras_core, uint64_t addr); #endif diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_core.c b/drivers/gpu/drm/amd/ras/rascore/ras_core.c index 62d124a3eeac..2346918c7736 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_core.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_core.c @@ -676,3 +676,13 @@ int ras_core_convert_soc_pa_to_cur_nps_pages(struct ras_core_context *ras_core, return count; } + +int ras_core_check_address_sanity(struct ras_core_context *ras_core, + uint64_t addr) +{ + if (ras_core && ras_core->sys_fn && + ras_core->sys_fn->check_address_sanity) + return ras_core->sys_fn->check_address_sanity(ras_core, addr); + + return 0; +} diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c index f32ee2fecf53..e366fb97293e 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c @@ -406,7 +406,7 @@ static int ras_umc_update_eeprom_ram_data(struct ras_core_context *ras_core, struct ras_umc *ras_umc = &ras_core->ras_umc; struct eeprom_store_record *data = &ras_umc->umc_err_data.ram_data; uint64_t page_pfn[16]; - int count = 0, j; + int count = 0, i, j; if (!data->space_left && ras_umc_realloc_err_data_space(ras_core, data, 256)) { @@ -418,10 +418,23 @@ static int ras_umc_update_eeprom_ram_data(struct ras_core_context *ras_core, bps, bps->cur_nps, page_pfn, ARRAY_SIZE(page_pfn)); if (count > 0) { for (j = 0; j < count; j++) { + if (ras_core_check_address_sanity(ras_core, + page_pfn[j] << AMDGPU_GPU_PAGE_SHIFT)) { + + for (i = 0; i < data->count; i++) + if (page_pfn[j] == data->bps[i].cur_nps_retired_row_pfn) + break; + data->bps[data->count].cur_nps_retired_row_pfn = U64_MAX; + data->count++; + data->space_left--; + continue; + } + bps->cur_nps_retired_row_pfn = page_pfn[j]; memcpy(&data->bps[data->count], bps, sizeof(*data->bps)); data->count++; data->space_left--; + data->bad_page_num++; } } else { RAS_DEV_ERR(ras_core->dev, "Failed to convert record to nps pages!"); @@ -431,6 +444,14 @@ static int ras_umc_update_eeprom_ram_data(struct ras_core_context *ras_core, return 0; } +static void ras_umc_update_bad_pages(struct ras_core_context *ras_core) +{ + struct ras_umc *ras_umc = &ras_core->ras_umc; + struct eeprom_store_record *data = &ras_umc->umc_err_data.ram_data; + + data->bad_page_num_old = data->bad_page_num; +} + /* it deal with vram only. */ static int ras_umc_add_bad_pages(struct ras_core_context *ras_core, struct eeprom_umc_record *bps, @@ -506,6 +527,7 @@ int ras_umc_load_bad_pages(struct ras_core_context *ras_core) } else { ras_core->ras_umc.umc_err_data.last_retired_pfn = UMC_INV_MEM_PFN; ret = ras_umc_add_bad_pages(ras_core, bps, ras_num_recs, true); + ras_umc_update_bad_pages(ras_core); } kfree(bps); @@ -521,7 +543,8 @@ static int ras_umc_save_bad_pages(struct ras_core_context *ras_core) { struct ras_umc *ras_umc = &ras_core->ras_umc; struct eeprom_store_record *data = &ras_umc->umc_err_data.rom_data; - uint32_t eeprom_record_num; + struct eeprom_store_record *ram_data = &ras_umc->umc_err_data.ram_data; + uint32_t eeprom_record_num, logical_count = 0; int save_count; int ret = 0; @@ -534,6 +557,7 @@ static int ras_umc_save_bad_pages(struct ras_core_context *ras_core) eeprom_record_num = ras_eeprom_get_record_count(ras_core); mutex_lock(&ras_umc->umc_lock); save_count = data->count - eeprom_record_num; + logical_count = ram_data->bad_page_num - ram_data->bad_page_num_old; /* only new entries are saved */ if (save_count > 0) { if (ras_fw_eeprom_supported(ras_core)) @@ -547,8 +571,8 @@ static int ras_umc_save_bad_pages(struct ras_core_context *ras_core) ret = -EIO; goto exit; } - - RAS_DEV_INFO(ras_core->dev, "Saved %d pages to EEPROM table.\n", save_count); + ras_umc_update_bad_pages(ras_core); + RAS_DEV_INFO(ras_core->dev, "Saved %d pages to EEPROM table.\n", logical_count); } exit: diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h index 237525b46b9b..ee7100f25f51 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h @@ -119,6 +119,12 @@ struct eeprom_store_record { int count; /* the space can place new entries */ int space_left; + /* logical bad page number */ + int bad_page_num; + /* the bad page number is ras_num_recs or + * ras_num_recs * retire_unit + */ + int bad_page_num_old; }; struct ras_umc_err_data { -- cgit v1.2.3 From f56d2422bc38147b4e3ca597298676af7ed523c6 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Thu, 28 May 2026 17:33:16 -0400 Subject: drm/amd/display: Increase dcn42b uclk value Increase uclk value in order to enable UHBR20. Reviewed-by: Dillon Varone Signed-off-by: Gabe Teeger Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ce4025591b87..eae4a37b0984 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 @@ -75,7 +75,7 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = { .clk_values_khz = {2}, }, .uclk = { - .clk_values_khz = {400000}, + .clk_values_khz = {2400000}, .num_clk_values = 1, }, .fclk = { -- cgit v1.2.3 From e32cb17d4c97ef4e44f6cbd81d084b3f6df6808e Mon Sep 17 00:00:00 2001 From: Nicholas Kazlauskas Date: Thu, 28 May 2026 10:51:20 -0400 Subject: drm/amd/display: Add a new interface to set idle opts in clock manager [Why & How] For future use in migrating the idle optimizations message to PMFW to DC core. Reviewed-by: Dillon Varone Signed-off-by: Nicholas Kazlauskas Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 2 ++ 1 file changed, 2 insertions(+) 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 f829ce3f70e5..9b5bdcddfa7a 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 @@ -337,6 +337,8 @@ struct clk_mgr_funcs { void (*exit_low_power_state)(struct clk_mgr *clk_mgr); bool (*is_ips_supported)(struct clk_mgr *clk_mgr); + void (*set_idle_power_optimizations)(struct clk_mgr *clk_mgr, bool enable); + void (*init_clocks)(struct clk_mgr *clk_mgr); void (*dump_clk_registers)(struct clk_state_registers_and_bypass *regs_and_bypass, -- cgit v1.2.3 From d00531ce78a29240099e58b813ed6c6cbe833a37 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Thu, 28 May 2026 13:10:27 -0400 Subject: drm/amd/display: Add utm_qos_model pointer to clk_bw_params [Why] Add support for passing QoS model data from clock manager to bandwidth calculation consumers. [How] - Add forward declaration and const pointer for utm_qos_model in clk_bw_params Reviewed-by: Dillon Varone Signed-off-by: Wenjing Liu Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 2 ++ 1 file changed, 2 insertions(+) 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 9b5bdcddfa7a..69c4a49a40fc 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 @@ -30,6 +30,7 @@ #include "dc.h" #include "core_types.h" #include "dm_pp_smu.h" +struct utm_qos_model; /* Constants */ #define DDR4_DRAM_WIDTH 64 @@ -311,6 +312,7 @@ struct clk_bw_params { struct wm_table wm_table; struct dummy_pstate_entry dummy_pstate_table[4]; struct clk_limit_table_entry dc_mode_limit; + const struct utm_qos_model *utm_qos_model; }; /* Public interfaces */ -- cgit v1.2.3 From 234acf1e0a2939dd2db1cbfaafcc19fdaf3ecf5a Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Thu, 28 May 2026 16:43:26 -0400 Subject: drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator [Why] The QoS model is now populated directly in clock manager from firmware data. The translator function pointer is no longer needed. [How] - Remove get_utm_qos_model function pointer from soc_and_ip_translator_funcs - Remove associated forward declarations from soc_and_ip_translator.h Reviewed-by: Dillon Varone Signed-off-by: Wenjing Liu Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h b/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h index 6a97a3e28bd2..5dcb9f8f4daf 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h +++ b/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h @@ -8,26 +8,12 @@ #include "dc.h" #include "dml_top_soc_parameter_types.h" -/* Forward declarations — callers that dereference these structs must include - * the full UTM model headers themselves. */ -struct utm_qos_model; -struct utm_qos_model_dchub_v2; - struct soc_and_ip_translator_funcs { void (*get_soc_bb)( struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config); void (*get_ip_caps)(struct dml2_ip_capabilities *dml_ip_caps); - /** - * get_utm_qos_model - Return the static UTM QoS model for this DCN - * generation. Caller provides storage for @qos_model and @dchub. - * @qos_model: output — populated with SoC bounding box and SOP table - * @dchub: output — populated with DCHUB client extension data - */ - void (*get_utm_qos_model)( - struct utm_qos_model *qos_model, - struct utm_qos_model_dchub_v2 *dchub); }; struct soc_and_ip_translator { -- cgit v1.2.3 From b3aa48ee3e0a00cc85d720da77031605adfc0b66 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Mon, 1 Jun 2026 17:40:18 -0400 Subject: drm/amd/display: Remove unused project_id from DML2 core instance [Why] The project_id field stored in dml2_core_instance and related context structs was not consumed after initial setup and represents unnecessary coupling between the core layer and project-specific identifiers. [How] - Remove project_id field from dml2_core_instance - Remove the corresponding assignment in dml2_core_create Reviewed-by: Austin Zheng Signed-off-by: Wenjing Liu Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c | 2 -- .../amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h | 2 -- .../amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h | 1 - 3 files changed, 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c index 67e307fa4310..9f1222f5a835 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c @@ -15,8 +15,6 @@ bool dml2_core_create(enum dml2_project_id project_id, struct dml2_core_instance memset(out, 0, sizeof(struct dml2_core_instance)); - out->project_id = project_id; - switch (project_id) { case dml2_project_dcn4x_stage1: result = 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 11e295253f72..e9f970794488 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 @@ -2329,7 +2329,6 @@ struct dml2_core_calcs_mode_support_ex { const struct dml2_display_cfg *in_display_cfg; const struct dml2_mcg_min_clock_table *min_clk_table; int min_clk_index; - enum dml2_project_id project_id; //unsigned int in_state_index; struct dml2_core_internal_mode_support_info *out_evaluation_info; }; @@ -2342,7 +2341,6 @@ struct dml2_core_calcs_mode_programming_ex { const struct dml2_mcg_min_clock_table *min_clk_table; const struct core_display_cfg_support_info *cfg_support_info; int min_clk_index; - enum dml2_project_id project_id; struct dml2_display_cfg_programming *programming; }; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h index d328d92240b4..3ae817ea2aad 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h @@ -489,7 +489,6 @@ struct dml2_core_scratch { }; struct dml2_core_instance { - enum dml2_project_id project_id; struct dml2_mcg_min_clock_table *minimum_clock_table; struct dml2_core_internal_state_inputs inputs; struct dml2_core_internal_state_intermediates intermediates; -- cgit v1.2.3 From b008c67efb36b102988ea16d5019c8364170264c Mon Sep 17 00:00:00 2001 From: Rafal Ostrowski Date: Fri, 22 May 2026 08:02:16 +0200 Subject: drm/amd/display: Introduce dc_plane_cm and migrate surface update color path [Why] Begin convergence with upstream Color Manager refactor (fda768acb2a1 "drm/amd/display: Sync dcn42 with DC 3.2.373") by consolidating fragmented per-plane CM state (shaper, 3DLUT, blend, CM2) into a single dc_plane_cm structure shared by dc_plane_state and dc_surface_update. Legacy fields are gated behind TRIM_CM2 so that it keeps compatibility with other repositories. [How] Refactored to use newer structures. No functional behavior change intended. Under !TRIM_CM2 the legacy fields are still populated for compatibility with other repositories. v2: squash in conflicting types fix Reviewed-by: Dillon Varone Signed-off-by: Rafal Ostrowski Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 69 +++-- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 8 +- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 64 ++--- drivers/gpu/drm/amd/display/dc/core/dc.c | 130 ++++++--- drivers/gpu/drm/amd/display/dc/core/dc_surface.c | 44 ++- drivers/gpu/drm/amd/display/dc/dc.h | 72 +++-- drivers/gpu/drm/amd/display/dc/dc_types.h | 66 +++-- .../drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c | 2 +- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 20 +- .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 10 +- .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 29 +- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 172 ++++++----- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 2 +- .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 316 ++++++++++----------- .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h | 5 +- .../drm/amd/display/dc/hwss/hw_sequencer_private.h | 3 +- drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h | 2 +- 18 files changed, 588 insertions(+), 430 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 f34f4e65e933..7a46c9e56d87 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -10321,9 +10321,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func; bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; bundle->surface_updates[planes_count].hdr_mult = dc_plane->hdr_mult; - bundle->surface_updates[planes_count].func_shaper = &dc_plane->in_shaper_func; - bundle->surface_updates[planes_count].lut3d_func = &dc_plane->lut3d_func; - bundle->surface_updates[planes_count].blend_tf = &dc_plane->blend_tf; + bundle->surface_updates[planes_count].cm = &dc_plane->cm; } amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state, 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 86086d10c543..69a3783e5223 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 @@ -1051,26 +1051,28 @@ EXPORT_IF_KUNIT(__drm_3dlut32_to_dc_3dlut); /* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream * @drm_lut3d: user 3D LUT * @drm_lut3d_size: size of 3D LUT - * @lut3d: DC 3D LUT + * @cm: DC Color Manager (includes 3D LUT) * * Map user 3D LUT data to DC 3D LUT and all necessary bits to program it * on DCN accordingly. */ STATIC_IFN_KUNIT void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, uint32_t drm_lut3d_size, - struct dc_3dlut *lut) + struct dc_plane_cm *cm) { if (!drm_lut3d_size) { - lut->state.bits.initialized = 0; + cm->lut3d_func.state.bits.initialized = 0; + cm->flags.bits.lut3d_enable = 0; } else { /* Stride and bit depth are not programmable by API yet. * Therefore, only supports 17x17x17 3D LUT (12-bit). */ - lut->lut_3d.use_tetrahedral_9 = false; - lut->lut_3d.use_12bits = true; - lut->state.bits.initialized = 1; - __drm_3dlut_to_dc_3dlut(drm_lut3d, drm_lut3d_size, &lut->lut_3d, - lut->lut_3d.use_tetrahedral_9, + cm->lut3d_func.lut_3d.use_tetrahedral_9 = false; + cm->lut3d_func.lut_3d.use_12bits = true; + cm->lut3d_func.state.bits.initialized = 1; + cm->flags.bits.lut3d_enable = 1; + __drm_3dlut_to_dc_3dlut(drm_lut3d, drm_lut3d_size, &cm->lut3d_func.lut_3d, + cm->lut3d_func.lut_3d.use_tetrahedral_9, MAX_COLOR_3DLUT_BITDEPTH); } } @@ -1080,7 +1082,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *sha bool has_rom, enum dc_transfer_func_predefined tf, uint32_t shaper_size, - struct dc_transfer_func *func_shaper) + struct dc_plane_cm *cm) { int ret = 0; @@ -1089,10 +1091,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *sha * If user shaper LUT is set, we assume a linear color space * (linearized by degamma 1D LUT or not). */ - __set_tf_distributed_points(func_shaper, tf); - ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom); + __set_tf_distributed_points(&cm->shaper_func, tf); + cm->flags.bits.shaper_enable = 1; + + ret = __set_output_tf(&cm->shaper_func, shaper_lut, shaper_size, has_rom); } else { - __set_tf_bypass(func_shaper); + __set_tf_bypass(&cm->shaper_func); + cm->flags.bits.shaper_enable = 0; } return ret; @@ -1103,7 +1108,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blen bool has_rom, enum dc_transfer_func_predefined tf, uint32_t blend_size, - struct dc_transfer_func *func_blend) + struct dc_plane_cm *cm) { int ret = 0; @@ -1115,10 +1120,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blen * module to fill the parameters that will be translated to HW * points. */ - __set_tf_distributed_points(func_blend, tf); - ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size); + __set_tf_distributed_points(&cm->blend_func, tf); + cm->flags.bits.blend_enable = 1; + + ret = __set_input_tf(NULL, &cm->blend_func, blend_lut, blend_size); } else { - __set_tf_bypass(func_blend); + __set_tf_bypass(&cm->blend_func); + cm->flags.bits.blend_enable = 0; } return ret; @@ -1635,7 +1643,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; struct drm_atomic_commit *state = plane_state->state; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; - struct dc_transfer_func *tf = &dc_plane_state->in_shaper_func; + struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func; const struct drm_color_lut32 *shaper_lut; struct drm_device *dev = colorop->dev; bool enabled = false; @@ -1696,8 +1704,12 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, } } - if (!enabled) + if (!enabled) { tf->type = TF_TYPE_BYPASS; + dc_plane_state->cm.flags.bits.shaper_enable = 0; + } else { + dc_plane_state->cm.flags.bits.shaper_enable = 1; + } return 0; } @@ -1741,7 +1753,7 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, { struct drm_colorop *old_colorop; struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; - struct dc_transfer_func *tf = &dc_plane_state->in_shaper_func; + struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func; struct drm_atomic_commit *state = plane_state->state; const struct amdgpu_device *adev = drm_to_adev(colorop->dev); bool has_3dlut = adev->dm.dc->caps.color.dpp.hw_3d_lut || adev->dm.dc->caps.color.mpc.preblend; @@ -1769,13 +1781,15 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, drm_dbg(dev, "3D LUT colorop with ID: %d\n", colorop->base.id); lut3d = __extract_blob_lut32(colorop_state->data, &lut3d_size); lut3d_size = lut3d != NULL ? lut3d_size : 0; - ret = __set_colorop_3dlut(lut3d, lut3d_size, &dc_plane_state->lut3d_func); + ret = __set_colorop_3dlut(lut3d, lut3d_size, &dc_plane_state->cm.lut3d_func); if (ret) { drm_dbg(dev, "3D LUT colorop with ID: %d has LUT size = %d\n", colorop->base.id, lut3d_size); return ret; } + dc_plane_state->cm.flags.bits.lut3d_enable = 1; + /* 3D LUT requires shaper. If shaper colorop is bypassed, enable shaper curve * with TRANSFER_FUNCTION_LINEAR */ @@ -1785,6 +1799,8 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; ret = __set_output_tf_32(tf, NULL, 0, false); } + } else { + dc_plane_state->cm.flags.bits.lut3d_enable = 0; } return ret; @@ -1799,12 +1815,14 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; struct drm_atomic_commit *state = plane_state->state; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; - struct dc_transfer_func *tf = &dc_plane_state->blend_tf; + struct dc_transfer_func *tf = &dc_plane_state->cm.blend_func; const struct drm_color_lut32 *blend_lut = NULL; struct drm_device *dev = colorop->dev; uint32_t blend_size = 0; int i = 0; + dc_plane_state->cm.flags.bits.blend_enable = 0; + /* 1D Curve - BLND TF */ old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { @@ -1821,6 +1839,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + dc_plane_state->cm.flags.bits.blend_enable = 1; __set_input_tf_32(NULL, tf, blend_lut, blend_size); } @@ -1846,6 +1865,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + dc_plane_state->cm.flags.bits.blend_enable = 1; blend_lut = __extract_blob_lut32(colorop_state->data, &blend_size); blend_size = blend_lut != NULL ? blend_size : 0; @@ -1876,11 +1896,11 @@ amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state, lut3d = __extract_blob_lut(dm_plane_state->lut3d, &lut3d_size); lut3d_size = lut3d != NULL ? lut3d_size : 0; - amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, &dc_plane_state->lut3d_func); + amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, &dc_plane_state->cm); ret = amdgpu_dm_atomic_shaper_lut(shaper_lut, false, amdgpu_tf_to_dc_tf(shaper_tf), shaper_size, - &dc_plane_state->in_shaper_func); + &dc_plane_state->cm); if (ret) { drm_dbg_kms(plane_state->plane->dev, "setting plane %d shaper LUT failed.\n", @@ -1895,7 +1915,8 @@ amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state, ret = amdgpu_dm_atomic_blend_lut(blend_lut, false, amdgpu_tf_to_dc_tf(blend_tf), - blend_size, &dc_plane_state->blend_tf); + blend_size, &dc_plane_state->cm); + if (ret) { drm_dbg_kms(plane_state->plane->dev, "setting plane %d gamma lut failed.\n", 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 e4f53b7bc753..8dbbcb3ab156 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 @@ -87,10 +87,10 @@ void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, struct tetrahedral_params *params, bool use_tetrahedral_9, int bit_depth); -struct dc_3dlut; +struct dc_plane_cm; void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, uint32_t drm_lut3d_size, - struct dc_3dlut *lut); + struct dc_plane_cm *cm); int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d, uint32_t drm_lut3d_size, struct dc_3dlut *lut); @@ -105,12 +105,12 @@ int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, bool has_rom, enum dc_transfer_func_predefined tf, uint32_t shaper_size, - struct dc_transfer_func *func_shaper); + struct dc_plane_cm *cm); int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, bool has_rom, enum dc_transfer_func_predefined tf, uint32_t blend_size, - struct dc_transfer_func *func_blend); + 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); #endif 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 f943361b70e8..d64c7da20f2c 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 @@ -1159,19 +1159,19 @@ static void dm_test_verify_lut_sizes_invalid_degamma_valid_gamma(struct kunit *t */ static void dm_test_atomic_lut3d_zero_size(struct kunit *test) { - struct dc_3dlut *lut; + struct dc_plane_cm *cm; u32 initialized; - lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, lut); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); /* Pre-set initialized so we can confirm it is cleared */ - lut->state.bits.initialized = 1; + cm->lut3d_func.state.bits.initialized = 1; - amdgpu_dm_atomic_lut3d(NULL, 0, lut); + amdgpu_dm_atomic_lut3d(NULL, 0, cm); /* Copy bit-field: typeof cannot be applied to a bit-field */ - initialized = lut->state.bits.initialized; + initialized = cm->lut3d_func.state.bits.initialized; KUNIT_EXPECT_EQ(test, initialized, 0U); } @@ -1183,22 +1183,22 @@ static void dm_test_atomic_lut3d_nonzero_state_bits(struct kunit *test) { const uint32_t lut3d_size = 5; struct drm_color_lut *lut_data; - struct dc_3dlut *lut; + struct dc_plane_cm *cm; u32 initialized; lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, lut_data); - lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, lut); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); - amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut); + amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, cm); /* Copy bit-field: typeof cannot be applied to a bit-field */ - initialized = lut->state.bits.initialized; + initialized = cm->lut3d_func.state.bits.initialized; KUNIT_EXPECT_EQ(test, initialized, 1U); - KUNIT_EXPECT_FALSE(test, lut->lut_3d.use_tetrahedral_9); - KUNIT_EXPECT_TRUE(test, lut->lut_3d.use_12bits); + KUNIT_EXPECT_FALSE(test, cm->lut3d_func.lut_3d.use_tetrahedral_9); + KUNIT_EXPECT_TRUE(test, cm->lut3d_func.lut_3d.use_12bits); } /** @@ -1209,29 +1209,29 @@ static void dm_test_atomic_lut3d_data_forwarded(struct kunit *test) { const uint32_t lut3d_size = 5; struct drm_color_lut *lut_data; - struct dc_3dlut *lut; + struct dc_plane_cm *cm; lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, lut_data); - lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, lut); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); lut_data[0].red = 0xFFFF; lut_data[0].green = 0x8000; lut_data[0].blue = 0x4000; - amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut); + amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, cm); /* * use_tetrahedral_9 == false → data goes into tetrahedral_17. * lut[0] maps to lut0[0] (first element of the first group). */ - KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].red, + KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].red, drm_color_lut_extract(0xFFFF, MAX_COLOR_3DLUT_BITDEPTH)); - KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].green, + KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].green, drm_color_lut_extract(0x8000, MAX_COLOR_3DLUT_BITDEPTH)); - KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].blue, + KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].blue, drm_color_lut_extract(0x4000, MAX_COLOR_3DLUT_BITDEPTH)); } @@ -1398,19 +1398,19 @@ static void dm_test_set_atomic_regamma_bypass(struct kunit *test) */ static void dm_test_atomic_shaper_lut_bypass(struct kunit *test) { - struct dc_transfer_func *func_shaper; + struct dc_plane_cm *cm; - func_shaper = kunit_kzalloc(test, sizeof(*func_shaper), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, func_shaper); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); /* size=0 and tf=LINEAR: must take the bypass branch */ KUNIT_EXPECT_EQ(test, amdgpu_dm_atomic_shaper_lut(NULL, false, TRANSFER_FUNCTION_LINEAR, - 0, func_shaper), + 0, cm), 0); - KUNIT_EXPECT_EQ(test, (int)func_shaper->type, (int)TF_TYPE_BYPASS); - KUNIT_EXPECT_EQ(test, (int)func_shaper->tf, (int)TRANSFER_FUNCTION_LINEAR); + KUNIT_EXPECT_EQ(test, (int)cm->shaper_func.type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)cm->shaper_func.tf, (int)TRANSFER_FUNCTION_LINEAR); } /** @@ -1419,19 +1419,19 @@ static void dm_test_atomic_shaper_lut_bypass(struct kunit *test) */ static void dm_test_atomic_blend_lut_bypass(struct kunit *test) { - struct dc_transfer_func *func_blend; + struct dc_plane_cm *cm; - func_blend = kunit_kzalloc(test, sizeof(*func_blend), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, func_blend); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); /* size=0 and tf=LINEAR: must take the bypass branch */ KUNIT_EXPECT_EQ(test, amdgpu_dm_atomic_blend_lut(NULL, false, TRANSFER_FUNCTION_LINEAR, - 0, func_blend), + 0, cm), 0); - KUNIT_EXPECT_EQ(test, (int)func_blend->type, (int)TF_TYPE_BYPASS); - KUNIT_EXPECT_EQ(test, (int)func_blend->tf, (int)TRANSFER_FUNCTION_LINEAR); + KUNIT_EXPECT_EQ(test, (int)cm->blend_func.type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)cm->blend_func.tf, (int)TRANSFER_FUNCTION_LINEAR); } /* ---- Tests for __set_colorop_in_tf_1d_curve ---- */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index bcdbf3471039..4220481d3960 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -2964,16 +2964,28 @@ static struct surface_update_descriptor det_surface_update( elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } - if (u->blend_tf || (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) { + if ((u->cm && u->cm->flags.bits.blend_enable) || + (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) { update_flags->bits.gamma_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } - if (u->lut3d_func || u->func_shaper) { + if (u->cm && (u->cm->flags.bits.lut3d_enable || u->cm->flags.bits.shaper_enable)) { update_flags->bits.lut_3d = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } + if (u->cm && u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable && + u->cm->flags.bits.lut3d_enable && u->surface->cm.flags.bits.lut3d_enable) { + /* Toggling 3DLUT loading between DMA and Host is illegal */ + BREAK_TO_DEBUGGER(); + } + + if (u->cm && u->cm->flags.bits.lut3d_enable && !u->cm->flags.bits.lut3d_dma_enable) { + /* Host loading 3DLUT requires full update but only stream lock */ + elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_STREAM); + } + if (u->hdr_mult.value) if (u->hdr_mult.value != u->surface->hdr_mult.value) { // TODO: Should be fast? @@ -2992,17 +3004,30 @@ static struct surface_update_descriptor det_surface_update( update_flags->bits.cm_hist_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } - if (u->cm2_params) { - if (u->cm2_params->component_settings.shaper_3dlut_setting != u->surface->mcm_shaper_3dlut_setting - || u->cm2_params->component_settings.lut1d_enable != u->surface->mcm_lut1d_enable - || u->cm2_params->cm2_luts.lut3d_data.lut3d_src != u->surface->mcm_luts.lut3d_data.lut3d_src) { + + if (u->cm) { + const union dc_plane_cm_flags blend_only_flags = { + .bits = { + .blend_enable = 1, + } + }; + + if (u->cm->flags.bits.shaper_enable != u->surface->cm.flags.bits.shaper_enable + || u->cm->flags.bits.blend_enable != u->surface->cm.flags.bits.blend_enable + || u->cm->flags.bits.lut3d_enable != u->surface->cm.flags.bits.lut3d_enable + || u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable) { update_flags->bits.mcm_transfer_function_enable_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } + + if ((u->cm->flags.all != blend_only_flags.all && u->cm->flags.all != 0) || + (u->surface->cm.flags.all != blend_only_flags.all && u->surface->cm.flags.all != 0)) { + elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); + } } if (update_flags->bits.lut_3d && - u->surface->mcm_luts.lut3d_data.lut3d_src != DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) { + !u->surface->cm.flags.bits.lut3d_dma_enable) { elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } @@ -3304,24 +3329,55 @@ static void copy_surface_update_to_plane( sizeof(struct dc_transfer_func_distributed_points)); } - if (srf_update->cm2_params) { - surface->mcm_shaper_3dlut_setting = srf_update->cm2_params->component_settings.shaper_3dlut_setting; - surface->mcm_lut1d_enable = srf_update->cm2_params->component_settings.lut1d_enable; - surface->mcm_luts = srf_update->cm2_params->cm2_luts; - } - - if (srf_update->func_shaper) { - memcpy(&surface->in_shaper_func, srf_update->func_shaper, - sizeof(surface->in_shaper_func)); + /* Shaper, 3DLUT, 1DLUT */ + if (srf_update->cm) { + struct kref refcount = surface->cm.refcount; + + memcpy(&surface->cm, srf_update->cm, sizeof(surface->cm)); + surface->cm.refcount = refcount; + +#ifndef TRIM_CM2 + /* Populate mcm_luts from cm for legacy consumers (dml2, hwseq) */ + surface->mcm_luts.lut1d_func = &surface->cm.blend_func; + surface->mcm_luts.shaper = &surface->cm.shaper_func; + if (srf_update->cm->flags.bits.lut3d_dma_enable) { + surface->mcm_luts.lut3d_data.lut3d_src = DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM; + surface->mcm_luts.lut3d_data.gpu_mem_params.addr = surface->cm.lut3d_dma.addr; + surface->mcm_luts.lut3d_data.gpu_mem_params.layout = + (surface->cm.lut3d_dma.swizzle == CM_LUT_3D_SWIZZLE_LINEAR_RGB) ? + DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB : + (surface->cm.lut3d_dma.swizzle == CM_LUT_3D_SWIZZLE_LINEAR_BGR) ? + DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR : + DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR; + surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.format = + (surface->cm.lut3d_dma.format == CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB) ? + DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB : + (surface->cm.lut3d_dma.format == CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB) ? + DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB : + DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10; + surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias = + surface->cm.lut3d_dma.bias; + surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale = + surface->cm.lut3d_dma.scale; + surface->mcm_luts.lut3d_data.gpu_mem_params.component_order = + DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA; + surface->mcm_luts.lut3d_data.gpu_mem_params.size = DC_CM2_GPU_MEM_SIZE_TRANSFORMED; + surface->mcm_luts.lut3d_data.mpc_3dlut_enable = (srf_update->cm->flags.bits.lut3d_enable != 0); + } else { + surface->mcm_luts.lut3d_data.lut3d_src = DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM; + surface->mcm_luts.lut3d_data.lut3d_func = &surface->cm.lut3d_func; + } - if (surface->mcm_shaper_3dlut_setting >= DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER) - surface->mcm_luts.shaper = &surface->in_shaper_func; + if (srf_update->cm->flags.bits.shaper_enable && + srf_update->cm->flags.bits.lut3d_enable) + surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT; + else if (srf_update->cm->flags.bits.shaper_enable) + surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER; + else + surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL; +#endif /* TRIM_CM2 */ } - if (srf_update->lut3d_func) - memcpy(&surface->lut3d_func, srf_update->lut3d_func, - sizeof(surface->lut3d_func)); - if (srf_update->hdr_mult.value) surface->hdr_mult = srf_update->hdr_mult; @@ -3330,15 +3386,10 @@ static void copy_surface_update_to_plane( surface->sdr_white_level_nits = srf_update->sdr_white_level_nits; - if (srf_update->blend_tf) { - memcpy(&surface->blend_tf, srf_update->blend_tf, - sizeof(surface->blend_tf)); - - if (surface->mcm_lut1d_enable) - surface->mcm_luts.lut1d_func = &surface->blend_tf; - } - - if (srf_update->cm2_params || srf_update->blend_tf) + if (srf_update->cm && + (srf_update->cm->flags.bits.blend_enable || + srf_update->cm->flags.bits.shaper_enable || + srf_update->cm->flags.bits.lut3d_enable)) surface->lut_bank_a = !surface->lut_bank_a; if (srf_update->input_csc_color_matrix) @@ -5073,11 +5124,9 @@ static void commit_planes_for_stream(struct dc *dc, if (!should_update_pipe_for_plane(context, pipe_ctx, plane_state)) continue; - if (srf_updates[i].cm2_params && - srf_updates[i].cm2_params->cm2_luts.lut3d_data.lut3d_src == - DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM && - srf_updates[i].cm2_params->component_settings.shaper_3dlut_setting == - DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT && + if (srf_updates[i].cm && + srf_updates[i].cm->flags.bits.lut3d_enable && + srf_updates[i].cm->flags.bits.lut3d_dma_enable && dc->hwss.trigger_3dlut_dma_load) dc->hwss.trigger_3dlut_dma_load(dc, pipe_ctx); @@ -5792,14 +5841,9 @@ static bool full_update_required( (srf_updates[i].sdr_white_level_nits && srf_updates[i].sdr_white_level_nits != srf_updates->surface->sdr_white_level_nits) || srf_updates[i].in_transfer_func || - srf_updates[i].func_shaper || - srf_updates[i].lut3d_func || srf_updates[i].surface->force_full_update || (srf_updates[i].flip_addr && - srf_updates[i].flip_addr->address.tmz_surface != srf_updates[i].surface->address.tmz_surface) || - (srf_updates[i].cm2_params && - (srf_updates[i].cm2_params->component_settings.shaper_3dlut_setting != srf_updates[i].surface->mcm_shaper_3dlut_setting || - srf_updates[i].cm2_params->component_settings.lut1d_enable != srf_updates[i].surface->mcm_lut1d_enable)))) + srf_updates[i].flip_addr->address.tmz_surface != srf_updates[i].surface->address.tmz_surface))) return true; } @@ -7542,7 +7586,7 @@ bool dc_capture_register_software_state(struct dc *dc, struct dc_register_softwa struct dc_plane_state *plane_state = pipe_ctx->plane_state; /* MPCC blending tree and mode control - capture actual blend configuration */ - state->mpc.mpcc_mode[i] = (plane_state->blend_tf.type != TF_TYPE_BYPASS) ? 1 : 0; + state->mpc.mpcc_mode[i] = (plane_state->cm.blend_func.type != TF_TYPE_BYPASS) ? 1 : 0; state->mpc.mpcc_alpha_blend_mode[i] = plane_state->per_pixel_alpha ? 1 : 0; state->mpc.mpcc_alpha_multiplied_mode[i] = plane_state->pre_multiplied_alpha ? 1 : 0; state->mpc.mpcc_blnd_active_overlap_only[i] = 0; /* Default - no overlap restriction */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c index 72845fc788f3..88e825a6582c 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c @@ -45,14 +45,13 @@ void dc_plane_construct(struct dc_context *ctx, struct dc_plane_state *plane_sta plane_state->in_transfer_func.type = TF_TYPE_BYPASS; - plane_state->in_shaper_func.type = TF_TYPE_BYPASS; - - plane_state->lut3d_func.state.raw = 0; - - plane_state->blend_tf.type = TF_TYPE_BYPASS; - plane_state->pre_multiplied_alpha = true; + /* CM */ + plane_state->cm.shaper_func.type = TF_TYPE_BYPASS; + plane_state->cm.blend_func.type = TF_TYPE_BYPASS; + plane_state->cm.lut3d_func.state.raw = 0; + plane_state->cm.flags.all = 0; } void dc_plane_destruct(struct dc_plane_state *plane_state) @@ -282,6 +281,39 @@ void dc_3dlut_func_retain(struct dc_3dlut *lut) kref_get(&lut->refcount); } +static void dc_plane_cm_free(struct kref *kref) +{ + struct dc_plane_cm *cm = container_of(kref, struct dc_plane_cm, refcount); + + kvfree(cm); +} + +struct dc_plane_cm *dc_plane_cm_create(void) +{ + struct dc_plane_cm *cm = kvzalloc(sizeof(*cm), GFP_KERNEL); + + if (cm == NULL) + goto alloc_fail; + + kref_init(&cm->refcount); + + return cm; + +alloc_fail: + return NULL; + +} + +void dc_plane_cm_release(struct dc_plane_cm *cm) +{ + kref_put(&cm->refcount, dc_plane_cm_free); +} + +void dc_plane_cm_retain(struct dc_plane_cm *cm) +{ + kref_get(&cm->refcount); +} + void dc_plane_force_dcc_and_tiling_disable(struct dc_plane_state *plane_state, bool clear_tiling) { diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index b8ac462a676a..2a47d7ddf53b 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1486,6 +1486,47 @@ struct dc_3dlut { struct fixed31_32 hdr_multiplier; union dc_3dlut_state state; }; + +/* 3DLUT DMA (Fast Load) params */ +struct dc_3dlut_dma { + struct dc_plane_address addr; + enum dc_cm_lut_swizzle swizzle; + enum dc_cm_lut_pixel_format format; + uint16_t bias; /* FP1.5.10 */ + uint16_t scale; /* FP1.5.10 */ + enum dc_cm_lut_size size; +}; + +/* color manager */ +union dc_plane_cm_flags { + unsigned int all; + struct { + unsigned int shaper_enable : 1; + unsigned int lut3d_enable : 1; + 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; +}; + +struct dc_plane_cm { + struct kref refcount; + struct dc_transfer_func shaper_func; + union { + struct dc_3dlut lut3d_func; + struct dc_3dlut_dma lut3d_dma; + }; + struct dc_transfer_func blend_func; + union dc_plane_cm_flags flags; +}; + /* * This structure is filled in by dc_surface_get_status and contains * the last requested address and the currently active address so the called @@ -1564,14 +1605,22 @@ struct dc_plane_state { struct fixed31_32 hdr_mult; struct colorspace_transform gamut_remap_matrix; + enum dc_color_space color_space; + +#ifndef TRIM_CM2 // TODO: No longer used, remove struct dc_hdr_static_metadata hdr_static_ctx; - enum dc_color_space color_space; - struct dc_3dlut lut3d_func; struct dc_transfer_func in_shaper_func; struct dc_transfer_func blend_tf; + enum dc_cm2_shaper_3dlut_setting mcm_shaper_3dlut_setting; + bool mcm_lut1d_enable; + struct dc_cm2_func_luts mcm_luts; +#endif /* TRIM_CM2 */ + bool lut_bank_a; + enum mpcc_movable_cm_location mcm_location; + struct dc_plane_cm cm; struct dc_transfer_func *gamcor_tf; enum surface_pixel_format format; @@ -1608,11 +1657,6 @@ struct dc_plane_state { bool is_statically_allocated; enum chroma_cositing cositing; - enum dc_cm2_shaper_3dlut_setting mcm_shaper_3dlut_setting; - bool mcm_lut1d_enable; - struct dc_cm2_func_luts mcm_luts; - bool lut_bank_a; - enum mpcc_movable_cm_location mcm_location; struct dc_csc_transform cursor_csc_color_matrix; bool adaptive_sharpness_en; int adaptive_sharpness_policy; @@ -1976,17 +2020,7 @@ struct dc_surface_update { const struct dc_csc_transform *input_csc_color_matrix; const struct fixed31_32 *coeff_reduction_factor; - const struct dc_transfer_func *func_shaper; - const struct dc_3dlut *lut3d_func; - const struct dc_transfer_func *blend_tf; const struct colorspace_transform *gamut_remap_matrix; - /* - * Color Transformations for pre-blend MCM (Shaper, 3DLUT, 1DLUT) - * - * change cm2_params.component_settings: Full update - * change cm2_params.cm2_luts: Fast update - */ - const struct dc_cm2_parameters *cm2_params; const struct dc_plane_cm *cm; const struct dc_csc_transform *cursor_csc_color_matrix; unsigned int sdr_white_level_nits; @@ -2032,6 +2066,10 @@ struct dc_3dlut *dc_create_3dlut_func(void); void dc_3dlut_func_release(struct dc_3dlut *lut); void dc_3dlut_func_retain(struct dc_3dlut *lut); +struct dc_plane_cm *dc_plane_cm_create(void); +void dc_plane_cm_release(struct dc_plane_cm *cm); +void dc_plane_cm_retain(struct dc_plane_cm *cm); + void dc_post_update_surfaces_to_stream( struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 4ed1efa17270..db6a89d938b6 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -1397,6 +1397,39 @@ enum dc_hpd_enable_select { HPD_EN_FOR_SECONDARY_EDP_ONLY, }; +enum dc_cm_lut_swizzle { + CM_LUT_3D_SWIZZLE_LINEAR_RGB, + CM_LUT_3D_SWIZZLE_LINEAR_BGR, + CM_LUT_1D_PACKED_LINEAR +}; + +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 enum dc_cm2_shaper_3dlut_setting { DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL, DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER, @@ -1421,6 +1454,16 @@ enum dc_cm2_gpu_mem_format { DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10 }; +enum dc_cm2_gpu_mem_size { + DC_CM2_GPU_MEM_SIZE_171717, + DC_CM2_GPU_MEM_SIZE_333333, + DC_CM2_GPU_MEM_SIZE_454545, + DC_CM2_GPU_MEM_SIZE_656565, + DC_CM2_GPU_MEM_SIZE_TRANSFORMED, +}; +#endif /* TRIM_CM2 */ + +#ifndef TRIM_CM2 struct dc_cm2_gpu_mem_format_parameters { enum dc_cm2_gpu_mem_format format; union { @@ -1432,14 +1475,6 @@ struct dc_cm2_gpu_mem_format_parameters { }; }; -enum dc_cm2_gpu_mem_size { - DC_CM2_GPU_MEM_SIZE_171717, - DC_CM2_GPU_MEM_SIZE_333333, - DC_CM2_GPU_MEM_SIZE_454545, - DC_CM2_GPU_MEM_SIZE_656565, - DC_CM2_GPU_MEM_SIZE_TRANSFORMED, -}; - struct dc_cm2_gpu_mem_parameters { struct dc_plane_address addr; enum dc_cm2_gpu_mem_layout layout; @@ -1448,17 +1483,16 @@ struct dc_cm2_gpu_mem_parameters { enum dc_cm2_gpu_mem_size size; uint16_t bit_depth; }; +#endif /* TRIM_CM2 */ +#ifndef TRIM_CM2 enum dc_cm2_transfer_func_source { DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM, DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM }; +#endif /* TRIM_CM2 */ -struct dc_cm2_component_settings { - enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting; - bool lut1d_enable; -}; - +#ifndef TRIM_CM2 /* * All pointers in this struct must remain valid for as long as the 3DLUTs are used */ @@ -1478,11 +1512,7 @@ struct dc_cm2_func_luts { } lut3d_data; const struct dc_transfer_func *lut1d_func; }; - -struct dc_cm2_parameters { - struct dc_cm2_component_settings component_settings; - struct dc_cm2_func_luts cm2_luts; -}; +#endif /* TRIM_CM2 */ enum mall_stream_type { SUBVP_NONE, // subvp not in use diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c index 302515128358..9965cf572354 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c @@ -136,7 +136,7 @@ void hubp401_program_3dlut_fl_config( uint32_t mpc_width = {(cfg->width == 17) ? 0 : 1}; uint32_t width = {cfg->width}; - if (cfg->layout == DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR) + if (cfg->layout == CM_LUT_1D_PACKED_LINEAR) width = (cfg->width == 17) ? 4916 : 35940; REG_UPDATE_2(_3DLUT_FL_CONFIG, 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 e6a8206f8ce0..50d039b3fb43 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 @@ -1066,11 +1066,11 @@ bool dcn20_set_blend_lut( bool result = true; const struct pwl_params *blend_lut = NULL; - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - blend_lut = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + blend_lut = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, + &plane_state->cm.blend_func, &dpp_base->regamma_params, false); blend_lut = &dpp_base->regamma_params; } @@ -1086,19 +1086,19 @@ bool dcn20_set_shaper_3dlut( bool result = true; const struct pwl_params *shaper_lut = NULL; - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - shaper_lut = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + shaper_lut = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, + &plane_state->cm.shaper_func, &dpp_base->shaper_params, true); shaper_lut = &dpp_base->shaper_params; } result = dpp_base->funcs->dpp_program_shaper_lut(dpp_base, shaper_lut); - if (plane_state->lut3d_func.state.bits.initialized == 1) + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) result = dpp_base->funcs->dpp_program_3dlut(dpp_base, - &plane_state->lut3d_func.lut_3d); + &plane_state->cm.lut3d_func.lut_3d); else result = dpp_base->funcs->dpp_program_3dlut(dpp_base, NULL); 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 a7c85a2302ab..aed9d06ec538 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 @@ -239,11 +239,13 @@ bool dcn30_set_blend_lut( bool result = true; const struct pwl_params *blend_lut = NULL; - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - blend_lut = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + blend_lut = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, &dpp_base->regamma_params, false); + &plane_state->cm.blend_func, + &dpp_base->regamma_params, + false); if (!result) return result; 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 a3242e7521a4..34cbd90b2283 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 @@ -490,12 +490,14 @@ bool dcn32_set_mcm_luts( const struct pwl_params *lut_params = NULL; // 1D LUT - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - lut_params = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { - result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, - &dpp_base->regamma_params, false); + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + result = cm3_helper_translate_curve_to_hw_format( + plane_state->ctx, + &plane_state->cm.blend_func, + &dpp_base->regamma_params, + false); if (!result) return result; @@ -505,21 +507,22 @@ bool dcn32_set_mcm_luts( lut_params = NULL; // Shaper - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - lut_params = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, - &dpp_base->shaper_params, true); + &plane_state->cm.shaper_func, + &dpp_base->shaper_params, + true); lut_params = rval ? &dpp_base->shaper_params : NULL; } mpc->funcs->program_shaper(mpc, lut_params, mpcc_id); // 3D - if (plane_state->lut3d_func.state.bits.initialized == 1) - result = mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id); + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) + result = mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id); else result = mpc->funcs->program_3dlut(mpc, NULL, mpcc_id); 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 96815a92a629..49efd1f11c9a 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 @@ -410,37 +410,27 @@ static void dcn401_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ct enum MCM_LUT_XABLE *lut3d_xable, enum MCM_LUT_XABLE *lut1d_xable) { - enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL; - bool lut1d_enable = false; struct mpc *mpc = dc->res_pool->mpc; int mpcc_id = pipe_ctx->plane_res.hubp->inst; if (!pipe_ctx->plane_state) return; - shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting; - lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable; + mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; - *lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE; - - switch (shaper_3dlut_setting) { - case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL: - *lut3d_xable = *shaper_xable = MCM_LUT_DISABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER: - *lut3d_xable = MCM_LUT_DISABLE; - *shaper_xable = MCM_LUT_ENABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT: - *lut3d_xable = *shaper_xable = MCM_LUT_ENABLE; - break; - } + *lut1d_xable = pipe_ctx->plane_state->cm.flags.bits.blend_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *shaper_xable = pipe_ctx->plane_state->cm.flags.bits.shaper_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *lut3d_xable = (pipe_ctx->plane_state->cm.flags.bits.shaper_enable && + pipe_ctx->plane_state->cm.flags.bits.lut3d_enable) ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; } void dcn401_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a) { struct dpp *dpp_base = pipe_ctx->plane_res.dpp; @@ -448,14 +438,17 @@ void dcn401_populate_mcm_luts(struct dc *dc, int mpcc_id = hubp->inst; struct mpc *mpc = dc->res_pool->mpc; union mcm_lut_params m_lut_params; - enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src; + const bool lut3d_dma = !!cm->flags.bits.lut3d_dma_enable; enum hubp_3dlut_fl_format format = 0; enum hubp_3dlut_fl_mode mode; - enum hubp_3dlut_fl_width width = 0; + /* Width was previously hard-coded to TRANSFORMED via local_mcm build, + * preserve identical behavior. + */ + enum hubp_3dlut_fl_width width = hubp_3dlut_fl_width_transformed; enum hubp_3dlut_fl_addressing_mode addr_mode; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r = 0; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r; enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE; @@ -464,13 +457,13 @@ void dcn401_populate_mcm_luts(struct dc *dc, dcn401_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable); /* 1D LUT */ - if (mcm_luts.lut1d_func) { + { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.lut1d_func->pwl; - else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->blend_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->blend_func.pwl; + else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.lut1d_func, + &cm->blend_func, &dpp_base->regamma_params, false); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -483,14 +476,14 @@ void dcn401_populate_mcm_luts(struct dc *dc, } /* Shaper */ - if (mcm_luts.shaper && mcm_luts.lut3d_data.mpc_3dlut_enable) { + if (cm->flags.bits.lut3d_enable) { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.shaper->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.shaper->pwl; - else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->shaper_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->shaper_func.pwl; + else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.shaper, + &cm->shaper_func, &dpp_base->regamma_params, true); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -503,42 +496,43 @@ void dcn401_populate_mcm_luts(struct dc *dc, } /* 3DLUT */ - switch (lut3d_src) { - case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: + if (!lut3d_dma) { + /* SYSMEM (legacy lut3d_func) */ memset(&m_lut_params, 0, sizeof(m_lut_params)); if (hubp->funcs->hubp_enable_3dlut_fl) hubp->funcs->hubp_enable_3dlut_fl(hubp, false); - if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) { - m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d; + if (cm->lut3d_func.state.bits.initialized) { + m_lut_params.lut3d = &cm->lut3d_func.lut_3d; if (mpc->funcs->populate_lut) mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id); if (mpc->funcs->program_lut_mode) mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); } - break; - case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM: - switch (mcm_luts.lut3d_data.gpu_mem_params.size) { - case DC_CM2_GPU_MEM_SIZE_333333: + } else { + /* VIDMEM (3DLUT DMA Fast Load) */ + + /* 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; - case DC_CM2_GPU_MEM_SIZE_171717: +#endif // CONFIG_DRM_AMD_DC_DCN4_2 + case CM_LUT_SIZE_171717: width = hubp_3dlut_fl_width_17; break; - case DC_CM2_GPU_MEM_SIZE_TRANSFORMED: - width = hubp_3dlut_fl_width_transformed; - break; default: - //TODO: handle default case + /* keep default hubp_3dlut_fl_width_transformed */ break; } //check for support if (mpc->funcs->mcm.is_config_supported && !mpc->funcs->mcm.is_config_supported(width)) - break; + return; if (mpc->funcs->program_lut_read_write_control) mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id); @@ -546,21 +540,24 @@ void dcn401_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); if (hubp->funcs->hubp_program_3dlut_fl_addr) - hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr); + hubp->funcs->hubp_program_3dlut_fl_addr(hubp, cm->lut3d_dma.addr); + /* bit_depth was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ if (mpc->funcs->mcm.program_bit_depth) - mpc->funcs->mcm.program_bit_depth(mpc, mcm_luts.lut3d_data.gpu_mem_params.bit_depth, mpcc_id); + mpc->funcs->mcm.program_bit_depth(mpc, 0, mpcc_id); - switch (mcm_luts.lut3d_data.gpu_mem_params.layout) { - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB: + switch (cm->lut3d_dma.swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: mode = hubp_3dlut_fl_mode_native_1; addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR: + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: mode = hubp_3dlut_fl_mode_native_2; addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR: + case CM_LUT_1D_PACKED_LINEAR: mode = hubp_3dlut_fl_mode_transform; addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; break; @@ -575,40 +572,38 @@ void dcn401_populate_mcm_luts(struct dc *dc, if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode) hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode); - switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) { - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB: + switch (cm->lut3d_dma.format) { + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB: format = hubp_3dlut_fl_format_unorm_12msb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB: format = hubp_3dlut_fl_format_unorm_12lsb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10: format = hubp_3dlut_fl_format_float_fp1_5_10; break; + default: + break; } if (hubp->funcs->hubp_program_3dlut_fl_format) hubp->funcs->hubp_program_3dlut_fl_format(hubp, format); if (hubp->funcs->hubp_update_3dlut_fl_bias_scale && mpc->funcs->mcm.program_bias_scale) { mpc->funcs->mcm.program_bias_scale(mpc, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale, + cm->lut3d_dma.bias, + cm->lut3d_dma.scale, mpcc_id); hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale); + cm->lut3d_dma.bias, + cm->lut3d_dma.scale); } - //navi 4x has a bug and r and blue are swapped and need to be worked around here in - //TODO: need to make a method for get_xbar per asic OR do the workaround in program_crossbar for 4x - switch (mcm_luts.lut3d_data.gpu_mem_params.component_order) { - case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA: - default: - crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; - crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; - crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; - break; - } + /* component_order was previously hard-coded to RGBA in local_mcm, + * preserve identical behavior. + */ + crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; + crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; + crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; if (hubp->funcs->hubp_program_3dlut_fl_crossbar) hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp, @@ -634,8 +629,6 @@ void dcn401_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id); } } - break; - } } @@ -660,19 +653,19 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, const struct pwl_params *lut_params = NULL; bool rval; - if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) { - dcn401_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a); + if (plane_state->cm.flags.bits.lut3d_dma_enable) { + dcn401_populate_mcm_luts(dc, pipe_ctx, &plane_state->cm, plane_state->lut_bank_a); return true; } mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; // 1D LUT - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - lut_params = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, + &plane_state->cm.blend_func, &dpp_base->regamma_params, false); lut_params = rval ? &dpp_base->regamma_params : NULL; } @@ -680,12 +673,12 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, lut_params = NULL; // Shaper - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - lut_params = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, + &plane_state->cm.shaper_func, &dpp_base->shaper_params, true); lut_params = rval ? &dpp_base->shaper_params : NULL; } @@ -693,8 +686,8 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, // 3D if (mpc->funcs->program_3dlut) { - if (plane_state->lut3d_func.state.bits.initialized == 1) - result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id); + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) + result &= mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id); else result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id); } @@ -1999,10 +1992,9 @@ void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx) for (odm_pipe = pipe_ctx; odm_pipe != NULL; odm_pipe = odm_pipe->next_odm_pipe) { for (mpc_pipe = odm_pipe; mpc_pipe != NULL; mpc_pipe = mpc_pipe->bottom_pipe) { - if (mpc_pipe->plane_state && mpc_pipe->plane_state->mcm_luts.lut3d_data.lut3d_src - == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM - && mpc_pipe->plane_state->mcm_shaper_3dlut_setting - == DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT) { + if (mpc_pipe->plane_state && + mpc_pipe->plane_state->cm.flags.bits.lut3d_enable && + mpc_pipe->plane_state->cm.flags.bits.lut3d_dma_enable) { wa_pipes[wa_pipe_ct++] = mpc_pipe; } } 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 f78162ab859b..2afeafc902c7 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 @@ -52,7 +52,7 @@ enum dc_status dcn401_enable_stream_timing( void dcn401_enable_stream(struct pipe_ctx *pipe_ctx); void dcn401_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a); void dcn401_setup_hpo_hw_control(const struct dce_hwseq *hws, bool enable); 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 96e0133880e1..9cf8b379cb34 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 @@ -401,40 +401,33 @@ void dcn42_program_cm_hist( } static void dc_get_lut_xbar( - enum dc_cm2_gpu_mem_pixel_component_order order, enum hubp_3dlut_fl_crossbar_bit_slice *cr_r, enum hubp_3dlut_fl_crossbar_bit_slice *y_g, enum hubp_3dlut_fl_crossbar_bit_slice *cb_b) { - switch (order) { - case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA: - *cr_r = hubp_3dlut_fl_crossbar_bit_slice_32_47; - *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; - *cb_b = hubp_3dlut_fl_crossbar_bit_slice_0_15; - break; - case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_BGRA: - *cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; - *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; - *cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; - break; - } + /* component_order was previously hard-coded to RGBA in local_mcm, + * preserve identical behavior. + */ + *cr_r = hubp_3dlut_fl_crossbar_bit_slice_32_47; + *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; + *cb_b = hubp_3dlut_fl_crossbar_bit_slice_0_15; } static void dc_get_lut_mode( - enum dc_cm2_gpu_mem_layout layout, + enum dc_cm_lut_swizzle swizzle, enum hubp_3dlut_fl_mode *mode, enum hubp_3dlut_fl_addressing_mode *addr_mode) { - switch (layout) { - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB: + switch (swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: *mode = hubp_3dlut_fl_mode_native_1; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR: + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: *mode = hubp_3dlut_fl_mode_native_2; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR: + case CM_LUT_1D_PACKED_LINEAR: *mode = hubp_3dlut_fl_mode_transform; *addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; break; @@ -446,19 +439,22 @@ static void dc_get_lut_mode( } static void dc_get_lut_format( - enum dc_cm2_gpu_mem_format dc_format, + enum dc_cm_lut_pixel_format dc_format, enum hubp_3dlut_fl_format *format) { switch (dc_format) { - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB: *format = hubp_3dlut_fl_format_unorm_12msb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB: *format = hubp_3dlut_fl_format_unorm_12lsb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10: *format = hubp_3dlut_fl_format_float_fp1_5_10; break; + default: + *format = hubp_3dlut_fl_format_unorm_12msb_bitslice; + break; } } @@ -472,16 +468,17 @@ static bool dc_is_rmcm_3dlut_supported(struct hubp *hubp, struct mpc *mpc) return false; } -static bool is_rmcm_3dlut_fl_supported(struct dc *dc, enum dc_cm2_gpu_mem_size size) +#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, + * which mapped to dim_17. Preserve identical behavior. + */ if (!dc->caps.color.mpc.rmcm_3d_lut_caps.dma_3d_lut) return false; - if (size == DC_CM2_GPU_MEM_SIZE_171717) - return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u; - else if (size == DC_CM2_GPU_MEM_SIZE_333333) - return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33 != 0u; - 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) { @@ -502,56 +499,45 @@ static void dcn42_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ctx enum MCM_LUT_XABLE *lut3d_xable, enum MCM_LUT_XABLE *lut1d_xable) { - enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL; - bool lut1d_enable = false; struct mpc *mpc = dc->res_pool->mpc; int mpcc_id = pipe_ctx->plane_res.hubp->inst; if (!pipe_ctx->plane_state) return; - shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting; - lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable; + mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; - *lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE; - - switch (shaper_3dlut_setting) { - case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL: - *lut3d_xable = *shaper_xable = MCM_LUT_DISABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER: - *lut3d_xable = MCM_LUT_DISABLE; - *shaper_xable = MCM_LUT_ENABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT: - *lut3d_xable = *shaper_xable = MCM_LUT_ENABLE; - break; - } + *lut1d_xable = pipe_ctx->plane_state->cm.flags.bits.blend_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *shaper_xable = pipe_ctx->plane_state->cm.flags.bits.shaper_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *lut3d_xable = (pipe_ctx->plane_state->cm.flags.bits.shaper_enable && + pipe_ctx->plane_state->cm.flags.bits.lut3d_enable) ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; } static void fl_get_lut_mode( - enum dc_cm2_gpu_mem_layout layout, - enum dc_cm2_gpu_mem_size size, + enum dc_cm_lut_swizzle swizzle, enum hubp_3dlut_fl_mode *mode, enum hubp_3dlut_fl_addressing_mode *addr_mode, enum hubp_3dlut_fl_width *width) { + /* size was previously hard-coded to TRANSFORMED in local_mcm, + * preserve identical behavior (transformed width). + */ *width = hubp_3dlut_fl_width_17; - if (size == DC_CM2_GPU_MEM_SIZE_333333) - *width = hubp_3dlut_fl_width_33; - - switch (layout) { - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB: + switch (swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: *mode = hubp_3dlut_fl_mode_native_1; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR: + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: *mode = hubp_3dlut_fl_mode_native_2; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR: + case CM_LUT_1D_PACKED_LINEAR: *mode = hubp_3dlut_fl_mode_transform; *addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; break; @@ -565,8 +551,7 @@ static void fl_get_lut_mode( bool dcn42_program_rmcm_luts( struct hubp *hubp, struct pipe_ctx *pipe_ctx, - enum dc_cm2_transfer_func_source lut3d_src, - struct dc_cm2_func_luts *mcm_luts, + const struct dc_plane_cm *cm, struct mpc *mpc, bool lut_bank_a, int mpcc_id) @@ -596,21 +581,24 @@ bool dcn42_program_rmcm_luts( if (!rmcm_3dlut) return false; - rmcm_3dlut->protection_bits = mcm_luts->lut3d_data.rmcm_tmz; + /* rmcm_tmz was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ + rmcm_3dlut->protection_bits = 0; dcn42_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable); /* Shaper */ - if (mcm_luts->shaper) { + { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts->shaper->type == TF_TYPE_HWPWL) { - m_lut_params.pwl = &mcm_luts->shaper->pwl; - } else if (mcm_luts->shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->shaper_func.type == TF_TYPE_HWPWL) { + m_lut_params.pwl = &cm->shaper_func.pwl; + } else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); cm_helper_translate_curve_to_hw_format( dc->ctx, - mcm_luts->shaper, + &cm->shaper_func, &dpp_base->shaper_params, true); m_lut_params.pwl = &dpp_base->shaper_params; } @@ -626,15 +614,16 @@ bool dcn42_program_rmcm_luts( } /* 3DLUT */ - switch (lut3d_src) { - case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: + if (!cm->flags.bits.lut3d_dma_enable) { + /* SYSMEM path — no DMA 3DLUT available. + * Previously this was treated as a no-op for the DMA/VIDMEM + * programming, preserve identical behavior. + */ memset(&m_lut_params, 0, sizeof(m_lut_params)); - // Don't know what to do in this case. - //case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: - break; - case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM: - fl_get_lut_mode(mcm_luts->lut3d_data.gpu_mem_params.layout, - mcm_luts->lut3d_data.gpu_mem_params.size, + } else { + /* VIDMEM (3DLUT DMA Fast Load) */ + + fl_get_lut_mode(cm->lut3d_dma.swizzle, &mode, &addr_mode, &width); @@ -646,20 +635,19 @@ bool dcn42_program_rmcm_luts( return false; // setting native or transformed mode, - dc_get_lut_mode(mcm_luts->lut3d_data.gpu_mem_params.layout, &mode, &addr_mode); + dc_get_lut_mode(cm->lut3d_dma.swizzle, &mode, &addr_mode); //seems to be only for the MCM - dc_get_lut_format(mcm_luts->lut3d_data.gpu_mem_params.format_params.format, &format); + dc_get_lut_format(cm->lut3d_dma.format, &format); dc_get_lut_xbar( - mcm_luts->lut3d_data.gpu_mem_params.component_order, &crossbar_bit_slice_cr_r, &crossbar_bit_slice_y_g, &crossbar_bit_slice_cb_b); fl_config.mode = mode; fl_config.enabled = lut3d_xable != MCM_LUT_DISABLE; - fl_config.address = mcm_luts->lut3d_data.gpu_mem_params.addr; + fl_config.address = cm->lut3d_dma.addr; fl_config.format = format; fl_config.crossbar_bit_slice_y_g = crossbar_bit_slice_y_g; fl_config.crossbar_bit_slice_cb_b = crossbar_bit_slice_cb_b; @@ -667,17 +655,20 @@ bool dcn42_program_rmcm_luts( fl_config.width = width; fl_config.protection_bits = rmcm_3dlut->protection_bits; fl_config.addr_mode = addr_mode; - fl_config.layout = mcm_luts->lut3d_data.gpu_mem_params.layout; - fl_config.bias = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.bias; - fl_config.scale = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.scale; + fl_config.layout = cm->lut3d_dma.swizzle; + fl_config.bias = cm->lut3d_dma.bias; + fl_config.scale = cm->lut3d_dma.scale; mpc_fl_config.enabled = fl_config.enabled; mpc_fl_config.width = width; mpc_fl_config.select_lut_bank_a = lut_bank_a; - mpc_fl_config.bit_depth = mcm_luts->lut3d_data.gpu_mem_params.bit_depth; + /* bit_depth was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ + mpc_fl_config.bit_depth = 0; mpc_fl_config.hubp_index = hubp->inst; - mpc_fl_config.bias = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.bias; - mpc_fl_config.scale = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.scale; + mpc_fl_config.bias = cm->lut3d_dma.bias; + mpc_fl_config.scale = cm->lut3d_dma.scale; //1. power down the block mpc->funcs->rmcm.power_on_shaper_3dlut(mpc, mpcc_id, false); @@ -689,10 +680,6 @@ bool dcn42_program_rmcm_luts( //3. power on the block mpc->funcs->rmcm.power_on_shaper_3dlut(mpc, mpcc_id, true); - - break; - default: - return false; } return true; @@ -700,7 +687,7 @@ bool dcn42_program_rmcm_luts( void dcn42_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a) { struct dpp *dpp_base = pipe_ctx->plane_res.dpp; @@ -708,14 +695,17 @@ void dcn42_populate_mcm_luts(struct dc *dc, int mpcc_id = hubp->inst; struct mpc *mpc = dc->res_pool->mpc; union mcm_lut_params m_lut_params; - enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src; + const bool lut3d_dma = !!cm->flags.bits.lut3d_dma_enable; enum hubp_3dlut_fl_format format = 0; enum hubp_3dlut_fl_mode mode; - enum hubp_3dlut_fl_width width = 0; + /* Width was previously hard-coded to TRANSFORMED via local_mcm build, + * preserve identical behavior. + */ + enum hubp_3dlut_fl_width width = hubp_3dlut_fl_width_transformed; enum hubp_3dlut_fl_addressing_mode addr_mode; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r = 0; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r; enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE; @@ -724,33 +714,35 @@ void dcn42_populate_mcm_luts(struct dc *dc, dcn42_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable); //MCM - setting its location (Before/After) blender - //set to post blend (true) + //mpc_mcm_post_blend was previously zero-initialized in local_mcm, + //preserve identical behavior. dcn42_set_mcm_location_post_blend( dc, pipe_ctx, - mcm_luts.lut3d_data.mpc_mcm_post_blend); + false); //RMCM - 3dLUT+Shaper - if (mcm_luts.lut3d_data.rmcm_3dlut_enable && - is_rmcm_3dlut_fl_supported(dc, mcm_luts.lut3d_data.gpu_mem_params.size)) { +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + if (cm->flags.bits.rmcm_enable && + is_rmcm_3dlut_fl_supported(dc)) { dcn42_program_rmcm_luts( hubp, pipe_ctx, - lut3d_src, - &mcm_luts, + cm, mpc, lut_bank_a, mpcc_id); } +#endif /* CONFIG_DRM_AMD_DC_DCN4_2 */ /* 1D LUT */ - if (mcm_luts.lut1d_func) { + { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.lut1d_func->pwl; - else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->blend_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->blend_func.pwl; + else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.lut1d_func, + &cm->blend_func, &dpp_base->regamma_params, false); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -763,14 +755,14 @@ void dcn42_populate_mcm_luts(struct dc *dc, } /* Shaper */ - if (mcm_luts.shaper && mcm_luts.lut3d_data.mpc_3dlut_enable) { + if (cm->flags.bits.lut3d_enable) { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.shaper->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.shaper->pwl; - else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->shaper_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->shaper_func.pwl; + else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.shaper, + &cm->shaper_func, &dpp_base->regamma_params, true); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -783,41 +775,27 @@ void dcn42_populate_mcm_luts(struct dc *dc, } /* 3DLUT */ - switch (lut3d_src) { - case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: + if (!lut3d_dma) { + /* SYSMEM (legacy lut3d_func) */ memset(&m_lut_params, 0, sizeof(m_lut_params)); if (hubp->funcs->hubp_enable_3dlut_fl) hubp->funcs->hubp_enable_3dlut_fl(hubp, false); - if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) { - m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d; + if (cm->lut3d_func.state.bits.initialized) { + m_lut_params.lut3d = &cm->lut3d_func.lut_3d; if (mpc->funcs->populate_lut) mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id); if (mpc->funcs->program_lut_mode) mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); } - break; - case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM: - switch (mcm_luts.lut3d_data.gpu_mem_params.size) { - case DC_CM2_GPU_MEM_SIZE_333333: - width = hubp_3dlut_fl_width_33; - break; - case DC_CM2_GPU_MEM_SIZE_171717: - width = hubp_3dlut_fl_width_17; - break; - case DC_CM2_GPU_MEM_SIZE_TRANSFORMED: - width = hubp_3dlut_fl_width_transformed; - break; - default: - //TODO: Handle default case - break; - } + } else { + /* VIDMEM (3DLUT DMA Fast Load) */ //check for support if (mpc->funcs->mcm.is_config_supported && !mpc->funcs->mcm.is_config_supported(width)) - break; + return; if (mpc->funcs->program_lut_read_write_control) mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id); @@ -825,49 +803,70 @@ void dcn42_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); if (hubp->funcs->hubp_program_3dlut_fl_addr) - hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr); + hubp->funcs->hubp_program_3dlut_fl_addr(hubp, cm->lut3d_dma.addr); + /* bit_depth was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ if (mpc->funcs->mcm.program_bit_depth) - mpc->funcs->mcm.program_bit_depth(mpc, mcm_luts.lut3d_data.gpu_mem_params.bit_depth, mpcc_id); + mpc->funcs->mcm.program_bit_depth(mpc, 0, mpcc_id); - dc_get_lut_mode(mcm_luts.lut3d_data.gpu_mem_params.layout, &mode, &addr_mode); + switch (cm->lut3d_dma.swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: + mode = hubp_3dlut_fl_mode_native_1; + addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; + break; + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: + mode = hubp_3dlut_fl_mode_native_2; + addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; + break; + case CM_LUT_1D_PACKED_LINEAR: + mode = hubp_3dlut_fl_mode_transform; + addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; + break; + default: + mode = hubp_3dlut_fl_mode_disable; + addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; + break; + } if (hubp->funcs->hubp_program_3dlut_fl_mode) hubp->funcs->hubp_program_3dlut_fl_mode(hubp, mode); if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode) hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode); - switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) { - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB: + switch (cm->lut3d_dma.format) { + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB: format = hubp_3dlut_fl_format_unorm_12msb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB: format = hubp_3dlut_fl_format_unorm_12lsb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10: format = hubp_3dlut_fl_format_float_fp1_5_10; break; + default: + break; } if (hubp->funcs->hubp_program_3dlut_fl_format) hubp->funcs->hubp_program_3dlut_fl_format(hubp, format); if (hubp->funcs->hubp_update_3dlut_fl_bias_scale && mpc->funcs->mcm.program_bias_scale) { mpc->funcs->mcm.program_bias_scale(mpc, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale, + cm->lut3d_dma.bias, + cm->lut3d_dma.scale, mpcc_id); hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale); + cm->lut3d_dma.bias, + cm->lut3d_dma.scale); } - //navi 4x has a bug and r and blue are swapped and need to be worked around here in - //TODO: need to make a method for get_xbar per asic OR do the workaround in program_crossbar for 4x - dc_get_lut_xbar( - mcm_luts.lut3d_data.gpu_mem_params.component_order, - &crossbar_bit_slice_cr_r, - &crossbar_bit_slice_y_g, - &crossbar_bit_slice_cb_b); + /* component_order was previously hard-coded to RGBA in local_mcm, + * preserve identical behavior. + */ + crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; + crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; + crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; if (hubp->funcs->hubp_program_3dlut_fl_crossbar) hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp, @@ -893,7 +892,6 @@ void dcn42_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id); } } - break; } } @@ -908,19 +906,19 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, const struct pwl_params *lut_params = NULL; bool rval; - if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) { - dcn42_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a); + if (plane_state->cm.flags.bits.lut3d_dma_enable) { + dcn42_populate_mcm_luts(dc, pipe_ctx, &plane_state->cm, plane_state->lut_bank_a); return true; } mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; // 1D LUT - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - lut_params = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, + &plane_state->cm.blend_func, &dpp_base->regamma_params, false); lut_params = rval ? &dpp_base->regamma_params : NULL; } @@ -928,12 +926,12 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, lut_params = NULL; // Shaper - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - lut_params = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, + &plane_state->cm.shaper_func, &dpp_base->shaper_params, true); lut_params = rval ? &dpp_base->shaper_params : NULL; } @@ -941,8 +939,8 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, // 3D if (mpc->funcs->program_3dlut) { - if (plane_state->lut3d_func.state.bits.initialized == 1) - result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id); + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) + result &= mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id); else result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id); } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h index 0539ee0ffaee..c469e7535114 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h @@ -20,14 +20,13 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, void dcn42_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a); bool dcn42_program_rmcm_luts( struct hubp *hubp, struct pipe_ctx *pipe_ctx, - enum dc_cm2_transfer_func_source lut3d_src, - struct dc_cm2_func_luts *mcm_luts, + const struct dc_plane_cm *cm, struct mpc *mpc, bool lut_bank_a, 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 63c6c841c681..b4956893ae9a 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 @@ -58,6 +58,7 @@ struct dc_state; struct dc_stream_status; struct dc_writeback_info; struct dchub_init_data; +struct dc_plane_cm; struct dc_static_screen_params; struct resource_pool; struct resource_context; @@ -219,7 +220,7 @@ struct hwseq_private_funcs { struct dc_state *context); void (*populate_mcm_luts)(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a); void (*perform_3dlut_wa_unlock)(struct pipe_ctx *pipe_ctx); void (*wait_for_pipe_update_if_needed)(struct dc *dc, struct pipe_ctx *pipe_ctx, bool is_surface_update_only); diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h b/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h index 1c18898aa475..6d6eda0e7e9d 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h @@ -108,7 +108,7 @@ struct hubp_fl_3dlut_config { uint16_t scale; struct dc_plane_address address; enum hubp_3dlut_fl_addressing_mode addr_mode; - enum dc_cm2_gpu_mem_layout layout; + enum dc_cm_lut_swizzle layout; uint8_t protection_bits; enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g; enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b; -- cgit v1.2.3 From f5165625b8b27a46993e0c55b4468bd4e215f7c6 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 23 Apr 2026 18:34:52 -0600 Subject: drm/amd/display: Extract backlight code to amdgpu_dm_backlight Move backlight-related functions from amdgpu_dm.c into a new amdgpu_dm_backlight.c file to improve code organization and reduce the size of the monolithic amdgpu_dm.c. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel 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 | 620 +------------------ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 - .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 660 +++++++++++++++++++++ .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 44 ++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 1 + 6 files changed, 710 insertions(+), 619 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 54a93e4255b3..2953c59d85e7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -41,7 +41,8 @@ AMDGPUDM = \ amdgpu_dm_quirks.o \ amdgpu_dm_wb.o \ amdgpu_dm_colorop.o \ - amdgpu_dm_ism.o + amdgpu_dm_ism.o \ + amdgpu_dm_backlight.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 7a46c9e56d87..3bd0ae0e54cd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -66,6 +66,7 @@ #endif #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" +#include "amdgpu_dm_backlight.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -246,10 +247,6 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, enum dc_detect_reason reason); static void handle_hpd_rx_irq(void *param); -static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, - int bl_idx, - u32 user_brightness); - static bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); @@ -4049,74 +4046,6 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } -static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) -{ - const struct drm_panel_backlight_quirk *panel_backlight_quirk; - struct amdgpu_dm_backlight_caps *caps; - struct drm_connector *conn_base; - struct amdgpu_device *adev; - struct drm_luminance_range_info *luminance_range; - struct drm_device *drm; - - if (aconnector->bl_idx == -1 || - aconnector->dc_link->connector_signal != SIGNAL_TYPE_EDP) - return; - - conn_base = &aconnector->base; - drm = conn_base->dev; - adev = drm_to_adev(drm); - - caps = &adev->dm.backlight_caps[aconnector->bl_idx]; - caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; - caps->aux_support = false; - - if (caps->ext_caps->bits.oled == 1 - /* - * || - * caps->ext_caps->bits.sdr_aux_backlight_control == 1 || - * caps->ext_caps->bits.hdr_aux_backlight_control == 1 - */) - caps->aux_support = true; - - if (amdgpu_backlight == 0) - caps->aux_support = false; - else if (amdgpu_backlight == 1) - caps->aux_support = true; - if (caps->aux_support) - aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; - - luminance_range = &conn_base->display_info.luminance_range; - - if (luminance_range->max_luminance) - caps->aux_max_input_signal = luminance_range->max_luminance; - else - caps->aux_max_input_signal = 512; - - if (luminance_range->min_luminance) - caps->aux_min_input_signal = luminance_range->min_luminance; - else - caps->aux_min_input_signal = 1; - - panel_backlight_quirk = - drm_get_panel_backlight_quirk(aconnector->drm_edid); - if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { - if (panel_backlight_quirk->min_brightness) { - caps->min_input_signal = - panel_backlight_quirk->min_brightness - 1; - drm_info(drm, - "Applying panel backlight quirk, min_brightness: %d\n", - caps->min_input_signal); - } - if (panel_backlight_quirk->brightness_mask) { - drm_info(drm, - "Applying panel backlight quirk, brightness_mask: 0x%X\n", - panel_backlight_quirk->brightness_mask); - caps->brightness_mask = - panel_backlight_quirk->brightness_mask; - } - } -} - DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) void amdgpu_dm_update_connector_after_detect( @@ -4242,7 +4171,7 @@ void amdgpu_dm_update_connector_after_detect( } amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); - update_connector_ext_caps(aconnector); + amdgpu_dm_update_connector_ext_caps(aconnector); dm_set_panel_type(aconnector); } else { hdmi_cec_unset_edid(aconnector); @@ -5160,420 +5089,6 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) return 0; } -#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 -#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 -#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) -#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 - -void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, - int bl_idx) -{ - struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx]; - - if (caps->caps_valid) - return; - -#if defined(CONFIG_ACPI) - amdgpu_acpi_get_backlight_caps(caps); - - /* validate the firmware value is sane */ - if (caps->caps_valid) { - int spread = caps->max_input_signal - caps->min_input_signal; - - if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || - caps->min_input_signal < 0 || - spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || - spread < AMDGPU_DM_MIN_SPREAD) { - drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n", - caps->min_input_signal, caps->max_input_signal); - caps->caps_valid = false; - } - } - - if (!caps->caps_valid) { - caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; - caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - caps->caps_valid = true; - } -#else - if (caps->aux_support) - return; - - caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; - caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - caps->caps_valid = true; -#endif -} - -static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, - unsigned int *min, unsigned int *max) -{ - if (!caps) - return 0; - - if (caps->aux_support) { - // Firmware limits are in nits, DC API wants millinits. - *max = 1000 * caps->aux_max_input_signal; - *min = 1000 * caps->aux_min_input_signal; - } else { - // Firmware limits are 8-bit, PWM control is 16-bit. - *max = 0x101 * caps->max_input_signal; - *min = 0x101 * caps->min_input_signal; - } - return 1; -} - -/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ -static inline u32 scale_input_to_fw(int min, int max, u64 input) -{ - return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min); -} - -/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */ -static inline u32 scale_fw_to_input(int min, int max, u64 input) -{ - return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); -} - -static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, - unsigned int min, unsigned int max, - uint32_t *user_brightness) -{ - u32 brightness = scale_input_to_fw(min, max, *user_brightness); - u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; - int left, right; - - if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) - return; - - if (!caps->data_points) - return; - - /* - * Handle the case where brightness is below the first data point - * Interpolate between (0,0) and (first_signal, first_lum) - */ - if (brightness < caps->luminance_data[0].input_signal) { - lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness, - caps->luminance_data[0].input_signal); - goto scale; - } - - left = 0; - right = caps->data_points - 1; - while (left <= right) { - int mid = left + (right - left) / 2; - u8 signal = caps->luminance_data[mid].input_signal; - - /* Exact match found */ - if (signal == brightness) { - lum = caps->luminance_data[mid].luminance; - goto scale; - } - - if (signal < brightness) - left = mid + 1; - else - right = mid - 1; - } - - /* verify bound */ - if (left >= caps->data_points) - left = caps->data_points - 1; - - /* At this point, left > right */ - lower_signal = caps->luminance_data[right].input_signal; - upper_signal = caps->luminance_data[left].input_signal; - lower_lum = caps->luminance_data[right].luminance; - upper_lum = caps->luminance_data[left].luminance; - - /* interpolate */ - if (right == left || !lower_lum) - lum = upper_lum; - else - lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) * - (brightness - lower_signal), - upper_signal - lower_signal); -scale: - *user_brightness = scale_fw_to_input(min, max, - DIV_ROUND_CLOSEST(lum * brightness, 101)); -} - -static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) -{ - unsigned int min, max; - - if (!get_brightness_range(caps, &min, &max)) - return brightness; - - convert_custom_brightness(caps, min, max, &brightness); - - // Rescale 0..max to min..max - return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max); -} - -static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) -{ - unsigned int min, max; - - if (!get_brightness_range(caps, &min, &max)) - return brightness; - - if (brightness < min) - return 0; - // Rescale min..max to 0..max - return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min), - max - min); -} - -static struct dc_stream_state *dm_find_stream_with_link( - struct amdgpu_display_manager *dm, - struct dc_link *link) -{ - struct dc_state *cur_dc_state = dm->dc->current_state; - struct dc_stream_state *stream = NULL; - int i; - - for (i = 0; i < cur_dc_state->stream_count; i++) { - stream = cur_dc_state->streams[i]; - if (stream->link == link) - return stream; - } - - return NULL; -} - -static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, - int bl_idx, - u32 user_brightness) -{ - struct amdgpu_dm_backlight_caps *caps; - struct dc_link *link; - u32 brightness = 0; - bool rc = false, reallow_idle = false; - struct drm_connector *connector; - struct dc_stream_state *stream; - unsigned int min, max; - - list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - - if (aconnector->bl_idx != bl_idx) - continue; - - /* if connector is off, save the brightness for next time it's on */ - if (!aconnector->base.encoder) { - dm->brightness[bl_idx] = user_brightness; - dm->actual_brightness[bl_idx] = 0; - return; - } - } - - amdgpu_dm_update_backlight_caps(dm, bl_idx); - caps = &dm->backlight_caps[bl_idx]; - - dm->brightness[bl_idx] = user_brightness; - /* update scratch register */ - if (bl_idx == 0) - amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); - brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); - link = (struct dc_link *)dm->backlight_link[bl_idx]; - - /* Apply brightness quirk */ - if (caps->brightness_mask) - brightness |= caps->brightness_mask; - - if (trace_amdgpu_dm_brightness_enabled()) { - trace_amdgpu_dm_brightness(__builtin_return_address(0), - user_brightness, - brightness, - caps->aux_support, - power_supply_is_system_supplied() > 0); - } - - stream = dm_find_stream_with_link(dm, link); - if (!stream) - return; - - mutex_lock(&dm->dc_lock); - if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) { - dc_allow_idle_optimizations(dm->dc, false); - reallow_idle = true; - } - - if (caps->aux_support) { - rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness, - AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); - } else { - /* power module uses millipercent */ - get_brightness_range(caps, &min, &max); - brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; - rc = mod_power_set_backlight_percent(dm->power_module, stream, - brightness, 0, false); - } - - /* - * Some kms clients create a ramped backlight transition effect - * by rapidly changing the backlight. Yet we must wait on dmcub - * fw to exit psr/replay before programming backlight. To - * prevent lag, keep disable psr/replay and let the next atomic - * flip clear the event. - * - * ToDo: use ISM to handle rapidly backlight change - * - * Rapidly backlight change is similar to rapidly cursor events, - * which is now handled by ISM. ISM can delay the event until system - * is really idle, so we may use ISM to handle backlight change as well. - */ - amdgpu_dm_psr_set_event(dm, stream, true, - psr_event_hw_programming, true); - amdgpu_dm_replay_set_event(dm, stream, true, - replay_event_hw_programming, true); - - if (dm->dc->caps.ips_support && reallow_idle) - dc_allow_idle_optimizations(dm->dc, true); - - mutex_unlock(&dm->dc_lock); - - if (rc) - dm->actual_brightness[bl_idx] = user_brightness; -} - -static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) -{ - struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; - - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; - amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); - - return 0; -} - -static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, - int bl_idx) -{ - int ret; - struct amdgpu_dm_backlight_caps caps; - struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx]; - - amdgpu_dm_update_backlight_caps(dm, bl_idx); - caps = dm->backlight_caps[bl_idx]; - - if (caps.aux_support) { - u32 avg, peak; - - if (!dc_link_get_backlight_level_nits(link, &avg, &peak)) - return dm->brightness[bl_idx]; - return convert_brightness_to_user(&caps, avg); - } - - ret = dc_link_get_backlight_level(link); - - if (ret == DC_ERROR_UNEXPECTED) - return dm->brightness[bl_idx]; - - return convert_brightness_to_user(&caps, ret); -} - -static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) -{ - struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; - - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; - return amdgpu_dm_backlight_get_level(dm, i); -} - -static const struct backlight_ops amdgpu_dm_backlight_ops = { - .options = BL_CORE_SUSPENDRESUME, - .get_brightness = amdgpu_dm_backlight_get_brightness, - .update_status = amdgpu_dm_backlight_update_status, -}; - -static void -amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) -{ - struct drm_device *drm = aconnector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct backlight_properties props = { 0 }; - struct amdgpu_dm_backlight_caps *caps; - char bl_name[16]; - int min, max; - int real_brightness; - int init_brightness; - - if (aconnector->bl_idx == -1) - return; - - if (!acpi_video_backlight_use_native()) { - drm_info(drm, "Skipping amdgpu DM backlight registration\n"); - /* Try registering an ACPI video backlight device instead. */ - acpi_video_register_backlight(); - return; - } - - caps = &dm->backlight_caps[aconnector->bl_idx]; - if (get_brightness_range(caps, &min, &max)) { - if (power_supply_is_system_supplied() > 0) - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); - else - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); - /* min is zero, so max needs to be adjusted */ - props.max_brightness = max - min; - drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, - caps->ac_level, caps->dc_level); - } else - props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; - - init_brightness = props.brightness; - - if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { - drm_info(drm, "Using custom brightness curve\n"); - props.scale = BACKLIGHT_SCALE_NON_LINEAR; - } else - props.scale = BACKLIGHT_SCALE_LINEAR; - props.type = BACKLIGHT_RAW; - - snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", - drm->primary->index + aconnector->bl_idx); - - dm->backlight_dev[aconnector->bl_idx] = - backlight_device_register(bl_name, aconnector->base.kdev, dm, - &amdgpu_dm_backlight_ops, &props); - dm->brightness[aconnector->bl_idx] = props.brightness; - - if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) { - drm_err(drm, "DM: Backlight registration failed!\n"); - dm->backlight_dev[aconnector->bl_idx] = NULL; - } else { - /* - * dm->brightness[x] can be inconsistent just after startup until - * ops.get_brightness is called. - */ - real_brightness = - amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]); - - if (real_brightness != init_brightness) { - dm->actual_brightness[aconnector->bl_idx] = real_brightness; - dm->brightness[aconnector->bl_idx] = real_brightness; - } - drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); - } -} - static int initialize_plane(struct amdgpu_display_manager *dm, struct amdgpu_mode_info *mode_info, int plane_id, enum drm_plane_type plane_type, @@ -5615,38 +5130,6 @@ static int initialize_plane(struct amdgpu_display_manager *dm, } -static void setup_backlight_device(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector) -{ - struct amdgpu_dm_backlight_caps *caps; - struct dc_link *link = aconnector->dc_link; - int bl_idx = dm->num_of_edps; - - if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) || - link->type == dc_connection_none) - return; - - if (dm->num_of_edps >= AMDGPU_DM_MAX_NUM_EDP) { - drm_warn(adev_to_drm(dm->adev), "Too much eDP connections, skipping backlight setup for additional eDPs\n"); - return; - } - - aconnector->bl_idx = bl_idx; - - amdgpu_dm_update_backlight_caps(dm, bl_idx); - dm->backlight_link[bl_idx] = link; - dm->num_of_edps++; - - update_connector_ext_caps(aconnector); - caps = &dm->backlight_caps[aconnector->bl_idx]; - - /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ - if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) - drm_object_attach_property(&aconnector->base.base, - dm->adev->mode_info.abm_level_property, - ABM_SYSFS_CONTROL); -} - static void amdgpu_set_panel_orientation(struct drm_connector *connector); @@ -5887,7 +5370,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) if (ret) { amdgpu_dm_update_connector_after_detect(aconnector); - setup_backlight_device(dm, aconnector); + amdgpu_dm_setup_backlight_device(dm, aconnector); /* Disable PSR if Replay can be enabled */ if (replay_feature_enabled) @@ -7964,103 +7447,6 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, return ret; } -/** - * DOC: panel power savings - * - * The display manager allows you to set your desired **panel power savings** - * level (between 0-4, with 0 representing off), e.g. using the following:: - * - * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings - * - * Modifying this value can have implications on color accuracy, so tread - * carefully. - */ - -static ssize_t panel_power_savings_show(struct device *device, - struct device_attribute *attr, - char *buf) -{ - struct drm_connector *connector = dev_get_drvdata(device); - struct drm_device *dev = connector->dev; - u8 val; - - drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); - val = to_dm_connector_state(connector->state)->abm_level == - ABM_LEVEL_IMMEDIATE_DISABLE ? 0 : - to_dm_connector_state(connector->state)->abm_level; - drm_modeset_unlock(&dev->mode_config.connection_mutex); - - return sysfs_emit(buf, "%u\n", val); -} - -static ssize_t panel_power_savings_store(struct device *device, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct drm_connector *connector = dev_get_drvdata(device); - struct drm_device *dev = connector->dev; - long val; - int ret; - - ret = kstrtol(buf, 0, &val); - - if (ret) - return ret; - - if (val < 0 || val > 4) - return -EINVAL; - - drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); - if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden) - ret = -EBUSY; - else - to_dm_connector_state(connector->state)->abm_level = val ?: - ABM_LEVEL_IMMEDIATE_DISABLE; - drm_modeset_unlock(&dev->mode_config.connection_mutex); - - if (ret) - return ret; - - drm_kms_helper_hotplug_event(dev); - - return count; -} - -static DEVICE_ATTR_RW(panel_power_savings); - -static struct attribute *amdgpu_attrs[] = { - &dev_attr_panel_power_savings.attr, - NULL -}; - -static const struct attribute_group amdgpu_group = { - .name = "amdgpu", - .attrs = amdgpu_attrs -}; - -static bool -amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) -{ - if (amdgpu_dm_abm_level >= 0) - return false; - - if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) - return false; - - /* check for OLED panels */ - if (amdgpu_dm_connector->bl_idx >= 0) { - struct drm_device *drm = amdgpu_dm_connector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct amdgpu_dm_backlight_caps *caps; - - caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; - if (caps->aux_support) - return false; - } - - return true; -} - static void amdgpu_dm_connector_unregister(struct drm_connector *connector) { struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); 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 dd199e0b7922..f0e91a0a15fc 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1167,5 +1167,4 @@ int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); void retrieve_dmi_info(struct amdgpu_display_manager *dm); -void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx); #endif /* __AMDGPU_DM_H__ */ 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 new file mode 100644 index 000000000000..3770e8dafdbf --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -0,0 +1,660 @@ +// 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 "dc.h" +#include "dc/dc_dmub_srv.h" +#include "dc/dc_state.h" +#include "dc/dc_stat.h" + +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_psr.h" +#include "amdgpu_dm_replay.h" +#include "amdgpu_atombios.h" + +#include "modules/inc/mod_power.h" + +#include +#include +#include +#include + +#include + +#include "amdgpu_dm_trace.h" +#include "amd_shared.h" + +#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 +#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 +#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) +#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 + +void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, + int bl_idx) +{ + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx]; + + if (caps->caps_valid) + return; + +#if defined(CONFIG_ACPI) + amdgpu_acpi_get_backlight_caps(caps); + + /* validate the firmware value is sane */ + if (caps->caps_valid) { + int spread = caps->max_input_signal - caps->min_input_signal; + + if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || + caps->min_input_signal < 0 || + spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || + spread < AMDGPU_DM_MIN_SPREAD) { + drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n", + caps->min_input_signal, caps->max_input_signal); + caps->caps_valid = false; + } + } + + if (!caps->caps_valid) { + caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->caps_valid = true; + } +#else + if (caps->aux_support) + return; + + caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->caps_valid = true; +#endif +} + +static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, + unsigned int *min, unsigned int *max) +{ + if (!caps) + return 0; + + if (caps->aux_support) { + /* Firmware limits are in nits, DC API wants millinits. */ + *max = 1000 * caps->aux_max_input_signal; + *min = 1000 * caps->aux_min_input_signal; + } else { + /* Firmware limits are 8-bit, PWM control is 16-bit. */ + *max = 0x101 * caps->max_input_signal; + *min = 0x101 * caps->min_input_signal; + } + return 1; +} + +/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ +static inline u32 scale_input_to_fw(int min, int max, u64 input) +{ + return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min); +} + +/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */ +static inline u32 scale_fw_to_input(int min, int max, u64 input) +{ + return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); +} + +static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, + unsigned int min, unsigned int max, + uint32_t *user_brightness) +{ + u32 brightness = scale_input_to_fw(min, max, *user_brightness); + u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; + int left, right; + + if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) + return; + + if (!caps->data_points) + return; + + /* + * Handle the case where brightness is below the first data point + * Interpolate between (0,0) and (first_signal, first_lum) + */ + if (brightness < caps->luminance_data[0].input_signal) { + lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness, + caps->luminance_data[0].input_signal); + goto scale; + } + + left = 0; + right = caps->data_points - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + u8 signal = caps->luminance_data[mid].input_signal; + + /* Exact match found */ + if (signal == brightness) { + lum = caps->luminance_data[mid].luminance; + goto scale; + } + + if (signal < brightness) + left = mid + 1; + else + right = mid - 1; + } + + /* verify bound */ + if (left >= caps->data_points) + left = caps->data_points - 1; + + /* At this point, left > right */ + lower_signal = caps->luminance_data[right].input_signal; + upper_signal = caps->luminance_data[left].input_signal; + lower_lum = caps->luminance_data[right].luminance; + upper_lum = caps->luminance_data[left].luminance; + + /* interpolate */ + if (right == left || !lower_lum) + lum = upper_lum; + else + lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) * + (brightness - lower_signal), + upper_signal - lower_signal); +scale: + *user_brightness = scale_fw_to_input(min, max, + DIV_ROUND_CLOSEST(lum * brightness, 101)); +} + +static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) +{ + unsigned int min, max; + + if (!get_brightness_range(caps, &min, &max)) + return brightness; + + convert_custom_brightness(caps, min, max, &brightness); + + /* Rescale 0..max to min..max */ + return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max); +} + +static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) +{ + unsigned int min, max; + + if (!get_brightness_range(caps, &min, &max)) + return brightness; + + if (brightness < min) + return 0; + /* Rescale min..max to 0..max */ + return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min), + max - min); +} + +static struct dc_stream_state *dm_find_stream_with_link( + struct amdgpu_display_manager *dm, + struct dc_link *link) +{ + struct dc_state *cur_dc_state = dm->dc->current_state; + struct dc_stream_state *stream = NULL; + int i; + + for (i = 0; i < cur_dc_state->stream_count; i++) { + stream = cur_dc_state->streams[i]; + if (stream->link == link) + return stream; + } + + return NULL; +} + +void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, + int bl_idx, + u32 user_brightness) +{ + struct amdgpu_dm_backlight_caps *caps; + struct dc_link *link; + u32 brightness = 0; + bool rc = false, reallow_idle = false; + struct drm_connector *connector; + struct dc_stream_state *stream; + unsigned int min, max; + + list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + if (aconnector->bl_idx != bl_idx) + continue; + + /* if connector is off, save the brightness for next time it's on */ + if (!aconnector->base.encoder) { + dm->brightness[bl_idx] = user_brightness; + dm->actual_brightness[bl_idx] = 0; + return; + } + } + + amdgpu_dm_update_backlight_caps(dm, bl_idx); + caps = &dm->backlight_caps[bl_idx]; + + dm->brightness[bl_idx] = user_brightness; + /* update scratch register */ + if (bl_idx == 0) + amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); + brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); + link = (struct dc_link *)dm->backlight_link[bl_idx]; + + /* Apply brightness quirk */ + if (caps->brightness_mask) + brightness |= caps->brightness_mask; + + if (trace_amdgpu_dm_brightness_enabled()) { + trace_amdgpu_dm_brightness(__builtin_return_address(0), + user_brightness, + brightness, + caps->aux_support, + power_supply_is_system_supplied() > 0); + } + + stream = dm_find_stream_with_link(dm, link); + if (!stream) + return; + + mutex_lock(&dm->dc_lock); + if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) { + dc_allow_idle_optimizations(dm->dc, false); + reallow_idle = true; + } + + if (caps->aux_support) { + rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness, + AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); + } else { + /* power module uses millipercent */ + get_brightness_range(caps, &min, &max); + brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; + rc = mod_power_set_backlight_percent(dm->power_module, stream, + brightness, 0, false); + } + + /* + * Some kms clients create a ramped backlight transition effect + * by rapidly changing the backlight. Yet we must wait on dmcub + * fw to exit psr/replay before programming backlight. To + * prevent lag, keep disable psr/replay and let the next atomic + * flip clear the event. + * + * ToDo: use ISM to handle rapidly backlight change + * + * Rapidly backlight change is similar to rapidly cursor events, + * which is now handled by ISM. ISM can delay the event until system + * is really idle, so we may use ISM to handle backlight change as well. + */ + amdgpu_dm_psr_set_event(dm, stream, true, + psr_event_hw_programming, true); + amdgpu_dm_replay_set_event(dm, stream, true, + replay_event_hw_programming, true); + + if (dm->dc->caps.ips_support && reallow_idle) + dc_allow_idle_optimizations(dm->dc, true); + + mutex_unlock(&dm->dc_lock); + + if (rc) + dm->actual_brightness[bl_idx] = user_brightness; +} + +static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) +{ + struct amdgpu_display_manager *dm = bl_get_data(bd); + int i; + + for (i = 0; i < dm->num_of_edps; i++) { + if (bd == dm->backlight_dev[i]) + break; + } + if (i >= AMDGPU_DM_MAX_NUM_EDP) + i = 0; + amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); + + return 0; +} + +static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, + int bl_idx) +{ + int ret; + struct amdgpu_dm_backlight_caps caps; + struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx]; + + amdgpu_dm_update_backlight_caps(dm, bl_idx); + caps = dm->backlight_caps[bl_idx]; + + if (caps.aux_support) { + u32 avg, peak; + + if (!dc_link_get_backlight_level_nits(link, &avg, &peak)) + return dm->brightness[bl_idx]; + return convert_brightness_to_user(&caps, avg); + } + + ret = dc_link_get_backlight_level(link); + + if (ret == DC_ERROR_UNEXPECTED) + return dm->brightness[bl_idx]; + + return convert_brightness_to_user(&caps, ret); +} + +static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) +{ + struct amdgpu_display_manager *dm = bl_get_data(bd); + int i; + + for (i = 0; i < dm->num_of_edps; i++) { + if (bd == dm->backlight_dev[i]) + break; + } + if (i >= AMDGPU_DM_MAX_NUM_EDP) + i = 0; + return amdgpu_dm_backlight_get_level(dm, i); +} + +static const struct backlight_ops amdgpu_dm_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = amdgpu_dm_backlight_get_brightness, + .update_status = amdgpu_dm_backlight_update_status, +}; + +void +amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) +{ + struct drm_device *drm = aconnector->base.dev; + struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; + struct backlight_properties props = { 0 }; + struct amdgpu_dm_backlight_caps *caps; + char bl_name[16]; + int min, max; + int real_brightness; + int init_brightness; + + if (aconnector->bl_idx == -1) + return; + + if (!acpi_video_backlight_use_native()) { + drm_info(drm, "Skipping amdgpu DM backlight registration\n"); + /* Try registering an ACPI video backlight device instead. */ + acpi_video_register_backlight(); + return; + } + + caps = &dm->backlight_caps[aconnector->bl_idx]; + if (get_brightness_range(caps, &min, &max)) { + if (power_supply_is_system_supplied() > 0) + props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); + else + props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); + /* min is zero, so max needs to be adjusted */ + props.max_brightness = max - min; + drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, + caps->ac_level, caps->dc_level); + } else + props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; + + init_brightness = props.brightness; + + if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { + drm_info(drm, "Using custom brightness curve\n"); + props.scale = BACKLIGHT_SCALE_NON_LINEAR; + } else + props.scale = BACKLIGHT_SCALE_LINEAR; + props.type = BACKLIGHT_RAW; + + snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", + drm->primary->index + aconnector->bl_idx); + + dm->backlight_dev[aconnector->bl_idx] = + backlight_device_register(bl_name, aconnector->base.kdev, dm, + &amdgpu_dm_backlight_ops, &props); + dm->brightness[aconnector->bl_idx] = props.brightness; + + if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) { + drm_err(drm, "DM: Backlight registration failed!\n"); + dm->backlight_dev[aconnector->bl_idx] = NULL; + } else { + /* + * dm->brightness[x] can be inconsistent just after startup until + * ops.get_brightness is called. + */ + real_brightness = + amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]); + + if (real_brightness != init_brightness) { + dm->actual_brightness[aconnector->bl_idx] = real_brightness; + dm->brightness[aconnector->bl_idx] = real_brightness; + } + drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); + } +} + +void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) +{ + const struct drm_panel_backlight_quirk *panel_backlight_quirk; + struct amdgpu_dm_backlight_caps *caps; + struct drm_connector *conn_base; + struct amdgpu_device *adev; + struct drm_luminance_range_info *luminance_range; + struct drm_device *drm; + + if (aconnector->bl_idx == -1 || + aconnector->dc_link->connector_signal != SIGNAL_TYPE_EDP) + return; + + conn_base = &aconnector->base; + drm = conn_base->dev; + adev = drm_to_adev(drm); + + caps = &adev->dm.backlight_caps[aconnector->bl_idx]; + caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; + caps->aux_support = false; + + if (caps->ext_caps->bits.oled == 1 + /* + * || + * caps->ext_caps->bits.sdr_aux_backlight_control == 1 || + * caps->ext_caps->bits.hdr_aux_backlight_control == 1 + */) + caps->aux_support = true; + + if (amdgpu_backlight == 0) + caps->aux_support = false; + else if (amdgpu_backlight == 1) + caps->aux_support = true; + if (caps->aux_support) + aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; + + luminance_range = &conn_base->display_info.luminance_range; + + if (luminance_range->max_luminance) + caps->aux_max_input_signal = luminance_range->max_luminance; + else + caps->aux_max_input_signal = 512; + + if (luminance_range->min_luminance) + caps->aux_min_input_signal = luminance_range->min_luminance; + else + caps->aux_min_input_signal = 1; + + panel_backlight_quirk = + drm_get_panel_backlight_quirk(aconnector->drm_edid); + if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { + if (panel_backlight_quirk->min_brightness) { + caps->min_input_signal = + panel_backlight_quirk->min_brightness - 1; + drm_info(drm, + "Applying panel backlight quirk, min_brightness: %d\n", + caps->min_input_signal); + } + if (panel_backlight_quirk->brightness_mask) { + drm_info(drm, + "Applying panel backlight quirk, brightness_mask: 0x%X\n", + panel_backlight_quirk->brightness_mask); + caps->brightness_mask = + panel_backlight_quirk->brightness_mask; + } + } +} + +void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector) +{ + struct amdgpu_dm_backlight_caps *caps; + struct dc_link *link = aconnector->dc_link; + int bl_idx = dm->num_of_edps; + + if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) || + link->type == dc_connection_none) + return; + + if (dm->num_of_edps >= AMDGPU_DM_MAX_NUM_EDP) { + drm_warn(adev_to_drm(dm->adev), "Too much eDP connections, skipping backlight setup for additional eDPs\n"); + return; + } + + aconnector->bl_idx = bl_idx; + + amdgpu_dm_update_backlight_caps(dm, bl_idx); + dm->backlight_link[bl_idx] = link; + dm->num_of_edps++; + + amdgpu_dm_update_connector_ext_caps(aconnector); + caps = &dm->backlight_caps[aconnector->bl_idx]; + + /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ + if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) + drm_object_attach_property(&aconnector->base.base, + dm->adev->mode_info.abm_level_property, + ABM_SYSFS_CONTROL); +} + +/** + * DOC: panel power savings + * + * The display manager allows you to set your desired **panel power savings** + * level (between 0-4, with 0 representing off), e.g. using the following:: + * + * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings + * + * Modifying this value can have implications on color accuracy, so tread + * carefully. + */ + +static ssize_t panel_power_savings_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct drm_connector *connector = dev_get_drvdata(device); + struct drm_device *dev = connector->dev; + u8 val; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + val = to_dm_connector_state(connector->state)->abm_level == + ABM_LEVEL_IMMEDIATE_DISABLE ? 0 : + to_dm_connector_state(connector->state)->abm_level; + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t panel_power_savings_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct drm_connector *connector = dev_get_drvdata(device); + struct drm_device *dev = connector->dev; + long val; + int ret; + + ret = kstrtol(buf, 0, &val); + + if (ret) + return ret; + + if (val < 0 || val > 4) + return -EINVAL; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden) + ret = -EBUSY; + else + to_dm_connector_state(connector->state)->abm_level = val ?: + ABM_LEVEL_IMMEDIATE_DISABLE; + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + if (ret) + return ret; + + drm_kms_helper_hotplug_event(dev); + + return count; +} + +static DEVICE_ATTR_RW(panel_power_savings); + +static struct attribute *amdgpu_attrs[] = { + &dev_attr_panel_power_savings.attr, + NULL +}; + +const struct attribute_group amdgpu_group = { + .name = "amdgpu", + .attrs = amdgpu_attrs +}; + +bool +amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) +{ + if (amdgpu_dm_abm_level >= 0) + return false; + + if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) + return false; + + /* check for OLED panels */ + if (amdgpu_dm_connector->bl_idx >= 0) { + struct drm_device *drm = amdgpu_dm_connector->base.dev; + struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; + struct amdgpu_dm_backlight_caps *caps; + + caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; + if (caps->aux_support) + return false; + } + + return true; +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h new file mode 100644 index 000000000000..acff23f9feef --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -0,0 +1,44 @@ +/* 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. + */ + +#ifndef __AMDGPU_DM_BACKLIGHT_H__ +#define __AMDGPU_DM_BACKLIGHT_H__ + +struct amdgpu_display_manager; +struct amdgpu_dm_connector; +struct drm_connector; +struct attribute_group; + +void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, + int bl_idx); +void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, + int bl_idx, u32 user_brightness); +void amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector); +bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector); + +extern const struct attribute_group amdgpu_group; + +#endif /* __AMDGPU_DM_BACKLIGHT_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 84dcb573d98f..0fdcf70256cc 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 @@ -32,6 +32,7 @@ #include "dm_services.h" #include "amdgpu.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_backlight.h" #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "amdgpu_dm_trace.h" -- cgit v1.2.3 From ee55bf7d6a63f02738d79aa4368c145e97e032e3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 27 Apr 2026 19:20:53 -0600 Subject: drm/amd/display: Extract audio code to amdgpu_dm_audio Move audio component, init/fini, ELD notification, fill_audio_info, and commit_audio functions from amdgpu_dm.c into a dedicated amdgpu_dm_audio.c file with its own header. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel 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 | 268 +----------------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 302 +++++++++++++++++++++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h | 44 +++ 4 files changed, 350 insertions(+), 267 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 2953c59d85e7..83a7d03a0348 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -42,7 +42,8 @@ AMDGPUDM = \ amdgpu_dm_wb.o \ amdgpu_dm_colorop.o \ amdgpu_dm_ism.o \ - amdgpu_dm_backlight.o + amdgpu_dm_backlight.o \ + amdgpu_dm_audio.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 3bd0ae0e54cd..d72ce66e3fd4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -67,6 +67,7 @@ #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" #include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_audio.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -95,7 +96,6 @@ #include #include #include -#include #include #include @@ -1110,144 +1110,6 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector) } -static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port, - int pipe, bool *enabled, - unsigned char *buf, int max_bytes) -{ - struct drm_device *dev = dev_get_drvdata(kdev); - struct amdgpu_device *adev = drm_to_adev(dev); - struct drm_connector *connector; - struct drm_connector_list_iter conn_iter; - struct amdgpu_dm_connector *aconnector; - int ret = 0; - - *enabled = false; - - mutex_lock(&adev->dm.audio_lock); - - drm_connector_list_iter_begin(dev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (aconnector->audio_inst != port) - continue; - - *enabled = true; - mutex_lock(&connector->eld_mutex); - ret = drm_eld_size(connector->eld); - memcpy(buf, connector->eld, min(max_bytes, ret)); - mutex_unlock(&connector->eld_mutex); - - break; - } - drm_connector_list_iter_end(&conn_iter); - - mutex_unlock(&adev->dm.audio_lock); - - drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled); - - return ret; -} - -static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { - .get_eld = amdgpu_dm_audio_component_get_eld, -}; - -static int amdgpu_dm_audio_component_bind(struct device *kdev, - struct device *hda_kdev, void *data) -{ - struct drm_device *dev = dev_get_drvdata(kdev); - struct amdgpu_device *adev = drm_to_adev(dev); - struct drm_audio_component *acomp = data; - - acomp->ops = &amdgpu_dm_audio_component_ops; - acomp->dev = kdev; - adev->dm.audio_component = acomp; - - return 0; -} - -static void amdgpu_dm_audio_component_unbind(struct device *kdev, - struct device *hda_kdev, void *data) -{ - struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); - struct drm_audio_component *acomp = data; - - acomp->ops = NULL; - acomp->dev = NULL; - adev->dm.audio_component = NULL; -} - -static const struct component_ops amdgpu_dm_audio_component_bind_ops = { - .bind = amdgpu_dm_audio_component_bind, - .unbind = amdgpu_dm_audio_component_unbind, -}; - -static int amdgpu_dm_audio_init(struct amdgpu_device *adev) -{ - int i, ret; - - if (!amdgpu_audio) - return 0; - - adev->mode_info.audio.enabled = true; - - adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count; - - for (i = 0; i < adev->mode_info.audio.num_pins; i++) { - adev->mode_info.audio.pin[i].channels = -1; - adev->mode_info.audio.pin[i].rate = -1; - adev->mode_info.audio.pin[i].bits_per_sample = -1; - adev->mode_info.audio.pin[i].status_bits = 0; - adev->mode_info.audio.pin[i].category_code = 0; - adev->mode_info.audio.pin[i].connected = false; - adev->mode_info.audio.pin[i].id = - adev->dm.dc->res_pool->audios[i]->inst; - adev->mode_info.audio.pin[i].offset = 0; - } - - ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); - if (ret < 0) - return ret; - - adev->dm.audio_registered = true; - - return 0; -} - -static void amdgpu_dm_audio_fini(struct amdgpu_device *adev) -{ - if (!amdgpu_audio) - return; - - if (!adev->mode_info.audio.enabled) - return; - - if (adev->dm.audio_registered) { - component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops); - adev->dm.audio_registered = false; - } - - /* TODO: Disable audio? */ - - adev->mode_info.audio.enabled = false; -} - -static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) -{ - struct drm_audio_component *acomp = adev->dm.audio_component; - - if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) { - drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin); - - acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr, - pin, -1); - } -} - static int dm_dmub_hw_init(struct amdgpu_device *adev) { const struct dmcub_firmware_header_v1_0 *hdr; @@ -6530,51 +6392,6 @@ static void fill_stream_properties_from_drm_display_mode( stream->content_type = get_output_content_type(connector_state); } -static void fill_audio_info(struct audio_info *audio_info, - const struct drm_connector *drm_connector, - const struct dc_sink *dc_sink) -{ - int i = 0; - int cea_revision = 0; - const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps; - - audio_info->manufacture_id = edid_caps->manufacturer_id; - audio_info->product_id = edid_caps->product_id; - - cea_revision = drm_connector->display_info.cea_rev; - - strscpy(audio_info->display_name, - edid_caps->display_name, - AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); - - if (cea_revision >= 3) { - audio_info->mode_count = edid_caps->audio_mode_count; - - for (i = 0; i < audio_info->mode_count; ++i) { - audio_info->modes[i].format_code = - (enum audio_format_code) - (edid_caps->audio_modes[i].format_code); - audio_info->modes[i].channel_count = - edid_caps->audio_modes[i].channel_count; - audio_info->modes[i].sample_rates.all = - edid_caps->audio_modes[i].sample_rate; - audio_info->modes[i].sample_size = - edid_caps->audio_modes[i].sample_size; - } - } - - audio_info->flags.all = edid_caps->speaker_flags; - - /* TODO: We only check for the progressive mode, check for interlace mode too */ - if (drm_connector->latency_present[0]) { - audio_info->video_latency = drm_connector->video_latency[0]; - audio_info->audio_latency = drm_connector->audio_latency[0]; - } - - /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ - -} - static void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, struct drm_display_mode *dst_mode) @@ -7176,7 +6993,7 @@ create_stream_for_sink(struct drm_connector *connector, update_stream_scaling_settings(dev, &mode, dm_state, stream); - fill_audio_info( + amdgpu_dm_fill_audio_info( &stream->audio_info, connector, sink); @@ -9984,87 +9801,6 @@ cleanup: kfree(bundle); } -static void amdgpu_dm_commit_audio(struct drm_device *dev, - struct drm_atomic_commit *state) -{ - struct amdgpu_device *adev = drm_to_adev(dev); - struct amdgpu_dm_connector *aconnector; - struct drm_connector *connector; - struct drm_connector_state *old_con_state, *new_con_state; - struct drm_crtc_state *new_crtc_state; - struct dm_crtc_state *new_dm_crtc_state; - const struct dc_stream_status *status; - int i, inst; - - /* Notify device removals. */ - for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) { - if (old_con_state->crtc != new_con_state->crtc) { - /* CRTC changes require notification. */ - goto notify; - } - - if (!new_con_state->crtc) - continue; - - new_crtc_state = drm_atomic_get_new_crtc_state( - state, new_con_state->crtc); - - if (!new_crtc_state) - continue; - - if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) - continue; - -notify: - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - - mutex_lock(&adev->dm.audio_lock); - inst = aconnector->audio_inst; - aconnector->audio_inst = -1; - mutex_unlock(&adev->dm.audio_lock); - - amdgpu_dm_audio_eld_notify(adev, inst); - } - - /* Notify audio device additions. */ - for_each_new_connector_in_state(state, connector, new_con_state, i) { - if (!new_con_state->crtc) - continue; - - new_crtc_state = drm_atomic_get_new_crtc_state( - state, new_con_state->crtc); - - if (!new_crtc_state) - continue; - - if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) - continue; - - new_dm_crtc_state = to_dm_crtc_state(new_crtc_state); - if (!new_dm_crtc_state->stream) - continue; - - status = dc_stream_get_status(new_dm_crtc_state->stream); - if (!status) - continue; - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - - mutex_lock(&adev->dm.audio_lock); - inst = status->audio_inst; - aconnector->audio_inst = inst; - mutex_unlock(&adev->dm.audio_lock); - - amdgpu_dm_audio_eld_notify(adev, inst); - } -} - /* * amdgpu_dm_crtc_copy_transient_flags - copy mirrored flags from DRM to DC * @crtc_state: the DRM CRTC state 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 new file mode 100644 index 000000000000..a15b7c0c9075 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -0,0 +1,302 @@ +// 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 "amdgpu.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_audio.h" +#include "dc.h" + +#include +#include +#include +#include +#include +#include + +#include "dc/inc/core_types.h" + +static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port, + int pipe, bool *enabled, + unsigned char *buf, int max_bytes) +{ + struct drm_device *dev = dev_get_drvdata(kdev); + struct amdgpu_device *adev = drm_to_adev(dev); + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct amdgpu_dm_connector *aconnector; + int ret = 0; + + *enabled = false; + + mutex_lock(&adev->dm.audio_lock); + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (aconnector->audio_inst != port) + continue; + + *enabled = true; + mutex_lock(&connector->eld_mutex); + ret = drm_eld_size(connector->eld); + memcpy(buf, connector->eld, min(max_bytes, ret)); + mutex_unlock(&connector->eld_mutex); + + break; + } + drm_connector_list_iter_end(&conn_iter); + + mutex_unlock(&adev->dm.audio_lock); + + drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled); + + return ret; +} + +static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { + .get_eld = amdgpu_dm_audio_component_get_eld, +}; + +static int amdgpu_dm_audio_component_bind(struct device *kdev, + struct device *hda_kdev, void *data) +{ + struct drm_device *dev = dev_get_drvdata(kdev); + struct amdgpu_device *adev = drm_to_adev(dev); + struct drm_audio_component *acomp = data; + + acomp->ops = &amdgpu_dm_audio_component_ops; + acomp->dev = kdev; + adev->dm.audio_component = acomp; + + return 0; +} + +static void amdgpu_dm_audio_component_unbind(struct device *kdev, + struct device *hda_kdev, void *data) +{ + struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); + struct drm_audio_component *acomp = data; + + acomp->ops = NULL; + acomp->dev = NULL; + adev->dm.audio_component = NULL; +} + +static const struct component_ops amdgpu_dm_audio_component_bind_ops = { + .bind = amdgpu_dm_audio_component_bind, + .unbind = amdgpu_dm_audio_component_unbind, +}; + +int amdgpu_dm_audio_init(struct amdgpu_device *adev) +{ + int i, ret; + + if (!amdgpu_audio) + return 0; + + adev->mode_info.audio.enabled = true; + + adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count; + + for (i = 0; i < adev->mode_info.audio.num_pins; i++) { + adev->mode_info.audio.pin[i].channels = -1; + adev->mode_info.audio.pin[i].rate = -1; + adev->mode_info.audio.pin[i].bits_per_sample = -1; + adev->mode_info.audio.pin[i].status_bits = 0; + adev->mode_info.audio.pin[i].category_code = 0; + adev->mode_info.audio.pin[i].connected = false; + adev->mode_info.audio.pin[i].id = + adev->dm.dc->res_pool->audios[i]->inst; + adev->mode_info.audio.pin[i].offset = 0; + } + + ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); + if (ret < 0) + return ret; + + adev->dm.audio_registered = true; + + return 0; +} + +void amdgpu_dm_audio_fini(struct amdgpu_device *adev) +{ + if (!amdgpu_audio) + return; + + if (!adev->mode_info.audio.enabled) + return; + + if (adev->dm.audio_registered) { + component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops); + adev->dm.audio_registered = false; + } + + /* TODO: Disable audio? */ + + adev->mode_info.audio.enabled = false; +} + +static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) +{ + struct drm_audio_component *acomp = adev->dm.audio_component; + + if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) { + drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin); + + acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr, + pin, -1); + } +} + +void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, + const struct drm_connector *drm_connector, + const struct dc_sink *dc_sink) +{ + int i = 0; + int cea_revision = 0; + const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps; + + audio_info->manufacture_id = edid_caps->manufacturer_id; + audio_info->product_id = edid_caps->product_id; + + cea_revision = drm_connector->display_info.cea_rev; + + strscpy(audio_info->display_name, + edid_caps->display_name, + AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); + + if (cea_revision >= 3) { + audio_info->mode_count = edid_caps->audio_mode_count; + + for (i = 0; i < audio_info->mode_count; ++i) { + audio_info->modes[i].format_code = + (enum audio_format_code) + (edid_caps->audio_modes[i].format_code); + audio_info->modes[i].channel_count = + edid_caps->audio_modes[i].channel_count; + audio_info->modes[i].sample_rates.all = + edid_caps->audio_modes[i].sample_rate; + audio_info->modes[i].sample_size = + edid_caps->audio_modes[i].sample_size; + } + } + + audio_info->flags.all = edid_caps->speaker_flags; + + /* TODO: We only check for the progressive mode, check for interlace mode too */ + if (drm_connector->latency_present[0]) { + audio_info->video_latency = drm_connector->video_latency[0]; + audio_info->audio_latency = drm_connector->audio_latency[0]; + } + + /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ + +} + +void amdgpu_dm_commit_audio(struct drm_device *dev, + struct drm_atomic_commit *state) +{ + struct amdgpu_device *adev = drm_to_adev(dev); + struct amdgpu_dm_connector *aconnector; + struct drm_connector *connector; + struct drm_connector_state *old_con_state, *new_con_state; + struct drm_crtc_state *new_crtc_state; + struct dm_crtc_state *new_dm_crtc_state; + const struct dc_stream_status *status; + int i, inst; + + /* Notify device removals. */ + for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) { + if (old_con_state->crtc != new_con_state->crtc) { + /* CRTC changes require notification. */ + goto notify; + } + + if (!new_con_state->crtc) + continue; + + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_con_state->crtc); + + if (!new_crtc_state) + continue; + + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) + continue; + +notify: + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + + mutex_lock(&adev->dm.audio_lock); + inst = aconnector->audio_inst; + aconnector->audio_inst = -1; + mutex_unlock(&adev->dm.audio_lock); + + amdgpu_dm_audio_eld_notify(adev, inst); + } + + /* Notify audio device additions. */ + for_each_new_connector_in_state(state, connector, new_con_state, i) { + if (!new_con_state->crtc) + continue; + + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_con_state->crtc); + + if (!new_crtc_state) + continue; + + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) + continue; + + new_dm_crtc_state = to_dm_crtc_state(new_crtc_state); + if (!new_dm_crtc_state->stream) + continue; + + status = dc_stream_get_status(new_dm_crtc_state->stream); + if (!status) + continue; + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + + mutex_lock(&adev->dm.audio_lock); + inst = status->audio_inst; + aconnector->audio_inst = inst; + mutex_unlock(&adev->dm.audio_lock); + + amdgpu_dm_audio_eld_notify(adev, inst); + } +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h new file mode 100644 index 000000000000..58cce1f79ffd --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -0,0 +1,44 @@ +/* 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_AUDIO_H__ +#define __AMDGPU_DM_AUDIO_H__ + +struct amdgpu_device; +struct drm_device; +struct drm_atomic_state; +struct drm_connector; +struct audio_info; +struct dc_sink; + +int amdgpu_dm_audio_init(struct amdgpu_device *adev); +void amdgpu_dm_audio_fini(struct amdgpu_device *adev); +void amdgpu_dm_commit_audio(struct drm_device *dev, + struct drm_atomic_commit *state); +void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, + const struct drm_connector *drm_connector, + const struct dc_sink *dc_sink); + +#endif /* __AMDGPU_DM_AUDIO_H__ */ -- cgit v1.2.3 From 4734e045f49d9bb801b4a3eef30d6ca7fba44280 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 27 Apr 2026 20:49:30 -0600 Subject: drm/amd/display: Extract DMUB code to amdgpu_dm_dmub Move DMUB-related functions and firmware defines from amdgpu_dm.c into new amdgpu_dm_dmub.c and amdgpu_dm_dmub.h files to reduce the size of amdgpu_dm.c and improve code organization. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel 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 | 927 +-------------------- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 924 ++++++++++++++++++++ .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 68 ++ 4 files changed, 1002 insertions(+), 920 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 83a7d03a0348..a6408da05583 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -43,7 +43,8 @@ AMDGPUDM = \ amdgpu_dm_colorop.o \ amdgpu_dm_ism.o \ amdgpu_dm_backlight.o \ - amdgpu_dm_audio.o + amdgpu_dm_audio.o \ + amdgpu_dm_dmub.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 d72ce66e3fd4..f5766d083213 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -68,6 +68,7 @@ #include "amdgpu_dm_replay.h" #include "amdgpu_dm_backlight.h" #include "amdgpu_dm_audio.h" +#include "amdgpu_dm_dmub.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -108,60 +109,9 @@ #include "modules/inc/mod_power.h" #include "modules/power/power_helpers.h" -static_assert(AMDGPU_DMUB_NOTIFICATION_MAX == DMUB_NOTIFICATION_MAX, "AMDGPU_DMUB_NOTIFICATION_MAX mismatch"); - -#define FIRMWARE_RENOIR_DMUB "amdgpu/renoir_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_RENOIR_DMUB); -#define FIRMWARE_SIENNA_CICHLID_DMUB "amdgpu/sienna_cichlid_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_SIENNA_CICHLID_DMUB); -#define FIRMWARE_NAVY_FLOUNDER_DMUB "amdgpu/navy_flounder_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_NAVY_FLOUNDER_DMUB); -#define FIRMWARE_GREEN_SARDINE_DMUB "amdgpu/green_sardine_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_GREEN_SARDINE_DMUB); -#define FIRMWARE_VANGOGH_DMUB "amdgpu/vangogh_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_VANGOGH_DMUB); -#define FIRMWARE_DIMGREY_CAVEFISH_DMUB "amdgpu/dimgrey_cavefish_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DIMGREY_CAVEFISH_DMUB); -#define FIRMWARE_BEIGE_GOBY_DMUB "amdgpu/beige_goby_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_BEIGE_GOBY_DMUB); -#define FIRMWARE_YELLOW_CARP_DMUB "amdgpu/yellow_carp_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_YELLOW_CARP_DMUB); -#define FIRMWARE_DCN_314_DMUB "amdgpu/dcn_3_1_4_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_314_DMUB); -#define FIRMWARE_DCN_315_DMUB "amdgpu/dcn_3_1_5_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_315_DMUB); -#define FIRMWARE_DCN316_DMUB "amdgpu/dcn_3_1_6_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN316_DMUB); - -#define FIRMWARE_DCN_V3_2_0_DMCUB "amdgpu/dcn_3_2_0_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_0_DMCUB); -#define FIRMWARE_DCN_V3_2_1_DMCUB "amdgpu/dcn_3_2_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_1_DMCUB); - -#define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin" MODULE_FIRMWARE(FIRMWARE_RAVEN_DMCU); - -#define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin" MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU); -#define FIRMWARE_DCN_35_DMUB "amdgpu/dcn_3_5_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_35_DMUB); - -#define FIRMWARE_DCN_351_DMUB "amdgpu/dcn_3_5_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_351_DMUB); - -#define FIRMWARE_DCN_36_DMUB "amdgpu/dcn_3_6_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_36_DMUB); - -#define FIRMWARE_DCN_401_DMUB "amdgpu/dcn_4_0_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB); - -#define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); - -#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB); - /** * DOC: overview * @@ -781,47 +731,6 @@ static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) } #endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ -/** - * dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command. - * @adev: amdgpu_device pointer - * @notify: dmub notification structure - * - * Dmub AUX or SET_CONFIG command completion processing callback - * Copies dmub notification to DM which is to be read by AUX command. - * issuing thread and also signals the event to wake up the thread. - */ -static void dmub_aux_setconfig_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - if (adev->dm.dmub_notify) - memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification)); - if (notify->type == DMUB_NOTIFICATION_AUX_REPLY) - complete(&adev->dm.dmub_aux_transfer_done); -} - -static void dmub_aux_fused_io_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - if (!adev || !notify) { - ASSERT(false); - return; - } - - const struct dmub_cmd_fused_request *req = ¬ify->fused_request; - const uint8_t ddc_line = req->u.aux.ddc_line; - - if (ddc_line >= ARRAY_SIZE(adev->dm.fused_io)) { - ASSERT(false); - return; - } - - struct fused_io_sync *sync = &adev->dm.fused_io[ddc_line]; - - static_assert(sizeof(*req) <= sizeof(sync->reply_data), "Size mismatch"); - memcpy(sync->reply_data, req, sizeof(*req)); - complete(&sync->replied); -} - /** * dmub_hpd_callback - DMUB HPD interrupt processing callback. * @adev: amdgpu_device pointer @@ -911,32 +820,6 @@ static void dmub_hpd_sense_callback(struct amdgpu_device *adev, drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); } -/** - * register_dmub_notify_callback - Sets callback for DMUB notify - * @adev: amdgpu_device pointer - * @type: Type of dmub notification - * @callback: Dmub interrupt callback function - * @dmub_int_thread_offload: offload indicator - * - * API to register a dmub callback handler for a dmub notification - * Also sets indicator whether callback processing to be offloaded. - * to dmub interrupt handling thread - * Return: true if successfully registered, false if there is existing registration - */ -static bool register_dmub_notify_callback(struct amdgpu_device *adev, - enum dmub_notification_type type, - dmub_notify_interrupt_callback_t callback, - bool dmub_int_thread_offload) -{ - if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) { - adev->dm.dmub_callback[type] = callback; - adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; - } else - return false; - - return true; -} - static void dm_handle_hpd_work(struct work_struct *work) { struct dmub_hpd_work *dmub_hpd_wrk; @@ -1110,224 +993,6 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector) } -static int dm_dmub_hw_init(struct amdgpu_device *adev) -{ - const struct dmcub_firmware_header_v1_0 *hdr; - struct dmub_srv *dmub_srv = adev->dm.dmub_srv; - struct dmub_srv_fb_info *fb_info = adev->dm.dmub_fb_info; - const struct firmware *dmub_fw = adev->dm.dmub_fw; - struct dc *dc = adev->dm.dc; - struct dmcu *dmcu = adev->dm.dc->res_pool->dmcu; - struct abm *abm = adev->dm.dc->res_pool->abm; - struct dc_context *ctx = adev->dm.dc->ctx; - struct dmub_srv_hw_params hw_params; - enum dmub_status status; - const unsigned char *fw_inst_const, *fw_bss_data; - u32 i, fw_inst_const_size, fw_bss_data_size; - bool has_hw_support; - - if (!dmub_srv) - /* DMUB isn't supported on the ASIC. */ - return 0; - - if (!fb_info) { - drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n"); - return -EINVAL; - } - - if (!dmub_fw) { - /* Firmware required for DMUB support. */ - drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n"); - return -EINVAL; - } - - /* initialize register offsets for ASICs with runtime initialization available */ - if (dmub_srv->hw_funcs.init_reg_offsets) - dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx); - - status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status); - return -EINVAL; - } - - if (!has_hw_support) { - drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n"); - return 0; - } - - /* Reset DMCUB if it was previously running - before we overwrite its memory. */ - status = dmub_srv_hw_reset(dmub_srv); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status); - - hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data; - - fw_inst_const = dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - PSP_HEADER_BYTES_256; - - fw_bss_data = dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - le32_to_cpu(hdr->inst_const_bytes); - - /* Copy firmware and bios info into FB memory. */ - fw_inst_const_size = adev->dm.fw_inst_size; - - fw_bss_data_size = le32_to_cpu(hdr->bss_data_bytes); - - /* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP, - * amdgpu_ucode_init_single_fw will load dmub firmware - * fw_inst_const part to cw0; otherwise, the firmware back door load - * will be done by dm_dmub_hw_init - */ - if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) { - memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const, - fw_inst_const_size); - } - - if (fw_bss_data_size) - memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr, - fw_bss_data, fw_bss_data_size); - - /* Copy firmware bios info into FB memory. */ - memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios, - adev->bios_size); - - /* Reset regions that need to be reset. */ - memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_4_MAILBOX].size); - - memset(fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].size); - - memset(fb_info->fb[DMUB_WINDOW_6_FW_STATE].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_6_FW_STATE].size); - - memset(fb_info->fb[DMUB_WINDOW_SHARED_STATE].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_SHARED_STATE].size); - - /* Initialize hardware. */ - memset(&hw_params, 0, sizeof(hw_params)); - hw_params.soc_fb_info.fb_base = adev->gmc.fb_start; - hw_params.soc_fb_info.fb_offset = adev->vm_manager.vram_base_offset; - - /* backdoor load firmware and trigger dmub running */ - if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) - hw_params.load_inst_const = true; - - if (dmcu) - hw_params.psp_version = dmcu->psp_version; - - for (i = 0; i < fb_info->num_fb; ++i) - hw_params.fb[i] = &fb_info->fb[i]; - - /* Enable usb4 dpia in the FW APU */ - if (dc->caps.is_apu && - dc->res_pool->usb4_dpia_count != 0 && - !dc->debug.dpia_debug.bits.disable_dpia) { - hw_params.dpia_supported = true; - hw_params.disable_dpia = dc->debug.dpia_debug.bits.disable_dpia; - hw_params.dpia_hpd_int_enable_supported = false; - hw_params.enable_non_transparent_setconfig = dc->config.consolidated_dpia_dp_lt; - hw_params.disable_dpia_bw_allocation = !dc->config.usb4_bw_alloc_support; - } - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(3, 5, 0): - case IP_VERSION(3, 5, 1): - case IP_VERSION(3, 6, 0): - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - hw_params.ips_sequential_ono = adev->external_rev_id > 0x10; - hw_params.lower_hbr3_phy_ssc = true; - break; - default: - break; - } - - status = dmub_srv_hw_init(dmub_srv, &hw_params); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status); - return -EINVAL; - } - - /* Wait for firmware load to finish. */ - status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); - - /* Init DMCU and ABM if available. */ - if (dmcu && abm) { - dmcu->funcs->dmcu_init(dmcu); - abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu); - } - - if (!adev->dm.dc->ctx->dmub_srv) - adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv); - if (!adev->dm.dc->ctx->dmub_srv) { - drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n"); - return -ENOMEM; - } - - drm_info(adev_to_drm(adev), "DMUB hardware initialized: version=0x%08X\n", - adev->dm.dmcub_fw_version); - - /* Keeping sanity checks off if - * DCN31 >= 4.0.59.0 - * DCN314 >= 8.0.16.0 - * Otherwise, turn on sanity checks - */ - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(3, 1, 2): - case IP_VERSION(3, 1, 3): - if (adev->dm.dmcub_fw_version && - adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && - adev->dm.dmcub_fw_version < DMUB_FW_VERSION(4, 0, 59)) - adev->dm.dc->debug.sanity_checks = true; - break; - case IP_VERSION(3, 1, 4): - if (adev->dm.dmcub_fw_version && - adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && - adev->dm.dmcub_fw_version < DMUB_FW_VERSION(8, 0, 16)) - adev->dm.dc->debug.sanity_checks = true; - break; - default: - break; - } - - return 0; -} - -static void dm_dmub_hw_resume(struct amdgpu_device *adev) -{ - struct dmub_srv *dmub_srv = adev->dm.dmub_srv; - enum dmub_status status; - bool init; - int r; - - if (!dmub_srv) { - /* DMUB isn't supported on the ASIC. */ - return; - } - - status = dmub_srv_is_hw_init(dmub_srv, &init); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "DMUB hardware init check failed: %d\n", status); - - if (status == DMUB_STATUS_OK && init) { - /* Wait for firmware load to finish. */ - status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); - } else { - /* Perform the full hardware initialization. */ - r = dm_dmub_hw_init(adev); - if (r) - drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); - } -} - static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config) { u64 pt_base; @@ -1635,119 +1300,6 @@ dm_free_gpu_mem( } -static enum dmub_status -dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, - enum dmub_gpint_command command_code, - uint16_t param, - uint32_t timeout_us) -{ - union dmub_gpint_data_register reg, test; - uint32_t i; - - /* Assume that VBIOS DMUB is ready to take commands */ - - reg.bits.status = 1; - reg.bits.command_code = command_code; - reg.bits.param = param; - - cgs_write_register(adev->dm.cgs_device, 0x34c0 + 0x01f8, reg.all); - - for (i = 0; i < timeout_us; ++i) { - udelay(1); - - /* Check if our GPINT got acked */ - reg.bits.status = 0; - test = (union dmub_gpint_data_register) - cgs_read_register(adev->dm.cgs_device, 0x34c0 + 0x01f8); - - if (test.all == reg.all) - return DMUB_STATUS_OK; - } - - return DMUB_STATUS_TIMEOUT; -} - -static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) -{ - void *bb; - long long addr; - unsigned int bb_size; - int i = 0; - uint16_t chunk; - enum dmub_gpint_command send_addrs[] = { - DMUB_GPINT__SET_BB_ADDR_WORD0, - DMUB_GPINT__SET_BB_ADDR_WORD1, - DMUB_GPINT__SET_BB_ADDR_WORD2, - DMUB_GPINT__SET_BB_ADDR_WORD3, - }; - enum dmub_status ret; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(4, 0, 1): - bb_size = sizeof(struct dml2_soc_bb); - break; - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - bb_size = sizeof(struct dml2_soc_bb); - break; - default: - return NULL; - } - - bb = dm_allocate_gpu_mem(adev, - DC_MEM_ALLOC_TYPE_GART, - bb_size, - &addr); - if (!bb) - return NULL; - - for (i = 0; i < 4; i++) { - /* Extract 16-bit chunk */ - chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF; - /* Send the chunk */ - ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000); - if (ret != DMUB_STATUS_OK) - goto free_bb; - } - - /* Now ask DMUB to copy the bb */ - ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000); - if (ret != DMUB_STATUS_OK) - goto free_bb; - - return bb; - -free_bb: - dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (void *) bb); - return NULL; - -} - -static enum dmub_ips_disable_type dm_get_default_ips_mode( - struct amdgpu_device *adev) -{ - enum dmub_ips_disable_type ret = DMUB_IPS_ENABLE; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(3, 5, 0): - case IP_VERSION(3, 6, 0): - case IP_VERSION(3, 5, 1): - ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; - break; - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - ret = DMUB_IPS_ENABLE; - break; - default: - /* ASICs older than DCN35 do not have IPSs */ - if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0)) - ret = DMUB_IPS_DISABLE_ALL; - break; - } - - return ret; -} - static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) { struct mod_power_init_params init_data[MAX_NUM_EDP]; @@ -2143,8 +1695,8 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } amdgpu_dm_outbox_init(adev); - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY, - dmub_aux_setconfig_callback, false)) { + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY, + dm_dmub_aux_setconfig_callback, false)) { drm_err(adev_to_drm(adev), "fail to register dmub aux callback"); goto error; } @@ -2152,8 +1704,8 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) for (size_t i = 0; i < ARRAY_SIZE(adev->dm.fused_io); i++) init_completion(&adev->dm.fused_io[i].replied); - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO, - dmub_aux_fused_io_callback, false)) { + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO, + dm_dmub_aux_fused_io_callback, false)) { drm_err(adev_to_drm(adev), "fail to register dmub fused io callback"); goto error; } @@ -2441,224 +1993,6 @@ static int load_dmcu_fw(struct amdgpu_device *adev) return 0; } -static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address) -{ - struct amdgpu_device *adev = ctx; - - return dm_read_reg(adev->dm.dc->ctx, address); -} - -static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, - uint32_t value) -{ - struct amdgpu_device *adev = ctx; - - return dm_write_reg(adev->dm.dc->ctx, address, value); -} - -static int dm_dmub_sw_init(struct amdgpu_device *adev) -{ - struct dmub_srv_create_params create_params; - struct dmub_srv_fw_meta_info_params fw_meta_info_params; - struct dmub_srv_region_params region_params; - struct dmub_srv_region_info region_info; - struct dmub_srv_memory_params memory_params; - struct dmub_fw_meta_info fw_info; - struct dmub_srv_fb_info *fb_info; - struct dmub_srv *dmub_srv; - const struct dmcub_firmware_header_v1_0 *hdr; - enum dmub_asic dmub_asic; - enum dmub_status status; - static enum dmub_window_memory_type window_memory_type[DMUB_WINDOW_TOTAL] = { - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_0_INST_CONST - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_1_STACK - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_2_BSS_DATA - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_3_VBIOS - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_4_MAILBOX - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_5_TRACEBUFF - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_6_FW_STATE - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_7_SCRATCH_MEM - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_IB_MEM - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_SHARED_STATE - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_LSDMA_BUFFER - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_CURSOR_OFFLOAD - }; - int r; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(2, 1, 0): - dmub_asic = DMUB_ASIC_DCN21; - break; - case IP_VERSION(3, 0, 0): - dmub_asic = DMUB_ASIC_DCN30; - break; - case IP_VERSION(3, 0, 1): - dmub_asic = DMUB_ASIC_DCN301; - break; - case IP_VERSION(3, 0, 2): - dmub_asic = DMUB_ASIC_DCN302; - break; - case IP_VERSION(3, 0, 3): - dmub_asic = DMUB_ASIC_DCN303; - break; - case IP_VERSION(3, 1, 2): - case IP_VERSION(3, 1, 3): - dmub_asic = (adev->external_rev_id == YELLOW_CARP_B0) ? DMUB_ASIC_DCN31B : DMUB_ASIC_DCN31; - break; - case IP_VERSION(3, 1, 4): - dmub_asic = DMUB_ASIC_DCN314; - break; - case IP_VERSION(3, 1, 5): - dmub_asic = DMUB_ASIC_DCN315; - break; - case IP_VERSION(3, 1, 6): - dmub_asic = DMUB_ASIC_DCN316; - break; - case IP_VERSION(3, 2, 0): - dmub_asic = DMUB_ASIC_DCN32; - break; - case IP_VERSION(3, 2, 1): - dmub_asic = DMUB_ASIC_DCN321; - break; - case IP_VERSION(3, 5, 0): - case IP_VERSION(3, 5, 1): - dmub_asic = DMUB_ASIC_DCN35; - break; - case IP_VERSION(3, 6, 0): - dmub_asic = DMUB_ASIC_DCN36; - break; - case IP_VERSION(4, 0, 1): - dmub_asic = DMUB_ASIC_DCN401; - break; - case IP_VERSION(4, 2, 0): - dmub_asic = DMUB_ASIC_DCN42; - break; - case IP_VERSION(4, 2, 1): - dmub_asic = DMUB_ASIC_DCN42B; - break; - default: - /* ASIC doesn't support DMUB. */ - return 0; - } - - hdr = (const struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data; - adev->dm.dmcub_fw_version = le32_to_cpu(hdr->header.ucode_version); - - if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { - adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id = - AMDGPU_UCODE_ID_DMCUB; - adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw = - adev->dm.dmub_fw; - adev->firmware.fw_size += - ALIGN(le32_to_cpu(hdr->inst_const_bytes), PAGE_SIZE); - - drm_info(adev_to_drm(adev), "Loading DMUB firmware via PSP: version=0x%08X\n", - adev->dm.dmcub_fw_version); - } - - - adev->dm.dmub_srv = kzalloc_obj(*adev->dm.dmub_srv); - dmub_srv = adev->dm.dmub_srv; - - if (!dmub_srv) { - drm_err(adev_to_drm(adev), "Failed to allocate DMUB service!\n"); - return -ENOMEM; - } - - memset(&create_params, 0, sizeof(create_params)); - create_params.user_ctx = adev; - create_params.funcs.reg_read = amdgpu_dm_dmub_reg_read; - create_params.funcs.reg_write = amdgpu_dm_dmub_reg_write; - create_params.asic = dmub_asic; - - /* Create the DMUB service. */ - status = dmub_srv_create(dmub_srv, &create_params); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error creating DMUB service: %d\n", status); - return -EINVAL; - } - - /* Extract the FW meta info. */ - memset(&fw_meta_info_params, 0, sizeof(fw_meta_info_params)); - - fw_meta_info_params.inst_const_size = le32_to_cpu(hdr->inst_const_bytes) - - PSP_HEADER_BYTES_256; - fw_meta_info_params.bss_data_size = le32_to_cpu(hdr->bss_data_bytes); - fw_meta_info_params.fw_inst_const = adev->dm.dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - PSP_HEADER_BYTES_256; - fw_meta_info_params.fw_bss_data = fw_meta_info_params.bss_data_size ? adev->dm.dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - le32_to_cpu(hdr->inst_const_bytes) : NULL; - fw_meta_info_params.custom_psp_footer_size = 0; - - status = dmub_srv_get_fw_meta_info_from_raw_fw(&fw_meta_info_params, &fw_info); - if (status != DMUB_STATUS_OK) { - /* Skip returning early, just log the error. */ - drm_err(adev_to_drm(adev), "Error getting DMUB FW meta info: %d\n", status); - // return -EINVAL; - } - - /* Calculate the size of all the regions for the DMUB service. */ - memset(®ion_params, 0, sizeof(region_params)); - - region_params.inst_const_size = fw_meta_info_params.inst_const_size; - region_params.bss_data_size = fw_meta_info_params.bss_data_size; - region_params.vbios_size = adev->bios_size; - region_params.fw_bss_data = fw_meta_info_params.fw_bss_data; - region_params.fw_inst_const = fw_meta_info_params.fw_inst_const; - region_params.window_memory_type = window_memory_type; - region_params.fw_info = (status == DMUB_STATUS_OK) ? &fw_info : NULL; - - status = dmub_srv_calc_region_info(dmub_srv, ®ion_params, - ®ion_info); - - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status); - return -EINVAL; - } - - /* - * Allocate a framebuffer based on the total size of all the regions. - * TODO: Move this into GART. - */ - r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_VRAM | - AMDGPU_GEM_DOMAIN_GTT, - &adev->dm.dmub_bo, - &adev->dm.dmub_bo_gpu_addr, - &adev->dm.dmub_bo_cpu_addr); - if (r) - return r; - - /* Rebase the regions on the framebuffer address. */ - memset(&memory_params, 0, sizeof(memory_params)); - memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr; - memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr; - memory_params.region_info = ®ion_info; - memory_params.window_memory_type = window_memory_type; - - adev->dm.dmub_fb_info = kzalloc_obj(*adev->dm.dmub_fb_info); - fb_info = adev->dm.dmub_fb_info; - - if (!fb_info) { - drm_err(adev_to_drm(adev), - "Failed to allocate framebuffer info for DMUB service!\n"); - return -ENOMEM; - } - - status = dmub_srv_calc_mem_info(dmub_srv, &memory_params, fb_info); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error calculating DMUB FB info: %d\n", status); - return -EINVAL; - } - - adev->dm.bb_from_dmub = dm_dmub_get_vbios_bounding_box(adev); - adev->dm.fw_inst_size = fw_meta_info_params.inst_const_size; - - return 0; -} - static int dm_sw_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -4382,19 +3716,19 @@ static int register_hpd_handlers(struct amdgpu_device *adev) int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; if (dc_is_dmub_outbox_supported(adev->dm.dc)) { - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, dmub_hpd_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); return -EINVAL; } - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, dmub_hpd_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); return -EINVAL; } - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, dmub_hpd_sense_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); return -EINVAL; @@ -5405,78 +4739,6 @@ DEVICE_ATTR_WO(s3_debug); #endif -static int dm_init_microcode(struct amdgpu_device *adev) -{ - char *fw_name_dmub; - int r; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(2, 1, 0): - fw_name_dmub = FIRMWARE_RENOIR_DMUB; - if (ASICREV_IS_GREEN_SARDINE(adev->external_rev_id)) - fw_name_dmub = FIRMWARE_GREEN_SARDINE_DMUB; - break; - case IP_VERSION(3, 0, 0): - if (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(10, 3, 0)) - fw_name_dmub = FIRMWARE_SIENNA_CICHLID_DMUB; - else - fw_name_dmub = FIRMWARE_NAVY_FLOUNDER_DMUB; - break; - case IP_VERSION(3, 0, 1): - fw_name_dmub = FIRMWARE_VANGOGH_DMUB; - break; - case IP_VERSION(3, 0, 2): - fw_name_dmub = FIRMWARE_DIMGREY_CAVEFISH_DMUB; - break; - case IP_VERSION(3, 0, 3): - fw_name_dmub = FIRMWARE_BEIGE_GOBY_DMUB; - break; - case IP_VERSION(3, 1, 2): - case IP_VERSION(3, 1, 3): - fw_name_dmub = FIRMWARE_YELLOW_CARP_DMUB; - break; - case IP_VERSION(3, 1, 4): - fw_name_dmub = FIRMWARE_DCN_314_DMUB; - break; - case IP_VERSION(3, 1, 5): - fw_name_dmub = FIRMWARE_DCN_315_DMUB; - break; - case IP_VERSION(3, 1, 6): - fw_name_dmub = FIRMWARE_DCN316_DMUB; - break; - case IP_VERSION(3, 2, 0): - fw_name_dmub = FIRMWARE_DCN_V3_2_0_DMCUB; - break; - case IP_VERSION(3, 2, 1): - fw_name_dmub = FIRMWARE_DCN_V3_2_1_DMCUB; - break; - case IP_VERSION(3, 5, 0): - fw_name_dmub = FIRMWARE_DCN_35_DMUB; - break; - case IP_VERSION(3, 5, 1): - fw_name_dmub = FIRMWARE_DCN_351_DMUB; - break; - case IP_VERSION(3, 6, 0): - fw_name_dmub = FIRMWARE_DCN_36_DMUB; - break; - case IP_VERSION(4, 0, 1): - fw_name_dmub = FIRMWARE_DCN_401_DMUB; - break; - case IP_VERSION(4, 2, 0): - fw_name_dmub = FIRMWARE_DCN_42_DMUB; - break; - case IP_VERSION(4, 2, 1): - fw_name_dmub = FIRMWARE_DCN_42B_DMUB; - break; - default: - /* ASIC doesn't support DMUB. */ - return 0; - } - r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED, - "%s", fw_name_dmub); - return r; -} - static int dm_early_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -13009,179 +12271,6 @@ uint32_t dm_read_reg_func(const struct dc_context *ctx, uint32_t address, return value; } -int amdgpu_dm_process_dmub_aux_transfer_sync( - struct dc_context *ctx, - unsigned int link_index, - struct aux_payload *payload, - enum aux_return_code_type *operation_result) -{ - struct amdgpu_device *adev = ctx->driver_context; - struct dmub_notification *p_notify = adev->dm.dmub_notify; - int ret = -1; - - mutex_lock(&adev->dm.dpia_aux_lock); - if (!dc_process_dmub_aux_transfer_async(ctx->dc, link_index, payload)) { - *operation_result = AUX_RET_ERROR_ENGINE_ACQUIRE; - goto out; - } - - if (!wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { - drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); - *operation_result = AUX_RET_ERROR_TIMEOUT; - goto out; - } - - if (p_notify->result != AUX_RET_SUCCESS) { - /* - * Transient states before tunneling is enabled could - * lead to this error. We can ignore this for now. - */ - if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) { - drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n", - payload->address, payload->length, - p_notify->result); - } - *operation_result = p_notify->result; - goto out; - } - - payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF; - if (adev->dm.dmub_notify->aux_reply.command & 0xF0) - /* The reply is stored in the top nibble of the command. */ - payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF; - - /*write req may receive a byte indicating partially written number as well*/ - if (p_notify->aux_reply.length) - memcpy(payload->data, p_notify->aux_reply.data, - p_notify->aux_reply.length); - - /* success */ - ret = p_notify->aux_reply.length; - *operation_result = p_notify->result; -out: - reinit_completion(&adev->dm.dmub_aux_transfer_done); - mutex_unlock(&adev->dm.dpia_aux_lock); - return ret; -} - -static void abort_fused_io( - struct dc_context *ctx, - const struct dmub_cmd_fused_request *request -) -{ - union dmub_rb_cmd command = { 0 }; - struct dmub_rb_cmd_fused_io *io = &command.fused_io; - - io->header.type = DMUB_CMD__FUSED_IO; - io->header.sub_type = DMUB_CMD__FUSED_IO_ABORT; - io->header.payload_bytes = sizeof(*io) - sizeof(io->header); - io->request = *request; - dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT); -} - -static bool execute_fused_io( - struct amdgpu_device *dev, - struct dc_context *ctx, - union dmub_rb_cmd *commands, - uint8_t count, - uint32_t timeout_us -) -{ - const uint8_t ddc_line = commands[0].fused_io.request.u.aux.ddc_line; - - if (ddc_line >= ARRAY_SIZE(dev->dm.fused_io)) - return false; - - struct fused_io_sync *sync = &dev->dm.fused_io[ddc_line]; - struct dmub_rb_cmd_fused_io *first = &commands[0].fused_io; - const bool result = dm_execute_dmub_cmd_list(ctx, count, commands, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) - && first->header.ret_status - && first->request.status == FUSED_REQUEST_STATUS_SUCCESS; - - if (!result) - return false; - - while (wait_for_completion_timeout(&sync->replied, usecs_to_jiffies(timeout_us))) { - reinit_completion(&sync->replied); - - struct dmub_cmd_fused_request *reply = (struct dmub_cmd_fused_request *) sync->reply_data; - - static_assert(sizeof(*reply) <= sizeof(sync->reply_data), "Size mismatch"); - - if (reply->identifier == first->request.identifier) { - first->request = *reply; - return true; - } - } - - reinit_completion(&sync->replied); - first->request.status = FUSED_REQUEST_STATUS_TIMEOUT; - abort_fused_io(ctx, &first->request); - return false; -} - -bool amdgpu_dm_execute_fused_io( - struct amdgpu_device *dev, - struct dc_link *link, - union dmub_rb_cmd *commands, - uint8_t count, - uint32_t timeout_us) -{ - struct amdgpu_display_manager *dm = &dev->dm; - - mutex_lock(&dm->dpia_aux_lock); - - const bool result = execute_fused_io(dev, link->ctx, commands, count, timeout_us); - - mutex_unlock(&dm->dpia_aux_lock); - return result; -} - -int amdgpu_dm_process_dmub_set_config_sync( - struct dc_context *ctx, - unsigned int link_index, - struct set_config_cmd_payload *payload, - enum set_config_status *operation_result) -{ - struct amdgpu_device *adev = ctx->driver_context; - bool is_cmd_complete; - int ret; - - mutex_lock(&adev->dm.dpia_aux_lock); - is_cmd_complete = dc_process_dmub_set_config_async(ctx->dc, - link_index, payload, adev->dm.dmub_notify); - - if (is_cmd_complete || wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { - ret = 0; - *operation_result = adev->dm.dmub_notify->sc_status; - } else { - drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); - ret = -1; - *operation_result = SET_CONFIG_UNKNOWN_ERROR; - } - - if (!is_cmd_complete) - reinit_completion(&adev->dm.dmub_aux_transfer_done); - mutex_unlock(&adev->dm.dpia_aux_lock); - return ret; -} - -bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) -{ - struct amdgpu_device *adev = ctx->driver_context; - - guard(spinlock_irqsave)(&adev->dm.dmub_lock); - return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type); -} - -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) -{ - struct amdgpu_device *adev = ctx->driver_context; - - guard(spinlock_irqsave)(&adev->dm.dmub_lock); - return dc_dmub_srv_cmd_run_list(ctx->dmub_srv, count, cmd, wait_type); -} - void dm_acpi_process_phy_transition_interlock( const struct dc_context *ctx, struct dm_process_phy_transition_init_params process_phy_transition_init_params) 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 new file mode 100644 index 000000000000..739e685f1c3c --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -0,0 +1,924 @@ +// 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 "dm_services_types.h" +#include "dc.h" +#include "dc/inc/core_types.h" +#include "dc/dc_dmub_srv.h" +#include "dmub/dmub_srv.h" +#include "dc/inc/hw/dmcu.h" +#include "dc/inc/hw/abm.h" +#include "dal_asic_id.h" + +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_ucode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_dmub.h" +#include +#include + +static_assert(AMDGPU_DMUB_NOTIFICATION_MAX == DMUB_NOTIFICATION_MAX, "AMDGPU_DMUB_NOTIFICATION_MAX mismatch"); + +MODULE_FIRMWARE(FIRMWARE_RENOIR_DMUB); +MODULE_FIRMWARE(FIRMWARE_SIENNA_CICHLID_DMUB); +MODULE_FIRMWARE(FIRMWARE_NAVY_FLOUNDER_DMUB); +MODULE_FIRMWARE(FIRMWARE_GREEN_SARDINE_DMUB); +MODULE_FIRMWARE(FIRMWARE_VANGOGH_DMUB); +MODULE_FIRMWARE(FIRMWARE_DIMGREY_CAVEFISH_DMUB); +MODULE_FIRMWARE(FIRMWARE_BEIGE_GOBY_DMUB); +MODULE_FIRMWARE(FIRMWARE_YELLOW_CARP_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_314_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_315_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN316_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_0_DMCUB); +MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_1_DMCUB); +MODULE_FIRMWARE(FIRMWARE_DCN_35_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_351_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_36_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB); + +/** + * dm_dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command. + * @adev: amdgpu_device pointer + * @notify: dmub notification structure + * + * Dmub AUX or SET_CONFIG command completion processing callback + * Copies dmub notification to DM which is to be read by AUX command. + * issuing thread and also signals the event to wake up the thread. + */ +void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + if (adev->dm.dmub_notify) + memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification)); + if (notify->type == DMUB_NOTIFICATION_AUX_REPLY) + complete(&adev->dm.dmub_aux_transfer_done); +} + +void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + if (!adev || !notify) { + ASSERT(false); + return; + } + + const struct dmub_cmd_fused_request *req = ¬ify->fused_request; + const uint8_t ddc_line = req->u.aux.ddc_line; + + if (ddc_line >= ARRAY_SIZE(adev->dm.fused_io)) { + ASSERT(false); + return; + } + + struct fused_io_sync *sync = &adev->dm.fused_io[ddc_line]; + + static_assert(sizeof(*req) <= sizeof(sync->reply_data), "Size mismatch"); + memcpy(sync->reply_data, req, sizeof(*req)); + complete(&sync->replied); +} + +/** + * dm_register_dmub_notify_callback - Sets callback for DMUB notify + * @adev: amdgpu_device pointer + * @type: Type of dmub notification + * @callback: Dmub interrupt callback function + * @dmub_int_thread_offload: offload indicator + * + * API to register a dmub callback handler for a dmub notification + * Also sets indicator whether callback processing to be offloaded. + * to dmub interrupt handling thread + * Return: true if successfully registered, false if there is existing registration + */ +bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, + enum dmub_notification_type type, + dmub_notify_interrupt_callback_t callback, + bool dmub_int_thread_offload) +{ + if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) { + adev->dm.dmub_callback[type] = callback; + adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; + } else + return false; + + return true; +} + +int dm_dmub_hw_init(struct amdgpu_device *adev) +{ + const struct dmcub_firmware_header_v1_0 *hdr; + struct dmub_srv *dmub_srv = adev->dm.dmub_srv; + struct dmub_srv_fb_info *fb_info = adev->dm.dmub_fb_info; + const struct firmware *dmub_fw = adev->dm.dmub_fw; + struct dc *dc = adev->dm.dc; + struct dmcu *dmcu = adev->dm.dc->res_pool->dmcu; + struct abm *abm = adev->dm.dc->res_pool->abm; + struct dc_context *ctx = adev->dm.dc->ctx; + struct dmub_srv_hw_params hw_params; + enum dmub_status status; + const unsigned char *fw_inst_const, *fw_bss_data; + u32 i, fw_inst_const_size, fw_bss_data_size; + bool has_hw_support; + + if (!dmub_srv) + /* DMUB isn't supported on the ASIC. */ + return 0; + + if (!fb_info) { + drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n"); + return -EINVAL; + } + + if (!dmub_fw) { + /* Firmware required for DMUB support. */ + drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n"); + return -EINVAL; + } + + /* initialize register offsets for ASICs with runtime initialization available */ + if (dmub_srv->hw_funcs.init_reg_offsets) + dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx); + + status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status); + return -EINVAL; + } + + if (!has_hw_support) { + drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n"); + return 0; + } + + /* Reset DMCUB if it was previously running - before we overwrite its memory. */ + status = dmub_srv_hw_reset(dmub_srv); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status); + + hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data; + + fw_inst_const = dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + PSP_HEADER_BYTES_256; + + fw_bss_data = dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + le32_to_cpu(hdr->inst_const_bytes); + + /* Copy firmware and bios info into FB memory. */ + fw_inst_const_size = adev->dm.fw_inst_size; + + fw_bss_data_size = le32_to_cpu(hdr->bss_data_bytes); + + /* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP, + * amdgpu_ucode_init_single_fw will load dmub firmware + * fw_inst_const part to cw0; otherwise, the firmware back door load + * will be done by dm_dmub_hw_init + */ + if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) { + memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const, + fw_inst_const_size); + } + + if (fw_bss_data_size) + memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr, + fw_bss_data, fw_bss_data_size); + + /* Copy firmware bios info into FB memory. */ + memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios, + adev->bios_size); + + /* Reset regions that need to be reset. */ + memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_4_MAILBOX].size); + + memset(fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].size); + + memset(fb_info->fb[DMUB_WINDOW_6_FW_STATE].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_6_FW_STATE].size); + + memset(fb_info->fb[DMUB_WINDOW_SHARED_STATE].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_SHARED_STATE].size); + + /* Initialize hardware. */ + memset(&hw_params, 0, sizeof(hw_params)); + hw_params.soc_fb_info.fb_base = adev->gmc.fb_start; + hw_params.soc_fb_info.fb_offset = adev->vm_manager.vram_base_offset; + + /* backdoor load firmware and trigger dmub running */ + if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) + hw_params.load_inst_const = true; + + if (dmcu) + hw_params.psp_version = dmcu->psp_version; + + for (i = 0; i < fb_info->num_fb; ++i) + hw_params.fb[i] = &fb_info->fb[i]; + + /* Enable usb4 dpia in the FW APU */ + if (dc->caps.is_apu && + dc->res_pool->usb4_dpia_count != 0 && + !dc->debug.dpia_debug.bits.disable_dpia) { + hw_params.dpia_supported = true; + hw_params.disable_dpia = dc->debug.dpia_debug.bits.disable_dpia; + hw_params.dpia_hpd_int_enable_supported = false; + hw_params.enable_non_transparent_setconfig = dc->config.consolidated_dpia_dp_lt; + hw_params.disable_dpia_bw_allocation = !dc->config.usb4_bw_alloc_support; + } + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(3, 5, 0): + case IP_VERSION(3, 5, 1): + case IP_VERSION(3, 6, 0): + case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): + hw_params.ips_sequential_ono = adev->external_rev_id > 0x10; + hw_params.lower_hbr3_phy_ssc = true; + break; + default: + break; + } + + status = dmub_srv_hw_init(dmub_srv, &hw_params); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status); + return -EINVAL; + } + + /* Wait for firmware load to finish. */ + status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); + + /* Init DMCU and ABM if available. */ + if (dmcu && abm) { + dmcu->funcs->dmcu_init(dmcu); + abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu); + } + + if (!adev->dm.dc->ctx->dmub_srv) + adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv); + if (!adev->dm.dc->ctx->dmub_srv) { + drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n"); + return -ENOMEM; + } + + drm_info(adev_to_drm(adev), "DMUB hardware initialized: version=0x%08X\n", + adev->dm.dmcub_fw_version); + + /* Keeping sanity checks off if + * DCN31 >= 4.0.59.0 + * DCN314 >= 8.0.16.0 + * Otherwise, turn on sanity checks + */ + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(3, 1, 2): + case IP_VERSION(3, 1, 3): + if (adev->dm.dmcub_fw_version && + adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && + adev->dm.dmcub_fw_version < DMUB_FW_VERSION(4, 0, 59)) + adev->dm.dc->debug.sanity_checks = true; + break; + case IP_VERSION(3, 1, 4): + if (adev->dm.dmcub_fw_version && + adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && + adev->dm.dmcub_fw_version < DMUB_FW_VERSION(8, 0, 16)) + adev->dm.dc->debug.sanity_checks = true; + break; + default: + break; + } + + return 0; +} + +void dm_dmub_hw_resume(struct amdgpu_device *adev) +{ + struct dmub_srv *dmub_srv = adev->dm.dmub_srv; + enum dmub_status status; + bool init; + int r; + + if (!dmub_srv) { + /* DMUB isn't supported on the ASIC. */ + return; + } + + status = dmub_srv_is_hw_init(dmub_srv, &init); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "DMUB hardware init check failed: %d\n", status); + + if (status == DMUB_STATUS_OK && init) { + /* Wait for firmware load to finish. */ + status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); + } else { + /* Perform the full hardware initialization. */ + r = dm_dmub_hw_init(adev); + if (r) + drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); + } +} + +static enum dmub_status +dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, + enum dmub_gpint_command command_code, + uint16_t param, + uint32_t timeout_us) +{ + union dmub_gpint_data_register reg, test; + uint32_t i; + + /* Assume that VBIOS DMUB is ready to take commands */ + + reg.bits.status = 1; + reg.bits.command_code = command_code; + reg.bits.param = param; + + cgs_write_register(adev->dm.cgs_device, 0x34c0 + 0x01f8, reg.all); + + for (i = 0; i < timeout_us; ++i) { + udelay(1); + + /* Check if our GPINT got acked */ + reg.bits.status = 0; + test = (union dmub_gpint_data_register) + cgs_read_register(adev->dm.cgs_device, 0x34c0 + 0x01f8); + + if (test.all == reg.all) + return DMUB_STATUS_OK; + } + + return DMUB_STATUS_TIMEOUT; +} + +static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) +{ + void *bb; + long long addr; + unsigned int bb_size; + int i = 0; + uint16_t chunk; + enum dmub_gpint_command send_addrs[] = { + DMUB_GPINT__SET_BB_ADDR_WORD0, + DMUB_GPINT__SET_BB_ADDR_WORD1, + DMUB_GPINT__SET_BB_ADDR_WORD2, + DMUB_GPINT__SET_BB_ADDR_WORD3, + }; + enum dmub_status ret; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(4, 0, 1): + bb_size = sizeof(struct dml2_soc_bb); + break; + case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): + bb_size = sizeof(struct dml2_soc_bb); + break; + default: + return NULL; + } + + bb = dm_allocate_gpu_mem(adev, + DC_MEM_ALLOC_TYPE_GART, + bb_size, + &addr); + if (!bb) + return NULL; + + for (i = 0; i < 4; i++) { + /* Extract 16-bit chunk */ + chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF; + /* Send the chunk */ + ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000); + if (ret != DMUB_STATUS_OK) + goto free_bb; + } + + /* Now ask DMUB to copy the bb */ + ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000); + if (ret != DMUB_STATUS_OK) + goto free_bb; + + return bb; + +free_bb: + dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (void *) bb); + return NULL; + +} + +enum dmub_ips_disable_type dm_get_default_ips_mode( + struct amdgpu_device *adev) +{ + enum dmub_ips_disable_type ret = DMUB_IPS_ENABLE; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(3, 5, 0): + case IP_VERSION(3, 6, 0): + case IP_VERSION(3, 5, 1): + ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; + break; + case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): + ret = DMUB_IPS_ENABLE; + break; + default: + /* ASICs older than DCN35 do not have IPSs */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0)) + ret = DMUB_IPS_DISABLE_ALL; + break; + } + + return ret; +} + +static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address) +{ + struct amdgpu_device *adev = ctx; + + return dm_read_reg(adev->dm.dc->ctx, address); +} + +static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, + uint32_t value) +{ + struct amdgpu_device *adev = ctx; + + return dm_write_reg(adev->dm.dc->ctx, address, value); +} + +int dm_dmub_sw_init(struct amdgpu_device *adev) +{ + struct dmub_srv_create_params create_params; + struct dmub_srv_fw_meta_info_params fw_meta_info_params; + struct dmub_srv_region_params region_params; + struct dmub_srv_region_info region_info; + struct dmub_srv_memory_params memory_params; + struct dmub_fw_meta_info fw_info; + struct dmub_srv_fb_info *fb_info; + struct dmub_srv *dmub_srv; + const struct dmcub_firmware_header_v1_0 *hdr; + enum dmub_asic dmub_asic; + enum dmub_status status; + static enum dmub_window_memory_type window_memory_type[DMUB_WINDOW_TOTAL] = { + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_0_INST_CONST */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_1_STACK */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_2_BSS_DATA */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_3_VBIOS */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_4_MAILBOX */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_5_TRACEBUFF */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_6_FW_STATE */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_7_SCRATCH_MEM */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_IB_MEM */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_SHARED_STATE */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_LSDMA_BUFFER */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_CURSOR_OFFLOAD */ + }; + int r; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(2, 1, 0): + dmub_asic = DMUB_ASIC_DCN21; + break; + case IP_VERSION(3, 0, 0): + dmub_asic = DMUB_ASIC_DCN30; + break; + case IP_VERSION(3, 0, 1): + dmub_asic = DMUB_ASIC_DCN301; + break; + case IP_VERSION(3, 0, 2): + dmub_asic = DMUB_ASIC_DCN302; + break; + case IP_VERSION(3, 0, 3): + dmub_asic = DMUB_ASIC_DCN303; + break; + case IP_VERSION(3, 1, 2): + case IP_VERSION(3, 1, 3): + dmub_asic = (adev->external_rev_id == YELLOW_CARP_B0) ? DMUB_ASIC_DCN31B : DMUB_ASIC_DCN31; + break; + case IP_VERSION(3, 1, 4): + dmub_asic = DMUB_ASIC_DCN314; + break; + case IP_VERSION(3, 1, 5): + dmub_asic = DMUB_ASIC_DCN315; + break; + case IP_VERSION(3, 1, 6): + dmub_asic = DMUB_ASIC_DCN316; + break; + case IP_VERSION(3, 2, 0): + dmub_asic = DMUB_ASIC_DCN32; + break; + case IP_VERSION(3, 2, 1): + dmub_asic = DMUB_ASIC_DCN321; + break; + case IP_VERSION(3, 5, 0): + case IP_VERSION(3, 5, 1): + dmub_asic = DMUB_ASIC_DCN35; + break; + case IP_VERSION(3, 6, 0): + dmub_asic = DMUB_ASIC_DCN36; + break; + case IP_VERSION(4, 0, 1): + dmub_asic = DMUB_ASIC_DCN401; + break; + case IP_VERSION(4, 2, 0): + dmub_asic = DMUB_ASIC_DCN42; + break; + case IP_VERSION(4, 2, 1): + dmub_asic = DMUB_ASIC_DCN42B; + break; + default: + /* ASIC doesn't support DMUB. */ + return 0; + } + + hdr = (const struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data; + adev->dm.dmcub_fw_version = le32_to_cpu(hdr->header.ucode_version); + + if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { + adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id = + AMDGPU_UCODE_ID_DMCUB; + adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw = + adev->dm.dmub_fw; + adev->firmware.fw_size += + ALIGN(le32_to_cpu(hdr->inst_const_bytes), PAGE_SIZE); + + drm_info(adev_to_drm(adev), "Loading DMUB firmware via PSP: version=0x%08X\n", + adev->dm.dmcub_fw_version); + } + + + adev->dm.dmub_srv = kzalloc_obj(*adev->dm.dmub_srv); + dmub_srv = adev->dm.dmub_srv; + + if (!dmub_srv) { + drm_err(adev_to_drm(adev), "Failed to allocate DMUB service!\n"); + return -ENOMEM; + } + + memset(&create_params, 0, sizeof(create_params)); + create_params.user_ctx = adev; + create_params.funcs.reg_read = amdgpu_dm_dmub_reg_read; + create_params.funcs.reg_write = amdgpu_dm_dmub_reg_write; + create_params.asic = dmub_asic; + + /* Create the DMUB service. */ + status = dmub_srv_create(dmub_srv, &create_params); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error creating DMUB service: %d\n", status); + return -EINVAL; + } + + /* Extract the FW meta info. */ + memset(&fw_meta_info_params, 0, sizeof(fw_meta_info_params)); + + fw_meta_info_params.inst_const_size = le32_to_cpu(hdr->inst_const_bytes) - + PSP_HEADER_BYTES_256; + fw_meta_info_params.bss_data_size = le32_to_cpu(hdr->bss_data_bytes); + fw_meta_info_params.fw_inst_const = adev->dm.dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + PSP_HEADER_BYTES_256; + fw_meta_info_params.fw_bss_data = fw_meta_info_params.bss_data_size ? adev->dm.dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + le32_to_cpu(hdr->inst_const_bytes) : NULL; + fw_meta_info_params.custom_psp_footer_size = 0; + + status = dmub_srv_get_fw_meta_info_from_raw_fw(&fw_meta_info_params, &fw_info); + if (status != DMUB_STATUS_OK) { + /* Skip returning early, just log the error. */ + drm_err(adev_to_drm(adev), "Error getting DMUB FW meta info: %d\n", status); + } + + /* Calculate the size of all the regions for the DMUB service. */ + memset(®ion_params, 0, sizeof(region_params)); + + region_params.inst_const_size = fw_meta_info_params.inst_const_size; + region_params.bss_data_size = fw_meta_info_params.bss_data_size; + region_params.vbios_size = adev->bios_size; + region_params.fw_bss_data = fw_meta_info_params.fw_bss_data; + region_params.fw_inst_const = fw_meta_info_params.fw_inst_const; + region_params.window_memory_type = window_memory_type; + region_params.fw_info = (status == DMUB_STATUS_OK) ? &fw_info : NULL; + + status = dmub_srv_calc_region_info(dmub_srv, ®ion_params, + ®ion_info); + + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status); + return -EINVAL; + } + + /* + * Allocate a framebuffer based on the total size of all the regions. + * TODO: Move this into GART. + */ + r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_VRAM | + AMDGPU_GEM_DOMAIN_GTT, + &adev->dm.dmub_bo, + &adev->dm.dmub_bo_gpu_addr, + &adev->dm.dmub_bo_cpu_addr); + if (r) + return r; + + /* Rebase the regions on the framebuffer address. */ + memset(&memory_params, 0, sizeof(memory_params)); + memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr; + memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr; + memory_params.region_info = ®ion_info; + memory_params.window_memory_type = window_memory_type; + + adev->dm.dmub_fb_info = kzalloc_obj(*adev->dm.dmub_fb_info); + fb_info = adev->dm.dmub_fb_info; + + if (!fb_info) { + drm_err(adev_to_drm(adev), + "Failed to allocate framebuffer info for DMUB service!\n"); + return -ENOMEM; + } + + status = dmub_srv_calc_mem_info(dmub_srv, &memory_params, fb_info); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error calculating DMUB FB info: %d\n", status); + return -EINVAL; + } + + adev->dm.bb_from_dmub = dm_dmub_get_vbios_bounding_box(adev); + adev->dm.fw_inst_size = fw_meta_info_params.inst_const_size; + + return 0; +} + +int dm_init_microcode(struct amdgpu_device *adev) +{ + char *fw_name_dmub; + int r; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(2, 1, 0): + fw_name_dmub = FIRMWARE_RENOIR_DMUB; + if (ASICREV_IS_GREEN_SARDINE(adev->external_rev_id)) + fw_name_dmub = FIRMWARE_GREEN_SARDINE_DMUB; + break; + case IP_VERSION(3, 0, 0): + if (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(10, 3, 0)) + fw_name_dmub = FIRMWARE_SIENNA_CICHLID_DMUB; + else + fw_name_dmub = FIRMWARE_NAVY_FLOUNDER_DMUB; + break; + case IP_VERSION(3, 0, 1): + fw_name_dmub = FIRMWARE_VANGOGH_DMUB; + break; + case IP_VERSION(3, 0, 2): + fw_name_dmub = FIRMWARE_DIMGREY_CAVEFISH_DMUB; + break; + case IP_VERSION(3, 0, 3): + fw_name_dmub = FIRMWARE_BEIGE_GOBY_DMUB; + break; + case IP_VERSION(3, 1, 2): + case IP_VERSION(3, 1, 3): + fw_name_dmub = FIRMWARE_YELLOW_CARP_DMUB; + break; + case IP_VERSION(3, 1, 4): + fw_name_dmub = FIRMWARE_DCN_314_DMUB; + break; + case IP_VERSION(3, 1, 5): + fw_name_dmub = FIRMWARE_DCN_315_DMUB; + break; + case IP_VERSION(3, 1, 6): + fw_name_dmub = FIRMWARE_DCN316_DMUB; + break; + case IP_VERSION(3, 2, 0): + fw_name_dmub = FIRMWARE_DCN_V3_2_0_DMCUB; + break; + case IP_VERSION(3, 2, 1): + fw_name_dmub = FIRMWARE_DCN_V3_2_1_DMCUB; + break; + case IP_VERSION(3, 5, 0): + fw_name_dmub = FIRMWARE_DCN_35_DMUB; + break; + case IP_VERSION(3, 5, 1): + fw_name_dmub = FIRMWARE_DCN_351_DMUB; + break; + case IP_VERSION(3, 6, 0): + fw_name_dmub = FIRMWARE_DCN_36_DMUB; + break; + case IP_VERSION(4, 0, 1): + fw_name_dmub = FIRMWARE_DCN_401_DMUB; + break; + case IP_VERSION(4, 2, 0): + fw_name_dmub = FIRMWARE_DCN_42_DMUB; + break; + case IP_VERSION(4, 2, 1): + fw_name_dmub = FIRMWARE_DCN_42B_DMUB; + break; + default: + /* ASIC doesn't support DMUB. */ + return 0; + } + r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED, + "%s", fw_name_dmub); + return r; +} + +int amdgpu_dm_process_dmub_aux_transfer_sync( + struct dc_context *ctx, + unsigned int link_index, + struct aux_payload *payload, + enum aux_return_code_type *operation_result) +{ + struct amdgpu_device *adev = ctx->driver_context; + struct dmub_notification *p_notify = adev->dm.dmub_notify; + int ret = -1; + + mutex_lock(&adev->dm.dpia_aux_lock); + if (!dc_process_dmub_aux_transfer_async(ctx->dc, link_index, payload)) { + *operation_result = AUX_RET_ERROR_ENGINE_ACQUIRE; + goto out; + } + + if (!wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { + drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); + *operation_result = AUX_RET_ERROR_TIMEOUT; + goto out; + } + + if (p_notify->result != AUX_RET_SUCCESS) { + /* + * Transient states before tunneling is enabled could + * lead to this error. We can ignore this for now. + */ + if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) { + drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n", + payload->address, payload->length, + p_notify->result); + } + *operation_result = p_notify->result; + goto out; + } + + payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF; + if (adev->dm.dmub_notify->aux_reply.command & 0xF0) + /* The reply is stored in the top nibble of the command. */ + payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF; + + /*write req may receive a byte indicating partially written number as well*/ + if (p_notify->aux_reply.length) + memcpy(payload->data, p_notify->aux_reply.data, + p_notify->aux_reply.length); + + /* success */ + ret = p_notify->aux_reply.length; + *operation_result = p_notify->result; +out: + reinit_completion(&adev->dm.dmub_aux_transfer_done); + mutex_unlock(&adev->dm.dpia_aux_lock); + return ret; +} + +static void abort_fused_io( + struct dc_context *ctx, + const struct dmub_cmd_fused_request *request +) +{ + union dmub_rb_cmd command = { 0 }; + struct dmub_rb_cmd_fused_io *io = &command.fused_io; + + io->header.type = DMUB_CMD__FUSED_IO; + io->header.sub_type = DMUB_CMD__FUSED_IO_ABORT; + io->header.payload_bytes = sizeof(*io) - sizeof(io->header); + io->request = *request; + dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT); +} + +static bool execute_fused_io( + struct amdgpu_device *dev, + struct dc_context *ctx, + union dmub_rb_cmd *commands, + uint8_t count, + uint32_t timeout_us +) +{ + const uint8_t ddc_line = commands[0].fused_io.request.u.aux.ddc_line; + + if (ddc_line >= ARRAY_SIZE(dev->dm.fused_io)) + return false; + + struct fused_io_sync *sync = &dev->dm.fused_io[ddc_line]; + struct dmub_rb_cmd_fused_io *first = &commands[0].fused_io; + const bool result = dm_execute_dmub_cmd_list(ctx, count, commands, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) + && first->header.ret_status + && first->request.status == FUSED_REQUEST_STATUS_SUCCESS; + + if (!result) + return false; + + while (wait_for_completion_timeout(&sync->replied, usecs_to_jiffies(timeout_us))) { + reinit_completion(&sync->replied); + + struct dmub_cmd_fused_request *reply = (struct dmub_cmd_fused_request *) sync->reply_data; + + static_assert(sizeof(*reply) <= sizeof(sync->reply_data), "Size mismatch"); + + if (reply->identifier == first->request.identifier) { + first->request = *reply; + return true; + } + } + + reinit_completion(&sync->replied); + first->request.status = FUSED_REQUEST_STATUS_TIMEOUT; + abort_fused_io(ctx, &first->request); + return false; +} + +bool amdgpu_dm_execute_fused_io( + struct amdgpu_device *dev, + struct dc_link *link, + union dmub_rb_cmd *commands, + uint8_t count, + uint32_t timeout_us) +{ + struct amdgpu_display_manager *dm = &dev->dm; + + mutex_lock(&dm->dpia_aux_lock); + + const bool result = execute_fused_io(dev, link->ctx, commands, count, timeout_us); + + mutex_unlock(&dm->dpia_aux_lock); + return result; +} + +int amdgpu_dm_process_dmub_set_config_sync( + struct dc_context *ctx, + unsigned int link_index, + struct set_config_cmd_payload *payload, + enum set_config_status *operation_result) +{ + struct amdgpu_device *adev = ctx->driver_context; + bool is_cmd_complete; + int ret; + + mutex_lock(&adev->dm.dpia_aux_lock); + is_cmd_complete = dc_process_dmub_set_config_async(ctx->dc, + link_index, payload, adev->dm.dmub_notify); + + if (is_cmd_complete || wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { + ret = 0; + *operation_result = adev->dm.dmub_notify->sc_status; + } else { + drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); + ret = -1; + *operation_result = SET_CONFIG_UNKNOWN_ERROR; + } + + if (!is_cmd_complete) + reinit_completion(&adev->dm.dmub_aux_transfer_done); + mutex_unlock(&adev->dm.dpia_aux_lock); + return ret; +} + +bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) +{ + struct amdgpu_device *adev = ctx->driver_context; + + guard(spinlock_irqsave)(&adev->dm.dmub_lock); + return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type); +} + +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) +{ + struct amdgpu_device *adev = ctx->driver_context; + + guard(spinlock_irqsave)(&adev->dm.dmub_lock); + return dc_dmub_srv_cmd_run_list(ctx->dmub_srv, count, cmd, wait_type); +} 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 new file mode 100644 index 000000000000..a4a03e40ec37 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h @@ -0,0 +1,68 @@ +/* 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_AMDGPU_DM_DMUB_H_ +#define AMDGPU_DM_AMDGPU_DM_DMUB_H_ + +#include "amdgpu.h" + +void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, + enum dmub_notification_type type, + dmub_notify_interrupt_callback_t callback, + bool dmub_int_thread_offload); +int dm_dmub_hw_init(struct amdgpu_device *adev); +void dm_dmub_hw_resume(struct amdgpu_device *adev); +enum dmub_ips_disable_type dm_get_default_ips_mode(struct amdgpu_device *adev); +int dm_dmub_sw_init(struct amdgpu_device *adev); +int dm_init_microcode(struct amdgpu_device *adev); + +#define FIRMWARE_RENOIR_DMUB "amdgpu/renoir_dmcub.bin" +#define FIRMWARE_SIENNA_CICHLID_DMUB "amdgpu/sienna_cichlid_dmcub.bin" +#define FIRMWARE_NAVY_FLOUNDER_DMUB "amdgpu/navy_flounder_dmcub.bin" +#define FIRMWARE_GREEN_SARDINE_DMUB "amdgpu/green_sardine_dmcub.bin" +#define FIRMWARE_VANGOGH_DMUB "amdgpu/vangogh_dmcub.bin" +#define FIRMWARE_DIMGREY_CAVEFISH_DMUB "amdgpu/dimgrey_cavefish_dmcub.bin" +#define FIRMWARE_BEIGE_GOBY_DMUB "amdgpu/beige_goby_dmcub.bin" +#define FIRMWARE_YELLOW_CARP_DMUB "amdgpu/yellow_carp_dmcub.bin" +#define FIRMWARE_DCN_314_DMUB "amdgpu/dcn_3_1_4_dmcub.bin" +#define FIRMWARE_DCN_315_DMUB "amdgpu/dcn_3_1_5_dmcub.bin" +#define FIRMWARE_DCN316_DMUB "amdgpu/dcn_3_1_6_dmcub.bin" +#define FIRMWARE_DCN_V3_2_0_DMCUB "amdgpu/dcn_3_2_0_dmcub.bin" +#define FIRMWARE_DCN_V3_2_1_DMCUB "amdgpu/dcn_3_2_1_dmcub.bin" +#define FIRMWARE_DCN_35_DMUB "amdgpu/dcn_3_5_dmcub.bin" +#define FIRMWARE_DCN_351_DMUB "amdgpu/dcn_3_5_1_dmcub.bin" +#define FIRMWARE_DCN_36_DMUB "amdgpu/dcn_3_6_dmcub.bin" +#define FIRMWARE_DCN_401_DMUB "amdgpu/dcn_4_0_1_dmcub.bin" +#define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin" +#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin" +#define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin" +#define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin" + +#endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */ -- cgit v1.2.3 From 0618dec49415706c9701ce6719b5081593037210 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 11:23:59 -0600 Subject: drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq Move HPD handling (workqueue creation, debounce, handler registration) and IRQ handler callbacks (vblank, pflip, vupdate, vline0, outbox) from amdgpu_dm.c into the existing amdgpu_dm_irq.c. This keeps all IRQ-related code together rather than creating additional files. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1542 +------------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 4 + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 1501 ++++++++++++++++++- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 19 + 4 files changed, 1552 insertions(+), 1514 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 f5766d083213..87a849152d81 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -193,10 +193,6 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, - enum dc_detect_reason reason); -static void handle_hpd_rx_irq(void *param); - static bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); @@ -291,27 +287,6 @@ static int dm_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static struct amdgpu_crtc * -get_crtc_by_otg_inst(struct amdgpu_device *adev, - int otg_inst) -{ - struct drm_device *dev = adev_to_drm(adev); - struct drm_crtc *crtc; - struct amdgpu_crtc *amdgpu_crtc; - - if (WARN_ON(otg_inst == -1)) - return adev->mode_info.crtcs[0]; - - list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { - amdgpu_crtc = to_amdgpu_crtc(crtc); - - if (amdgpu_crtc->otg_inst == otg_inst) - return amdgpu_crtc; - } - - return NULL; -} - static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state) { @@ -378,566 +353,6 @@ static inline bool update_planes_and_stream_adapter(struct dc *dc, stream_update); } -/** - * dm_pflip_high_irq() - Handle pageflip interrupt - * @interrupt_params: ignored - * - * Handles the pageflip interrupt by notifying all interested parties - * that the pageflip has been completed. - */ -static void dm_pflip_high_irq(void *interrupt_params) -{ - struct amdgpu_crtc *amdgpu_crtc; - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct drm_device *dev = adev_to_drm(adev); - unsigned long flags; - struct drm_pending_vblank_event *e; - u32 vpos, hpos, v_blank_start, v_blank_end; - bool vrr_active; - - amdgpu_crtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP); - - /* IRQ could occur when in initial stage */ - /* TODO work and BO cleanup */ - if (amdgpu_crtc == NULL) { - drm_dbg_state(dev, "CRTC is null, returning.\n"); - return; - } - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - - if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) { - drm_dbg_state(dev, - "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n", - amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED, - amdgpu_crtc->crtc_id, amdgpu_crtc); - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - return; - } - - /* page flip completed. */ - e = amdgpu_crtc->event; - amdgpu_crtc->event = NULL; - - WARN_ON(!e); - - vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc); - - /* Fixed refresh rate, or VRR scanout position outside front-porch? */ - if (!vrr_active || - !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start, - &v_blank_end, &hpos, &vpos) || - (vpos < v_blank_start)) { - /* Update to correct count and vblank timestamp if racing with - * vblank irq. This also updates to the correct vblank timestamp - * even in VRR mode, as scanout is past the front-porch atm. - */ - drm_crtc_accurate_vblank_count(&amdgpu_crtc->base); - - /* Wake up userspace by sending the pageflip event with proper - * count and timestamp of vblank of flip completion. - */ - if (e) { - drm_crtc_send_vblank_event(&amdgpu_crtc->base, e); - - /* Event sent, so done with vblank for this flip */ - drm_crtc_vblank_put(&amdgpu_crtc->base); - } - } else if (e) { - /* VRR active and inside front-porch: vblank count and - * timestamp for pageflip event will only be up to date after - * drm_crtc_handle_vblank() has been executed from late vblank - * irq handler after start of back-porch (vline 0). We queue the - * pageflip event for send-out by drm_crtc_handle_vblank() with - * updated timestamp and count, once it runs after us. - * - * We need to open-code this instead of using the helper - * drm_crtc_arm_vblank_event(), as that helper would - * call drm_crtc_accurate_vblank_count(), which we must - * not call in VRR mode while we are in front-porch! - */ - - /* sequence will be replaced by real count during send-out. */ - e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base); - e->pipe = amdgpu_crtc->crtc_id; - - list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list); - e = NULL; - } - - /* Keep track of vblank of this flip for flip throttling. We use the - * cooked hw counter, as that one incremented at start of this vblank - * of pageflip completion, so last_flip_vblank is the forbidden count - * for queueing new pageflips if vsync + VRR is enabled. - */ - amdgpu_crtc->dm_irq_params.last_flip_vblank = - amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base); - - amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - - drm_dbg_state(dev, - "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", - amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e); -} - -static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) -{ - struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); - struct amdgpu_device *adev = work->adev; - struct dc_stream_state *stream = work->stream; - struct dc_crtc_timing_adjust *adjust = work->adjust; - - mutex_lock(&adev->dm.dc_lock); - dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust); - mutex_unlock(&adev->dm.dc_lock); - - dc_stream_release(stream); - kfree(work->adjust); - kfree(work); -} - -static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, - struct dc_stream_state *stream, - struct dc_crtc_timing_adjust *adjust) -{ - struct vupdate_offload_work *offload_work = kzalloc_obj(*offload_work, - GFP_NOWAIT); - if (!offload_work) { - drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n"); - return; - } - - struct dc_crtc_timing_adjust *adjust_copy = kzalloc_obj(*adjust_copy, - GFP_NOWAIT); - if (!adjust_copy) { - drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n"); - kfree(offload_work); - return; - } - - dc_stream_retain(stream); - memcpy(adjust_copy, adjust, sizeof(*adjust_copy)); - - INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update); - offload_work->adev = adev; - offload_work->stream = stream; - offload_work->adjust = adjust_copy; - - queue_work(system_percpu_wq, &offload_work->work); -} - -static void dm_vupdate_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - struct drm_device *drm_dev; - struct drm_vblank_crtc *vblank; - ktime_t frame_duration_ns, previous_timestamp; - unsigned long flags; - int vrr_active; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); - - if (acrtc) { - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - drm_dev = acrtc->base.dev; - vblank = drm_crtc_vblank_crtc(&acrtc->base); - previous_timestamp = atomic64_read(&irq_params->previous_timestamp); - frame_duration_ns = vblank->time - previous_timestamp; - - if (frame_duration_ns > 0) { - trace_amdgpu_refresh_rate_track(acrtc->base.index, - frame_duration_ns, - ktime_divns(NSEC_PER_SEC, frame_duration_ns)); - atomic64_set(&irq_params->previous_timestamp, vblank->time); - } - - drm_dbg_vbl(drm_dev, - "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, - vrr_active); - - /* Core vblank handling is done here after end of front-porch in - * vrr mode, as vblank timestamping will give valid results - * while now done after front-porch. This will also deliver - * page-flip completion events that have been queued to us - * if a pageflip happened inside front-porch. - */ - if (vrr_active && acrtc->dm_irq_params.stream) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state - == VRR_STATE_ACTIVE_VARIABLE; - - amdgpu_dm_crtc_handle_vblank(acrtc); - - /* BTR processing for pre-DCE12 ASICs */ - if (adev->family < AMDGPU_FAMILY_AI) { - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - mod_freesync_handle_v_update( - adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - } - } - } -} - -/** - * dm_crtc_high_irq() - Handles CRTC interrupt - * @interrupt_params: used for determining the CRTC instance - * - * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK - * event handler. - */ -static void dm_crtc_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct drm_writeback_job *job; - struct amdgpu_crtc *acrtc; - unsigned long flags; - int vrr_active; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); - if (!acrtc) - return; - - if (acrtc->wb_conn) { - spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); - - if (acrtc->wb_pending) { - job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, - struct drm_writeback_job, - list_entry); - acrtc->wb_pending = false; - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - - if (job) { - unsigned int v_total, refresh_hz; - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - - 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); - - drm_writeback_signal_completion(acrtc->wb_conn, 0); - dc_stream_fc_disable_writeback(adev->dm.dc, - acrtc->dm_irq_params.stream, 0); - } - } else - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - } - - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - - drm_dbg_vbl(adev_to_drm(adev), - "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id, - vrr_active, acrtc->dm_irq_params.active_planes); - - /** - * Core vblank handling at start of front-porch is only possible - * in non-vrr mode, as only there vblank timestamping will give - * valid results while done in front-porch. Otherwise defer it - * to dm_vupdate_high_irq after end of front-porch. - */ - if (!vrr_active) - amdgpu_dm_crtc_handle_vblank(acrtc); - - /** - * Following stuff must happen at start of vblank, for crc - * computation and below-the-range btr support in vrr mode. - */ - amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); - - /* BTR updates need to happen before VUPDATE on Vega and above. */ - if (adev->family < AMDGPU_FAMILY_AI) - return; - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - - if (acrtc->dm_irq_params.stream && - acrtc->dm_irq_params.vrr_params.supported) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE; - - mod_freesync_handle_v_update(adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - /* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */ - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - } - - /* - * If there aren't any active_planes then DCH HUBP may be clock-gated. - * In that case, pageflip completion interrupts won't fire and pageflip - * completion events won't get delivered. Prevent this by sending - * pending pageflip events from here if a flip is still pending. - * - * If any planes are enabled, use dm_pflip_high_irq() instead, to - * avoid race conditions between flip programming and completion, - * which could cause too early flip completion events. - */ - if (adev->family >= AMDGPU_FAMILY_RV && - acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && - acrtc->dm_irq_params.active_planes == 0) { - if (acrtc->event) { - drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); - acrtc->event = NULL; - drm_crtc_vblank_put(&acrtc->base); - } - acrtc->pflip_status = AMDGPU_FLIP_NONE; - } - - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); -} - -#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) -/** - * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for - * DCN generation ASICs - * @interrupt_params: interrupt parameters - * - * Used to set crc window/read out crc value at vertical line 0 position - */ -static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0); - - if (!acrtc) - return; - - amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base); -} -#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ - -/** - * dmub_hpd_callback - DMUB HPD interrupt processing callback. - * @adev: amdgpu_device pointer - * @notify: dmub notification structure - * - * Dmub Hpd interrupt processing callback. Gets displayindex through the - * ink index and calls helper to do the processing. - */ -static void dmub_hpd_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_dm_connector *hpd_aconnector = NULL; - struct drm_connector *connector; - struct drm_connector_list_iter iter; - struct dc_link *link; - u8 link_index = 0; - struct drm_device *dev; - - if (adev == NULL) - return; - - if (notify == NULL) { - drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL"); - return; - } - - if (notify->link_index > adev->dm.dc->link_count) { - drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index); - return; - } - - /* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */ - if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) { - drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n"); - return; - } - - link_index = notify->link_index; - link = adev->dm.dc->links[link_index]; - dev = adev->dm.ddev; - - drm_connector_list_iter_begin(dev, &iter); - drm_for_each_connector_iter(connector, &iter) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (link && aconnector->dc_link == link) { - if (notify->type == DMUB_NOTIFICATION_HPD) - drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index); - else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) - drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index); - else - drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n", - notify->type, link_index); - - hpd_aconnector = aconnector; - break; - } - } - drm_connector_list_iter_end(&iter); - - if (hpd_aconnector) { - if (notify->type == DMUB_NOTIFICATION_HPD) { - if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) - drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); - handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); - } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { - handle_hpd_rx_irq(hpd_aconnector); - } - } -} - -/** - * dmub_hpd_sense_callback - DMUB HPD sense processing callback. - * @adev: amdgpu_device pointer - * @notify: dmub notification structure - * - * HPD sense changes can occur during low power states and need to be - * notified from firmware to driver. - */ -static void dmub_hpd_sense_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); -} - -static void dm_handle_hpd_work(struct work_struct *work) -{ - struct dmub_hpd_work *dmub_hpd_wrk; - - dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work); - - if (!dmub_hpd_wrk->dmub_notify) { - drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL"); - return; - } - - if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) { - dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev, - dmub_hpd_wrk->dmub_notify); - } - - kfree(dmub_hpd_wrk->dmub_notify); - kfree(dmub_hpd_wrk); - -} - -static const char *dmub_notification_type_str(enum dmub_notification_type e) -{ - switch (e) { - case DMUB_NOTIFICATION_NO_DATA: - return "NO_DATA"; - case DMUB_NOTIFICATION_AUX_REPLY: - return "AUX_REPLY"; - case DMUB_NOTIFICATION_HPD: - return "HPD"; - case DMUB_NOTIFICATION_HPD_IRQ: - return "HPD_IRQ"; - case DMUB_NOTIFICATION_SET_CONFIG_REPLY: - return "SET_CONFIG_REPLY"; - case DMUB_NOTIFICATION_DPIA_NOTIFICATION: - return "DPIA_NOTIFICATION"; - case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY: - return "HPD_SENSE_NOTIFY"; - case DMUB_NOTIFICATION_FUSED_IO: - return "FUSED_IO"; - default: - return ""; - } -} - -#define DMUB_TRACE_MAX_READ 64 -/** - * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt - * @interrupt_params: used for determining the Outbox instance - * - * Handles the Outbox Interrupt - * event handler. - */ -static void dm_dmub_outbox1_low_irq(void *interrupt_params) -{ - struct dmub_notification notify = {0}; - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_display_manager *dm = &adev->dm; - struct dmcub_trace_buf_entry entry = { 0 }; - u32 count = 0; - struct dmub_hpd_work *dmub_hpd_wrk; - - do { - if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) { - trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count, - entry.param0, entry.param1); - - drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n", - entry.trace_code, entry.tick_count, entry.param0, entry.param1); - } else - break; - - count++; - - } while (count <= DMUB_TRACE_MAX_READ); - - if (count > DMUB_TRACE_MAX_READ) - drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ"); - - if (dc_enable_dmub_notifications(adev->dm.dc) && - irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) { - - do { - dc_stat_get_dmub_notification(adev->dm.dc, ¬ify); - if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) { - drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type); - continue; - } - if (!dm->dmub_callback[notify.type]) { - drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n", - dmub_notification_type_str(notify.type)); - continue; - } - if (dm->dmub_thread_offload[notify.type] == true) { - dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, - GFP_ATOMIC); - if (!dmub_hpd_wrk) { - drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk"); - return; - } - dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification), - GFP_ATOMIC); - if (!dmub_hpd_wrk->dmub_notify) { - kfree(dmub_hpd_wrk); - drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify"); - return; - } - INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work); - dmub_hpd_wrk->adev = adev; - queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work); - } else { - dm->dmub_callback[notify.type](adev, ¬ify); - } - } while (notify.pending_notification); - } -} - static int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, enum amd_clockgating_state state) { @@ -1070,168 +485,23 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_ } -static void force_connector_state( - struct amdgpu_dm_connector *aconnector, - enum drm_connector_force force_state) -{ - struct drm_connector *connector = &aconnector->base; - - mutex_lock(&connector->dev->mode_config.mutex); - aconnector->base.force = force_state; - mutex_unlock(&connector->dev->mode_config.mutex); +struct amdgpu_stutter_quirk { + u16 chip_vendor; + u16 chip_device; + u16 subsys_vendor; + u16 subsys_device; + u8 revision; +}; - mutex_lock(&aconnector->hpd_lock); - drm_kms_helper_connector_hotplug_event(connector); - mutex_unlock(&aconnector->hpd_lock); -} +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 void dm_handle_hpd_rx_offload_work(struct work_struct *work) +static bool dm_should_disable_stutter(struct pci_dev *pdev) { - struct hpd_rx_irq_offload_work *offload_work; - struct amdgpu_dm_connector *aconnector; - struct dc_link *dc_link; - struct amdgpu_device *adev; - enum dc_connection_type new_connection_type = dc_connection_none; - unsigned long flags; - union test_response test_response; - - memset(&test_response, 0, sizeof(test_response)); - - offload_work = container_of(work, struct hpd_rx_irq_offload_work, work); - aconnector = offload_work->offload_wq->aconnector; - adev = offload_work->adev; - - if (!aconnector) { - drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work"); - goto skip; - } - - dc_link = aconnector->dc_link; - - mutex_lock(&aconnector->hpd_lock); - if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) - drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); - mutex_unlock(&aconnector->hpd_lock); - - if (new_connection_type == dc_connection_none) - goto skip; - - if (amdgpu_in_reset(adev)) - goto skip; - - if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || - offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { - dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT); - spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); - offload_work->offload_wq->is_handling_mst_msg_rdy_event = false; - spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); - goto skip; - } - - mutex_lock(&adev->dm.dc_lock); - if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) { - dc_link_dp_handle_automated_test(dc_link); - - if (aconnector->timing_changed) { - /* force connector disconnect and reconnect */ - force_connector_state(aconnector, DRM_FORCE_OFF); - msleep(100); - force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED); - } - - test_response.bits.ACK = 1; - - core_link_write_dpcd( - dc_link, - DP_TEST_RESPONSE, - &test_response.raw, - sizeof(test_response)); - } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) && - dc_link_check_link_loss_status(dc_link, &offload_work->data) && - dc_link_dp_allow_hpd_rx_irq(dc_link)) { - /* offload_work->data is from handle_hpd_rx_irq-> - * schedule_hpd_rx_offload_work.this is defer handle - * for hpd short pulse. upon here, link status may be - * changed, need get latest link status from dpcd - * registers. if link status is good, skip run link - * training again. - */ - union hpd_irq_data irq_data; - - memset(&irq_data, 0, sizeof(irq_data)); - - /* before dc_link_dp_handle_link_loss, allow new link lost handle - * request be added to work queue if link lost at end of dc_link_ - * dp_handle_link_loss - */ - spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); - offload_work->offload_wq->is_handling_link_loss = false; - spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); - - if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) && - dc_link_check_link_loss_status(dc_link, &irq_data)) - dc_link_dp_handle_link_loss(dc_link); - } - mutex_unlock(&adev->dm.dc_lock); - -skip: - kfree(offload_work); - -} - -static struct hpd_rx_irq_offload_work_queue *hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - int max_caps = dc->caps.max_links; - int i = 0; - struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL; - - hpd_rx_offload_wq = kzalloc_objs(*hpd_rx_offload_wq, max_caps); - - if (!hpd_rx_offload_wq) - return NULL; - - - for (i = 0; i < max_caps; i++) { - hpd_rx_offload_wq[i].wq = - create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq"); - - if (hpd_rx_offload_wq[i].wq == NULL) { - drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!"); - goto out_err; - } - - spin_lock_init(&hpd_rx_offload_wq[i].offload_lock); - } - - return hpd_rx_offload_wq; - -out_err: - for (i = 0; i < max_caps; i++) { - if (hpd_rx_offload_wq[i].wq) - destroy_workqueue(hpd_rx_offload_wq[i].wq); - } - kfree(hpd_rx_offload_wq); - return NULL; -} - -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 bool dm_should_disable_stutter(struct pci_dev *pdev) -{ - const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; + const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; while (p && p->chip_device != 0) { if (pdev->vendor == p->chip_vendor && @@ -1624,7 +894,7 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) dc_hardware_init(adev->dm.dc); - adev->dm.hpd_rx_offload_wq = hpd_rx_irq_create_workqueue(adev); + adev->dm.hpd_rx_offload_wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); if (!adev->dm.hpd_rx_offload_wq) { drm_err(adev_to_drm(adev), "failed to create hpd rx offload workqueue.\n"); goto error; @@ -1711,7 +981,7 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } /* Enable outbox notification only after IRQ handlers are registered and DMUB is alive. * It is expected that DMUB will resend any pending notifications at this point. Note - * that hpd and hpd_irq handler registration are deferred to register_hpd_handlers() to + * that hpd and hpd_irq handler registration are deferred to amdgpu_dm_register_hpd_handlers() to * align legacy interface initialization sequence. Connection status will be proactivly * detected once in the amdgpu_dm_initialize_drm_device. */ @@ -2461,7 +1731,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, int i = 0; for (i = 0; i < state->stream_count; i++) { - acrtc = get_crtc_by_otg_inst( + acrtc = amdgpu_dm_get_crtc_by_otg_inst( adev, state->stream_status[i].primary_otg_inst); if (acrtc && state->stream_status[i].plane_count != 0) { @@ -2538,16 +1808,6 @@ static enum dc_status amdgpu_dm_commit_zero_streams(struct dc *dc) return dc_commit_streams(dc, ¶ms); } -static void hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) -{ - int i; - - if (dm->hpd_rx_offload_wq) { - for (i = 0; i < dm->dc->caps.max_links; i++) - flush_workqueue(dm->hpd_rx_offload_wq[i].wq); - } -} - static int dm_cache_state(struct amdgpu_device *adev) { int r; @@ -2643,7 +1903,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) amdgpu_dm_irq_suspend(adev); - hpd_rx_irq_work_suspend(dm); + amdgpu_dm_hpd_rx_irq_work_suspend(dm); return 0; } @@ -2669,7 +1929,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) scoped_guard(mutex, &dm->dc_lock) amdgpu_dm_ism_force_full_power(dm); - hpd_rx_irq_work_suspend(dm); + amdgpu_dm_hpd_rx_irq_work_suspend(dm); dc_set_power_state(dm->dc, DC_ACPI_CM_POWER_STATE_D3); @@ -2700,7 +1960,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } -static void emulated_link_detect(struct dc_link *link) +void amdgpu_dm_emulated_link_detect(struct dc_link *link) { struct dc_sink_init_data sink_init_data = { 0 }; struct display_sink_capability sink_caps = { 0 }; @@ -2821,8 +2081,8 @@ static void dm_gpureset_commit_state(struct dc_state *dc_state, } } -static void apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, - struct dc_sink *sink) +void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, + struct dc_sink *sink) { struct dc_panel_patch *ppatch = NULL; @@ -3064,14 +2324,14 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(aconnector->dc_link); + amdgpu_dm_emulated_link_detect(aconnector->dc_link); } else { guard(mutex)(&dm->dc_lock); dc_exit_ips_for_hw_access(dm->dc); ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_RESUMEFROMS3S4); if (ret) { /* w/a delay for certain panels */ - apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); } } @@ -3392,750 +2652,6 @@ void amdgpu_dm_update_connector_after_detect( mutex_unlock(&dev->mode_config.mutex); } -static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) -{ - if (!sink1 || !sink2) - return false; - if (sink1->sink_signal != sink2->sink_signal) - return false; - - if (sink1->dc_edid.length != sink2->dc_edid.length) - return false; - - if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid, - sink1->dc_edid.length) != 0) - return false; - return true; -} - - -/** - * DOC: hdmi_hpd_debounce_work - * - * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD - * (such as during power save transitions), this delay determines how long to - * wait before processing the HPD event. This allows distinguishing between a - * physical unplug (>hdmi_hpd_debounce_delay) - * and a spontaneous RX HPD toggle (base; - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dc *dc = aconnector->dc_link->ctx->dc; - bool fake_reconnect = false; - bool reallow_idle = false; - bool ret = false; - guard(mutex)(&aconnector->hpd_lock); - - /* Re-detect the display */ - scoped_guard(mutex, &adev->dm.dc_lock) { - if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) { - dc_allow_idle_optimizations(dc, false); - reallow_idle = true; - } - ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); - } - - if (ret) { - /* Apply workaround delay for certain panels */ - apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); - /* Compare sinks to determine if this was a spontaneous HPD toggle */ - if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) { - /* - * Sinks match - this was a spontaneous HDMI HPD toggle. - */ - drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n"); - fake_reconnect = true; - } - - /* Update connector state */ - amdgpu_dm_update_connector_after_detect(aconnector); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - /* Only notify OS if sink actually changed */ - if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED) - drm_kms_helper_hotplug_event(dev); - } - - /* Release the cached sink reference */ - if (aconnector->hdmi_prev_sink) { - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = NULL; - } - - scoped_guard(mutex, &adev->dm.dc_lock) { - if (reallow_idle && dc->caps.ips_support) - dc_allow_idle_optimizations(dc, true); - } -} - -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, - enum dc_detect_reason reason) -{ - struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; - enum dc_connection_type new_connection_type = dc_connection_none; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); - struct dc *dc = aconnector->dc_link->ctx->dc; - bool ret = false; - bool debounce_required = false; - - if (adev->dm.disable_hpd_irq) - return; - - /* - * In case of failure or MST no need to update connector status or notify the OS - * since (for MST case) MST does this in its own context. - */ - guard(mutex)(&aconnector->hpd_lock); - - if (adev->dm.hdcp_workqueue) { - hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index); - dm_con_state->update_hdcp = true; - } - if (aconnector->fake_enable) - aconnector->fake_enable = false; - - aconnector->timing_changed = false; - - if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type)) - drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); - - /* - * Check for HDMI disconnect with debounce enabled. - */ - debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 && - dc_is_hdmi_signal(aconnector->dc_link->connector_signal) && - new_connection_type == dc_connection_none && - aconnector->dc_link->local_sink != NULL); - - if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(aconnector->dc_link); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || - reason == DETECT_REASON_HPDRX) - drm_kms_helper_connector_hotplug_event(connector); - } else if (debounce_required) { - /* - * HDMI disconnect detected - schedule delayed work instead of - * processing immediately. This allows us to coalesce spurious - * HDMI signals from physical unplugs. - */ - drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n", - aconnector->hdmi_hpd_debounce_delay_ms); - - /* Cache the current sink for later comparison */ - if (aconnector->hdmi_prev_sink) - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink; - if (aconnector->hdmi_prev_sink) - dc_sink_retain(aconnector->hdmi_prev_sink); - - /* Schedule delayed detection. */ - if (mod_delayed_work(system_percpu_wq, - &aconnector->hdmi_hpd_debounce_work, - msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms))) - drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n"); - - } else { - - /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */ - if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work)) - return; - - scoped_guard(mutex, &adev->dm.dc_lock) { - dc_exit_ips_for_hw_access(dc); - ret = dc_link_detect(aconnector->dc_link, reason); - } - if (ret) { - /* w/a delay for certain panels */ - apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); - amdgpu_dm_update_connector_after_detect(aconnector); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || - reason == DETECT_REASON_HPDRX) - drm_kms_helper_connector_hotplug_event(connector); - } - } -} - -static void handle_hpd_irq(void *param) -{ - struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; - - handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); - -} - -static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, - union hpd_irq_data hpd_irq_data) -{ - struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work); - - if (!offload_work) { - drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n"); - return; - } - - INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); - offload_work->data = hpd_irq_data; - offload_work->offload_wq = offload_wq; - offload_work->adev = adev; - - queue_work(offload_wq->wq, &offload_work->work); - drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); -} - -static void handle_hpd_rx_irq(void *param) -{ - struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; - struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; - struct dc_link *dc_link = aconnector->dc_link; - bool is_mst_root_connector = aconnector->mst_mgr.mst_state; - bool result = false; - struct amdgpu_device *adev = drm_to_adev(dev); - union hpd_irq_data hpd_irq_data; - bool link_loss = false; - bool has_left_work = false; - int idx = dc_link->link_index; - struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; - - memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); - - if (adev->dm.disable_hpd_irq) - return; - - /* - * TODO:Temporary add mutex to protect hpd interrupt not have a gpio - * conflict, after implement i2c helper, this mutex should be - * retired. - */ - mutex_lock(&aconnector->hpd_lock); - - result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data, - &link_loss, true, &has_left_work); - - if (!has_left_work) - goto out; - - if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { - schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); - goto out; - } - - if (dc_link_dp_allow_hpd_rx_irq(dc_link)) { - if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || - hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { - bool skip = false; - - /* - * DOWN_REP_MSG_RDY is also handled by polling method - * mgr->cbs->poll_hpd_irq() - */ - spin_lock(&offload_wq->offload_lock); - skip = offload_wq->is_handling_mst_msg_rdy_event; - - if (!skip) - offload_wq->is_handling_mst_msg_rdy_event = true; - - spin_unlock(&offload_wq->offload_lock); - - if (!skip) - schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); - - goto out; - } - - if (link_loss) { - bool skip = false; - - spin_lock(&offload_wq->offload_lock); - skip = offload_wq->is_handling_link_loss; - - if (!skip) - offload_wq->is_handling_link_loss = true; - - spin_unlock(&offload_wq->offload_lock); - - if (!skip) - schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); - - goto out; - } - } - -out: - if (result && !is_mst_root_connector) { - /* Downstream Port status changed. */ - handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); - } - if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { - if (adev->dm.hdcp_workqueue) - hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index); - } - - if (dc_link->type != dc_connection_mst_branch) - drm_dp_cec_irq(&aconnector->dm_dp_aux.aux); - - mutex_unlock(&aconnector->hpd_lock); -} - -static int register_hpd_handlers(struct amdgpu_device *adev) -{ - struct drm_device *dev = adev_to_drm(adev); - struct drm_connector *connector; - struct amdgpu_dm_connector *aconnector; - const struct dc_link *dc_link; - struct dc_interrupt_params int_params = {0}; - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - if (dc_is_dmub_outbox_supported(adev->dm.dc)) { - if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, - dmub_hpd_callback, true)) { - drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); - return -EINVAL; - } - - if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, - dmub_hpd_callback, true)) { - drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); - return -EINVAL; - } - - if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, - dmub_hpd_sense_callback, true)) { - drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); - return -EINVAL; - } - } - - list_for_each_entry(connector, - &dev->mode_config.connector_list, head) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - dc_link = aconnector->dc_link; - - if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { - int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; - int_params.irq_source = dc_link->irq_source_hpd; - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_HPD1 || - int_params.irq_source > DC_IRQ_SOURCE_HPD6) { - drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n"); - return -EINVAL; - } - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - handle_hpd_irq, (void *) aconnector)) - return -ENOMEM; - } - - if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - - /* Also register for DP short pulse (hpd_rx). */ - int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; - int_params.irq_source = dc_link->irq_source_hpd_rx; - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || - int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { - drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n"); - return -EINVAL; - } - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - handle_hpd_rx_irq, (void *) aconnector)) - return -ENOMEM; - } - } - return 0; -} - -/* Register IRQ sources and initialize IRQ callbacks */ -static int dce110_register_irq_handlers(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - struct common_irq_params *c_irq_params; - struct dc_interrupt_params int_params = {0}; - int r; - int i; - unsigned int src_id; - unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY; - /* Use different interrupts for VBLANK on DCE 6 vs. newer. */ - const unsigned int vblank_d1 = - adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0 - ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1; - - if (adev->family >= AMDGPU_FAMILY_AI) - client_id = SOC15_IH_CLIENTID_DCE; - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - /* - * Actions of amdgpu_irq_add_id(): - * 1. Register a set() function with base driver. - * Base driver will call set() function to enable/disable an - * interrupt in DC hardware. - * 2. Register amdgpu_dm_irq_handler(). - * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts - * coming from DC hardware. - * amdgpu_dm_irq_handler() will re-direct the interrupt to DC - * for acknowledging and handling. - */ - - /* Use VBLANK interrupt */ - for (i = 0; i < adev->mode_info.num_crtc; i++) { - src_id = vblank_d1 + i; - r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, src_id, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - - if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) { - /* Use VUPDATE interrupt */ - for (i = 0; i < adev->mode_info.num_crtc; i++) { - src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2; - r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, src_id, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || - int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { - drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vupdate_params[ - int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_vupdate_high_irq, c_irq_params)) - return -ENOMEM; - } - } - - /* Use GRPH_PFLIP interrupt */ - for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; - i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { - r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* HPD */ - r = amdgpu_irq_add_id(adev, client_id, - VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); - return r; - } - - r = register_hpd_handlers(adev); - - return r; -} - -/* Register IRQ sources and initialize IRQ callbacks */ -static int dcn10_register_irq_handlers(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - struct common_irq_params *c_irq_params; - struct dc_interrupt_params int_params = {0}; - int r; - int i; -#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) - static const unsigned int vrtl_int_srcid[] = { - DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL - }; -#endif - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - /* - * Actions of amdgpu_irq_add_id(): - * 1. Register a set() function with base driver. - * Base driver will call set() function to enable/disable an - * interrupt in DC hardware. - * 2. Register amdgpu_dm_irq_handler(). - * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts - * coming from DC hardware. - * amdgpu_dm_irq_handler() will re-direct the interrupt to DC - * for acknowledging and handling. - */ - - /* Use VSTARTUP interrupt */ - for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; - i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* Use otg vertical line interrupt */ -#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) - for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, - vrtl_int_srcid[i], &adev->vline0_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 || - int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) { - drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vline0_params[int_params.irq_source - - DC_IRQ_SOURCE_DC1_VLINE0]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_dcn_vertical_interrupt0_high_irq, - c_irq_params)) - return -ENOMEM; - } -#endif - - /* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to - * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx - * to trigger at end of each vblank, regardless of state of the lock, - * matching DCE behaviour. - */ - for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT; - i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || - int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { - drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_vupdate_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* Use GRPH_PFLIP interrupt */ - for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; - i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* HPD */ - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, - &adev->hpd_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); - return r; - } - - r = register_hpd_handlers(adev); - - return r; -} -/* Register Outbox IRQ sources and initialize IRQ callbacks */ -static int register_outbox_irq_handlers(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - struct common_irq_params *c_irq_params; - struct dc_interrupt_params int_params = {0}; - int r, i; - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT, - &adev->dmub_outbox_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n"); - return r; - } - - if (dc->ctx->dmub_srv) { - i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT; - int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - c_irq_params = &adev->dm.dmub_outbox_params[0]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_dmub_outbox1_low_irq, c_irq_params)) - return -ENOMEM; - } - - return 0; -} - /* * Acquires the lock for the atomic state object and returns * the new atomic state. @@ -4441,7 +2957,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): case IP_VERSION(4, 2, 1): - if (register_outbox_irq_handlers(dm->adev)) { + if (amdgpu_dm_register_outbox_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; } @@ -4554,7 +3070,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(link); + amdgpu_dm_emulated_link_detect(link); amdgpu_dm_update_connector_after_detect(aconnector); } else { bool ret = false; @@ -4618,7 +3134,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case CHIP_VEGA10: case CHIP_VEGA12: case CHIP_VEGA20: - if (dce110_register_irq_handlers(dm->adev)) { + if (amdgpu_dm_dce110_register_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; } @@ -4648,7 +3164,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): case IP_VERSION(4, 2, 1): - if (dcn10_register_irq_handlers(dm->adev)) { + if (amdgpu_dm_dcn10_register_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; } @@ -7760,7 +6276,7 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, if (amdgpu_hdmi_hpd_debounce_delay_ms) { aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); - INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work); + INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work); aconnector->hdmi_prev_sink = NULL; } else { aconnector->hdmi_hpd_debounce_delay_ms = 0; 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 f0e91a0a15fc..505164364e61 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1167,4 +1167,8 @@ int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); void retrieve_dmi_info(struct amdgpu_display_manager *dm); +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); + #endif /* __AMDGPU_DM_H__ */ 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 e49803a90eda..36c0177f5eb0 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 @@ -26,10 +26,24 @@ #include "dm_services_types.h" #include "dc.h" +#include "dc/dc_dmub_srv.h" +#include "dc/dc_stat.h" #include "amdgpu.h" +#include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_hdcp.h" +#include "amdgpu_dm_mst_types.h" +#include "amdgpu_dm_dmub.h" +#include "amdgpu_dm_trace.h" +#include "link/protocols/link_dpcd.h" +#include "link_service_types.h" +#include "ivsrcid/ivsrcid_vislands30.h" +#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" +#include "modules/inc/mod_freesync.h" +#include /** * DOC: overview @@ -55,7 +69,8 @@ * are all set to the DM generic handler amdgpu_dm_irq_handler(), which looks up * DM's IRQ tables. However, in order for base driver to recognize this hook, DM * still needs to register the IRQ with the base driver. See - * dce110_register_irq_handlers() and dcn10_register_irq_handlers(). + * amdgpu_dm_dce110_register_irq_handlers() and + * amdgpu_dm_dcn10_register_irq_handlers(). * * To expose DC's hardware interrupt toggle to the base driver, DM implements * &amdgpu_irq_src_funcs.set hooks. Base driver calls it through @@ -1020,3 +1035,1487 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_fini(dev); } + +/* ========== HPD handling ========== */ +static void force_connector_state( + struct amdgpu_dm_connector *aconnector, + enum drm_connector_force force_state) +{ + struct drm_connector *connector = &aconnector->base; + + mutex_lock(&connector->dev->mode_config.mutex); + aconnector->base.force = force_state; + mutex_unlock(&connector->dev->mode_config.mutex); + + mutex_lock(&aconnector->hpd_lock); + drm_kms_helper_connector_hotplug_event(connector); + mutex_unlock(&aconnector->hpd_lock); +} + +static void dm_handle_hpd_rx_offload_work(struct work_struct *work) +{ + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconnector; + struct dc_link *dc_link; + struct amdgpu_device *adev; + enum dc_connection_type new_connection_type = dc_connection_none; + unsigned long flags; + union test_response test_response; + + memset(&test_response, 0, sizeof(test_response)); + + offload_work = container_of(work, struct hpd_rx_irq_offload_work, work); + aconnector = offload_work->offload_wq->aconnector; + adev = offload_work->adev; + + if (!aconnector) { + drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work"); + goto skip; + } + + dc_link = aconnector->dc_link; + + mutex_lock(&aconnector->hpd_lock); + if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) + drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); + mutex_unlock(&aconnector->hpd_lock); + + if (new_connection_type == dc_connection_none) + goto skip; + + if (amdgpu_in_reset(adev)) + goto skip; + + if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || + offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT); + spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); + offload_work->offload_wq->is_handling_mst_msg_rdy_event = false; + spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); + goto skip; + } + + mutex_lock(&adev->dm.dc_lock); + if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) { + dc_link_dp_handle_automated_test(dc_link); + + if (aconnector->timing_changed) { + /* force connector disconnect and reconnect */ + force_connector_state(aconnector, DRM_FORCE_OFF); + msleep(100); + force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED); + } + + test_response.bits.ACK = 1; + + core_link_write_dpcd( + dc_link, + DP_TEST_RESPONSE, + &test_response.raw, + sizeof(test_response)); + } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) && + dc_link_check_link_loss_status(dc_link, &offload_work->data) && + dc_link_dp_allow_hpd_rx_irq(dc_link)) { + /* offload_work->data is from handle_hpd_rx_irq-> + * schedule_hpd_rx_offload_work.this is defer handle + * for hpd short pulse. upon here, link status may be + * changed, need get latest link status from dpcd + * registers. if link status is good, skip run link + * training again. + */ + union hpd_irq_data irq_data; + + memset(&irq_data, 0, sizeof(irq_data)); + + /* before dc_link_dp_handle_link_loss, allow new link lost handle + * request be added to work queue if link lost at end of dc_link_ + * dp_handle_link_loss + */ + spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); + offload_work->offload_wq->is_handling_link_loss = false; + spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); + + if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) && + dc_link_check_link_loss_status(dc_link, &irq_data)) + dc_link_dp_handle_link_loss(dc_link); + } + mutex_unlock(&adev->dm.dc_lock); + +skip: + kfree(offload_work); + +} + +struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + int max_caps = dc->caps.max_links; + int i = 0; + struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL; + + hpd_rx_offload_wq = kzalloc_objs(*hpd_rx_offload_wq, max_caps); + + if (!hpd_rx_offload_wq) + return NULL; + + + for (i = 0; i < max_caps; i++) { + hpd_rx_offload_wq[i].wq = + create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq"); + + if (hpd_rx_offload_wq[i].wq == NULL) { + drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!"); + goto out_err; + } + + spin_lock_init(&hpd_rx_offload_wq[i].offload_lock); + } + + return hpd_rx_offload_wq; + +out_err: + for (i = 0; i < max_caps; i++) { + if (hpd_rx_offload_wq[i].wq) + destroy_workqueue(hpd_rx_offload_wq[i].wq); + } + kfree(hpd_rx_offload_wq); + return NULL; +} + +void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) +{ + int i; + + if (dm->hpd_rx_offload_wq) { + for (i = 0; i < dm->dc->caps.max_links; i++) + flush_workqueue(dm->hpd_rx_offload_wq[i].wq); + } +} + +static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) +{ + if (!sink1 || !sink2) + return false; + if (sink1->sink_signal != sink2->sink_signal) + return false; + + if (sink1->dc_edid.length != sink2->dc_edid.length) + return false; + + if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid, + sink1->dc_edid.length) != 0) + return false; + return true; +} + + +/** + * DOC: amdgpu_dm_hdmi_hpd_debounce_work + * + * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD + * (such as during power save transitions), this delay determines how long to + * wait before processing the HPD event. This allows distinguishing between a + * physical unplug (>hdmi_hpd_debounce_delay) + * and a spontaneous RX HPD toggle (base; + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dc *dc = aconnector->dc_link->ctx->dc; + bool fake_reconnect = false; + bool reallow_idle = false; + bool ret = false; + + guard(mutex)(&aconnector->hpd_lock); + + /* Re-detect the display */ + scoped_guard(mutex, &adev->dm.dc_lock) { + if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) { + dc_allow_idle_optimizations(dc, false); + reallow_idle = true; + } + ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); + } + + if (ret) { + /* Apply workaround delay for certain panels */ + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); + /* Compare sinks to determine if this was a spontaneous HPD toggle */ + if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) { + /* + * Sinks match - this was a spontaneous HDMI HPD toggle. + */ + drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n"); + fake_reconnect = true; + } + + /* Update connector state */ + amdgpu_dm_update_connector_after_detect(aconnector); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + /* Only notify OS if sink actually changed */ + if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED) + drm_kms_helper_hotplug_event(dev); + } + + /* Release the cached sink reference */ + if (aconnector->hdmi_prev_sink) { + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = NULL; + } + + scoped_guard(mutex, &adev->dm.dc_lock) { + if (reallow_idle && dc->caps.ips_support) + dc_allow_idle_optimizations(dc, true); + } +} + +static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason) +{ + struct drm_connector *connector = &aconnector->base; + struct drm_device *dev = connector->dev; + enum dc_connection_type new_connection_type = dc_connection_none; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); + struct dc *dc = aconnector->dc_link->ctx->dc; + bool ret = false; + bool debounce_required = false; + + if (adev->dm.disable_hpd_irq) + return; + + /* + * In case of failure or MST no need to update connector status or notify the OS + * since (for MST case) MST does this in its own context. + */ + guard(mutex)(&aconnector->hpd_lock); + + if (adev->dm.hdcp_workqueue) { + hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index); + dm_con_state->update_hdcp = true; + } + if (aconnector->fake_enable) + aconnector->fake_enable = false; + + aconnector->timing_changed = false; + + if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type)) + drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); + + /* + * Check for HDMI disconnect with debounce enabled. + */ + debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 && + dc_is_hdmi_signal(aconnector->dc_link->connector_signal) && + new_connection_type == dc_connection_none && + aconnector->dc_link->local_sink != NULL); + + if (aconnector->base.force && new_connection_type == dc_connection_none) { + amdgpu_dm_emulated_link_detect(aconnector->dc_link); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) + drm_kms_helper_connector_hotplug_event(connector); + } else if (debounce_required) { + /* + * HDMI disconnect detected - schedule delayed work instead of + * processing immediately. This allows us to coalesce spurious + * HDMI signals from physical unplugs. + */ + drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n", + aconnector->hdmi_hpd_debounce_delay_ms); + + /* Cache the current sink for later comparison */ + if (aconnector->hdmi_prev_sink) + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink; + if (aconnector->hdmi_prev_sink) + dc_sink_retain(aconnector->hdmi_prev_sink); + + /* Schedule delayed detection. */ + if (mod_delayed_work(system_percpu_wq, + &aconnector->hdmi_hpd_debounce_work, + msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms))) + drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n"); + + } else { + + /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */ + if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work)) + return; + + scoped_guard(mutex, &adev->dm.dc_lock) { + dc_exit_ips_for_hw_access(dc); + ret = dc_link_detect(aconnector->dc_link, reason); + } + if (ret) { + /* w/a delay for certain panels */ + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); + amdgpu_dm_update_connector_after_detect(aconnector); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) + drm_kms_helper_connector_hotplug_event(connector); + } + } +} + +static void handle_hpd_irq(void *param) +{ + struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; + + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); + +} + +static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, + union hpd_irq_data hpd_irq_data) +{ + struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work); + + if (!offload_work) { + drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n"); + return; + } + + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + offload_work->data = hpd_irq_data; + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + + queue_work(offload_wq->wq, &offload_work->work); + drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); +} + +static void handle_hpd_rx_irq(void *param) +{ + struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; + struct drm_connector *connector = &aconnector->base; + struct drm_device *dev = connector->dev; + struct dc_link *dc_link = aconnector->dc_link; + bool is_mst_root_connector = aconnector->mst_mgr.mst_state; + bool result = false; + struct amdgpu_device *adev = drm_to_adev(dev); + union hpd_irq_data hpd_irq_data; + bool link_loss = false; + bool has_left_work = false; + int idx = dc_link->link_index; + struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; + + memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); + + if (adev->dm.disable_hpd_irq) + return; + + /* + * TODO:Temporary add mutex to protect hpd interrupt not have a gpio + * conflict, after implement i2c helper, this mutex should be + * retired. + */ + mutex_lock(&aconnector->hpd_lock); + + result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data, + &link_loss, true, &has_left_work); + + if (!has_left_work) + goto out; + + if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { + schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); + goto out; + } + + if (dc_link_dp_allow_hpd_rx_irq(dc_link)) { + if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || + hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { + bool skip = false; + + /* + * DOWN_REP_MSG_RDY is also handled by polling method + * mgr->cbs->poll_hpd_irq() + */ + spin_lock(&offload_wq->offload_lock); + skip = offload_wq->is_handling_mst_msg_rdy_event; + + if (!skip) + offload_wq->is_handling_mst_msg_rdy_event = true; + + spin_unlock(&offload_wq->offload_lock); + + if (!skip) + schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); + + goto out; + } + + if (link_loss) { + bool skip = false; + + spin_lock(&offload_wq->offload_lock); + skip = offload_wq->is_handling_link_loss; + + if (!skip) + offload_wq->is_handling_link_loss = true; + + spin_unlock(&offload_wq->offload_lock); + + if (!skip) + schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); + + goto out; + } + } + +out: + if (result && !is_mst_root_connector) { + /* Downstream Port status changed. */ + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); + } + if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { + if (adev->dm.hdcp_workqueue) + hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index); + } + + if (dc_link->type != dc_connection_mst_branch) + drm_dp_cec_irq(&aconnector->dm_dp_aux.aux); + + mutex_unlock(&aconnector->hpd_lock); +} + +/** + * dmub_hpd_callback - DMUB HPD interrupt processing callback. + * @adev: amdgpu_device pointer + * @notify: dmub notification structure + * + * Dmub Hpd interrupt processing callback. Gets displayindex through the + * ink index and calls helper to do the processing. + */ +static void dmub_hpd_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_dm_connector *hpd_aconnector = NULL; + struct drm_connector *connector; + struct drm_connector_list_iter iter; + struct dc_link *link; + u8 link_index = 0; + struct drm_device *dev; + + if (adev == NULL) + return; + + if (notify == NULL) { + drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL"); + return; + } + + if (notify->link_index > adev->dm.dc->link_count) { + drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index); + return; + } + + /* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */ + if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) { + drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n"); + return; + } + + link_index = notify->link_index; + link = adev->dm.dc->links[link_index]; + dev = adev->dm.ddev; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (link && aconnector->dc_link == link) { + if (notify->type == DMUB_NOTIFICATION_HPD) + drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index); + else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) + drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index); + else + drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n", + notify->type, link_index); + + hpd_aconnector = aconnector; + break; + } + } + drm_connector_list_iter_end(&iter); + + if (hpd_aconnector) { + if (notify->type == DMUB_NOTIFICATION_HPD) { + if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) + drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); + handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); + } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { + handle_hpd_rx_irq(hpd_aconnector); + } + } +} + +/** + * dmub_hpd_sense_callback - DMUB HPD sense processing callback. + * @adev: amdgpu_device pointer + * @notify: dmub notification structure + * + * HPD sense changes can occur during low power states and need to be + * notified from firmware to driver. + */ +static void dmub_hpd_sense_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); +} + +int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev_to_drm(adev); + struct drm_connector *connector; + struct amdgpu_dm_connector *aconnector; + const struct dc_link *dc_link; + struct dc_interrupt_params int_params = {0}; + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + if (dc_is_dmub_outbox_supported(adev->dm.dc)) { + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, + dmub_hpd_callback, true)) { + drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); + return -EINVAL; + } + + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, + dmub_hpd_callback, true)) { + drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); + return -EINVAL; + } + + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, + dmub_hpd_sense_callback, true)) { + drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); + return -EINVAL; + } + } + + list_for_each_entry(connector, + &dev->mode_config.connector_list, head) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + dc_link = aconnector->dc_link; + + if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = dc_link->irq_source_hpd; + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_HPD1 || + int_params.irq_source > DC_IRQ_SOURCE_HPD6) { + drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n"); + return -EINVAL; + } + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + handle_hpd_irq, (void *) aconnector)) + return -ENOMEM; + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { + + /* Also register for DP short pulse (hpd_rx). */ + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = dc_link->irq_source_hpd_rx; + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || + int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { + drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n"); + return -EINVAL; + } + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + handle_hpd_rx_irq, (void *) aconnector)) + return -ENOMEM; + } + } + return 0; +} + +/* ========== IRQ handlers ========== */ +struct amdgpu_crtc * +amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, + int otg_inst) +{ + struct drm_device *dev = adev_to_drm(adev); + struct drm_crtc *crtc; + struct amdgpu_crtc *amdgpu_crtc; + + if (WARN_ON(otg_inst == -1)) + return adev->mode_info.crtcs[0]; + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + amdgpu_crtc = to_amdgpu_crtc(crtc); + + if (amdgpu_crtc->otg_inst == otg_inst) + return amdgpu_crtc; + } + + return NULL; +} + +/** + * dm_pflip_high_irq() - Handle pageflip interrupt + * @interrupt_params: ignored + * + * Handles the pageflip interrupt by notifying all interested parties + * that the pageflip has been completed. + */ +static void dm_pflip_high_irq(void *interrupt_params) +{ + struct amdgpu_crtc *amdgpu_crtc; + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct drm_device *dev = adev_to_drm(adev); + unsigned long flags; + struct drm_pending_vblank_event *e; + u32 vpos, hpos, v_blank_start, v_blank_end; + bool vrr_active; + + amdgpu_crtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP); + + /* IRQ could occur when in initial stage */ + /* TODO work and BO cleanup */ + if (amdgpu_crtc == NULL) { + drm_dbg_state(dev, "CRTC is null, returning.\n"); + return; + } + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + + if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) { + drm_dbg_state(dev, + "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n", + amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED, + amdgpu_crtc->crtc_id, amdgpu_crtc); + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + return; + } + + /* page flip completed. */ + e = amdgpu_crtc->event; + amdgpu_crtc->event = NULL; + + WARN_ON(!e); + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc); + + /* Fixed refresh rate, or VRR scanout position outside front-porch? */ + if (!vrr_active || + !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start, + &v_blank_end, &hpos, &vpos) || + (vpos < v_blank_start)) { + /* Update to correct count and vblank timestamp if racing with + * vblank irq. This also updates to the correct vblank timestamp + * even in VRR mode, as scanout is past the front-porch atm. + */ + drm_crtc_accurate_vblank_count(&amdgpu_crtc->base); + + /* Wake up userspace by sending the pageflip event with proper + * count and timestamp of vblank of flip completion. + */ + if (e) { + drm_crtc_send_vblank_event(&amdgpu_crtc->base, e); + + /* Event sent, so done with vblank for this flip */ + drm_crtc_vblank_put(&amdgpu_crtc->base); + } + } else if (e) { + /* VRR active and inside front-porch: vblank count and + * timestamp for pageflip event will only be up to date after + * drm_crtc_handle_vblank() has been executed from late vblank + * irq handler after start of back-porch (vline 0). We queue the + * pageflip event for send-out by drm_crtc_handle_vblank() with + * updated timestamp and count, once it runs after us. + * + * We need to open-code this instead of using the helper + * drm_crtc_arm_vblank_event(), as that helper would + * call drm_crtc_accurate_vblank_count(), which we must + * not call in VRR mode while we are in front-porch! + */ + + /* sequence will be replaced by real count during send-out. */ + e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base); + e->pipe = amdgpu_crtc->crtc_id; + + list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list); + e = NULL; + } + + /* Keep track of vblank of this flip for flip throttling. We use the + * cooked hw counter, as that one incremented at start of this vblank + * of pageflip completion, so last_flip_vblank is the forbidden count + * for queueing new pageflips if vsync + VRR is enabled. + */ + amdgpu_crtc->dm_irq_params.last_flip_vblank = + amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base); + + amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + + drm_dbg_state(dev, + "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", + amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e); +} + +static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) +{ + struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); + struct amdgpu_device *adev = work->adev; + struct dc_stream_state *stream = work->stream; + struct dc_crtc_timing_adjust *adjust = work->adjust; + + mutex_lock(&adev->dm.dc_lock); + dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust); + mutex_unlock(&adev->dm.dc_lock); + + dc_stream_release(stream); + kfree(work->adjust); + kfree(work); +} + +static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, + struct dc_stream_state *stream, + struct dc_crtc_timing_adjust *adjust) +{ + struct vupdate_offload_work *offload_work = kzalloc_obj(*offload_work, + GFP_NOWAIT); + if (!offload_work) { + drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n"); + return; + } + + struct dc_crtc_timing_adjust *adjust_copy = kzalloc_obj(*adjust_copy, + GFP_NOWAIT); + if (!adjust_copy) { + drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n"); + kfree(offload_work); + return; + } + + dc_stream_retain(stream); + memcpy(adjust_copy, adjust, sizeof(*adjust_copy)); + + INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update); + offload_work->adev = adev; + offload_work->stream = stream; + offload_work->adjust = adjust_copy; + + queue_work(system_percpu_wq, &offload_work->work); +} + +static void dm_vupdate_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + struct drm_device *drm_dev; + struct drm_vblank_crtc *vblank; + ktime_t frame_duration_ns, previous_timestamp; + unsigned long flags; + int vrr_active; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); + + if (acrtc) { + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + drm_dev = acrtc->base.dev; + vblank = drm_crtc_vblank_crtc(&acrtc->base); + previous_timestamp = atomic64_read(&irq_params->previous_timestamp); + frame_duration_ns = vblank->time - previous_timestamp; + + if (frame_duration_ns > 0) { + trace_amdgpu_refresh_rate_track(acrtc->base.index, + frame_duration_ns, + ktime_divns(NSEC_PER_SEC, frame_duration_ns)); + atomic64_set(&irq_params->previous_timestamp, vblank->time); + } + + drm_dbg_vbl(drm_dev, + "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, + vrr_active); + + /* Core vblank handling is done here after end of front-porch in + * vrr mode, as vblank timestamping will give valid results + * while now done after front-porch. This will also deliver + * page-flip completion events that have been queued to us + * if a pageflip happened inside front-porch. + */ + if (vrr_active && acrtc->dm_irq_params.stream) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state + == VRR_STATE_ACTIVE_VARIABLE; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + /* BTR processing for pre-DCE12 ASICs */ + if (adev->family < AMDGPU_FAMILY_AI) { + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + mod_freesync_handle_v_update( + adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + } + } + } +} + +/** + * dm_crtc_high_irq() - Handles CRTC interrupt + * @interrupt_params: used for determining the CRTC instance + * + * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK + * event handler. + */ +static void dm_crtc_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct drm_writeback_job *job; + struct amdgpu_crtc *acrtc; + unsigned long flags; + int vrr_active; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); + if (!acrtc) + return; + + if (acrtc->wb_conn) { + spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); + + if (acrtc->wb_pending) { + job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, + struct drm_writeback_job, + list_entry); + acrtc->wb_pending = false; + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + + if (job) { + unsigned int v_total, refresh_hz; + struct dc_stream_state *stream = acrtc->dm_irq_params.stream; + + 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); + + drm_writeback_signal_completion(acrtc->wb_conn, 0); + dc_stream_fc_disable_writeback(adev->dm.dc, + acrtc->dm_irq_params.stream, 0); + } + } else + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + } + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + + drm_dbg_vbl(adev_to_drm(adev), + "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id, + vrr_active, acrtc->dm_irq_params.active_planes); + + /** + * Core vblank handling at start of front-porch is only possible + * in non-vrr mode, as only there vblank timestamping will give + * valid results while done in front-porch. Otherwise defer it + * to dm_vupdate_high_irq after end of front-porch. + */ + if (!vrr_active) + amdgpu_dm_crtc_handle_vblank(acrtc); + + /** + * Following stuff must happen at start of vblank, for crc + * computation and below-the-range btr support in vrr mode. + */ + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + + /* BTR updates need to happen before VUPDATE on Vega and above. */ + if (adev->family < AMDGPU_FAMILY_AI) + return; + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + + if (acrtc->dm_irq_params.stream && + acrtc->dm_irq_params.vrr_params.supported) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE; + + mod_freesync_handle_v_update(adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + /* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */ + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + } + + /* + * If there aren't any active_planes then DCH HUBP may be clock-gated. + * In that case, pageflip completion interrupts won't fire and pageflip + * completion events won't get delivered. Prevent this by sending + * pending pageflip events from here if a flip is still pending. + * + * If any planes are enabled, use dm_pflip_high_irq() instead, to + * avoid race conditions between flip programming and completion, + * which could cause too early flip completion events. + */ + if (adev->family >= AMDGPU_FAMILY_RV && + acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->dm_irq_params.active_planes == 0) { + if (acrtc->event) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; + drm_crtc_vblank_put(&acrtc->base); + } + acrtc->pflip_status = AMDGPU_FLIP_NONE; + } + + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); +} + +#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) +/** + * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for + * DCN generation ASICs + * @interrupt_params: interrupt parameters + * + * Used to set crc window/read out crc value at vertical line 0 position + */ +static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0); + + if (!acrtc) + return; + + amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base); +} +#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ + +static void dm_handle_hpd_work(struct work_struct *work) +{ + struct dmub_hpd_work *dmub_hpd_wrk; + + dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work); + + if (!dmub_hpd_wrk->dmub_notify) { + drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL"); + return; + } + + if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) { + dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev, + dmub_hpd_wrk->dmub_notify); + } + + kfree(dmub_hpd_wrk->dmub_notify); + kfree(dmub_hpd_wrk); + +} + +static const char *dmub_notification_type_str(enum dmub_notification_type e) +{ + switch (e) { + case DMUB_NOTIFICATION_NO_DATA: + return "NO_DATA"; + case DMUB_NOTIFICATION_AUX_REPLY: + return "AUX_REPLY"; + case DMUB_NOTIFICATION_HPD: + return "HPD"; + case DMUB_NOTIFICATION_HPD_IRQ: + return "HPD_IRQ"; + case DMUB_NOTIFICATION_SET_CONFIG_REPLY: + return "SET_CONFIG_REPLY"; + case DMUB_NOTIFICATION_DPIA_NOTIFICATION: + return "DPIA_NOTIFICATION"; + case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY: + return "HPD_SENSE_NOTIFY"; + case DMUB_NOTIFICATION_FUSED_IO: + return "FUSED_IO"; + default: + return ""; + } +} + +#define DMUB_TRACE_MAX_READ 64 +/** + * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt + * @interrupt_params: used for determining the Outbox instance + * + * Handles the Outbox Interrupt + * event handler. + */ +static void dm_dmub_outbox1_low_irq(void *interrupt_params) +{ + struct dmub_notification notify = {0}; + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_display_manager *dm = &adev->dm; + struct dmcub_trace_buf_entry entry = { 0 }; + u32 count = 0; + struct dmub_hpd_work *dmub_hpd_wrk; + + do { + if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) { + trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count, + entry.param0, entry.param1); + + drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n", + entry.trace_code, entry.tick_count, entry.param0, entry.param1); + } else + break; + + count++; + + } while (count <= DMUB_TRACE_MAX_READ); + + if (count > DMUB_TRACE_MAX_READ) + drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ"); + + if (dc_enable_dmub_notifications(adev->dm.dc) && + irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) { + + do { + dc_stat_get_dmub_notification(adev->dm.dc, ¬ify); + if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) { + drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type); + continue; + } + if (!dm->dmub_callback[notify.type]) { + drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n", + dmub_notification_type_str(notify.type)); + continue; + } + if (dm->dmub_thread_offload[notify.type] == true) { + dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, + GFP_ATOMIC); + if (!dmub_hpd_wrk) { + drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk"); + return; + } + dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification), + GFP_ATOMIC); + if (!dmub_hpd_wrk->dmub_notify) { + kfree(dmub_hpd_wrk); + drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify"); + return; + } + INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work); + dmub_hpd_wrk->adev = adev; + queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work); + } else { + dm->dmub_callback[notify.type](adev, ¬ify); + } + } while (notify.pending_notification); + } +} + +/* Register IRQ sources and initialize IRQ callbacks */ +int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + struct common_irq_params *c_irq_params; + struct dc_interrupt_params int_params = {0}; + int r; + int i; + unsigned int src_id; + unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY; + /* Use different interrupts for VBLANK on DCE 6 vs. newer. */ + const unsigned int vblank_d1 = + adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0 + ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1; + + if (adev->family >= AMDGPU_FAMILY_AI) + client_id = SOC15_IH_CLIENTID_DCE; + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + /* + * Actions of amdgpu_irq_add_id(): + * 1. Register a set() function with base driver. + * Base driver will call set() function to enable/disable an + * interrupt in DC hardware. + * 2. Register amdgpu_dm_irq_handler(). + * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts + * coming from DC hardware. + * amdgpu_dm_irq_handler() will re-direct the interrupt to DC + * for acknowledging and handling. + */ + + /* Use VBLANK interrupt */ + for (i = 0; i < adev->mode_info.num_crtc; i++) { + src_id = vblank_d1 + i; + r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, src_id, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || + int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { + drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_crtc_high_irq, c_irq_params)) + return -ENOMEM; + } + + if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) { + /* Use VUPDATE interrupt */ + for (i = 0; i < adev->mode_info.num_crtc; i++) { + src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2; + r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, src_id, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || + int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { + drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vupdate_params[ + int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_vupdate_high_irq, c_irq_params)) + return -ENOMEM; + } + } + + /* Use GRPH_PFLIP interrupt */ + for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; + i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { + r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || + int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { + drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_pflip_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* HPD */ + r = amdgpu_irq_add_id(adev, client_id, + VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); + return r; + } + + r = amdgpu_dm_register_hpd_handlers(adev); + + return r; +} + +/* Register IRQ sources and initialize IRQ callbacks */ +int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + struct common_irq_params *c_irq_params; + struct dc_interrupt_params int_params = {0}; + int r; + int i; +#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) + static const unsigned int vrtl_int_srcid[] = { + DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL + }; +#endif + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + /* + * Actions of amdgpu_irq_add_id(): + * 1. Register a set() function with base driver. + * Base driver will call set() function to enable/disable an + * interrupt in DC hardware. + * 2. Register amdgpu_dm_irq_handler(). + * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts + * coming from DC hardware. + * amdgpu_dm_irq_handler() will re-direct the interrupt to DC + * for acknowledging and handling. + */ + + /* Use VSTARTUP interrupt */ + for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; + i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; + i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); + + if (r) { + drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || + int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { + drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_crtc_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* Use otg vertical line interrupt */ +#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) + for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, + vrtl_int_srcid[i], &adev->vline0_irq); + + if (r) { + drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 || + int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) { + drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vline0_params[int_params.irq_source + - DC_IRQ_SOURCE_DC1_VLINE0]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_dcn_vertical_interrupt0_high_irq, + c_irq_params)) + return -ENOMEM; + } +#endif + + /* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to + * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx + * to trigger at end of each vblank, regardless of state of the lock, + * matching DCE behaviour. + */ + for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT; + i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1; + i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq); + + if (r) { + drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || + int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { + drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_vupdate_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* Use GRPH_PFLIP interrupt */ + for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; + i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; + i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || + int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { + drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_pflip_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* HPD */ + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, + &adev->hpd_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); + return r; + } + + r = amdgpu_dm_register_hpd_handlers(adev); + + return r; +} + +/* Register Outbox IRQ sources and initialize IRQ callbacks */ +int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + struct common_irq_params *c_irq_params; + struct dc_interrupt_params int_params = {0}; + int r, i; + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT, + &adev->dmub_outbox_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n"); + return r; + } + + if (dc->ctx->dmub_srv) { + i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT; + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + c_irq_params = &adev->dm.dmub_outbox_params[0]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_dmub_outbox1_low_irq, c_irq_params)) + return -ENOMEM; + } + + return 0; +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 4f6b58f4f90d..ba6968f5626f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -27,6 +27,12 @@ #include "irq_types.h" /* DAL irq definitions */ +struct amdgpu_device; +struct amdgpu_crtc; +struct amdgpu_display_manager; +struct hpd_rx_irq_offload_work_queue; +struct work_struct; + /* * Display Manager IRQ-related interfaces (for use by DAL). */ @@ -101,4 +107,17 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev); void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev); void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev); +/* HPD handling */ +struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev); +void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm); +int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev); +void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work); + +/* IRQ handlers */ +struct amdgpu_crtc *amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, + int otg_inst); +int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev); +int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); +int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); + #endif /* __AMDGPU_DM_IRQ_H__ */ -- cgit v1.2.3 From 0e967e086e7519966816b76a6309b4516d365aa5 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 27 Apr 2026 22:32:01 -0600 Subject: drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector Move connector lifecycle functions (init, detect, mode validation, property handling, EDID parsing, hotplug processing) and encoder functions (init, destroy, atomic_check, helper_funcs) from amdgpu_dm.c to amdgpu_dm_connector.c. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel 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 | 3743 +------------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 50 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3575 +++++++++++++++++++ .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 147 + .../drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 2 +- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +- 7 files changed, 3848 insertions(+), 3674 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index a6408da05583..d83878e35b61 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -44,7 +44,8 @@ AMDGPUDM = \ amdgpu_dm_ism.o \ amdgpu_dm_backlight.o \ amdgpu_dm_audio.o \ - amdgpu_dm_dmub.o + amdgpu_dm_dmub.o \ + amdgpu_dm_connector.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 87a849152d81..f3833e038e99 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -69,6 +69,7 @@ #include "amdgpu_dm_backlight.h" #include "amdgpu_dm_audio.h" #include "amdgpu_dm_dmub.h" +#include "amdgpu_dm_connector.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -125,46 +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 bool is_freesync_video_mode(const struct drm_display_mode *mode, struct amdgpu_dm_connector *aconnector); static void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); -static struct amdgpu_i2c_adapter * -create_i2c(struct ddc_service *ddc_service, bool oem); - -static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) -{ - switch (link->dpcd_caps.dongle_type) { - case DISPLAY_DONGLE_NONE: - return DRM_MODE_SUBCONNECTOR_Native; - case DISPLAY_DONGLE_DP_VGA_CONVERTER: - return DRM_MODE_SUBCONNECTOR_VGA; - case DISPLAY_DONGLE_DP_DVI_CONVERTER: - case DISPLAY_DONGLE_DP_DVI_DONGLE: - return DRM_MODE_SUBCONNECTOR_DVID; - case DISPLAY_DONGLE_DP_HDMI_CONVERTER: - case DISPLAY_DONGLE_DP_HDMI_DONGLE: - return DRM_MODE_SUBCONNECTOR_HDMIA; - case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE: - default: - return DRM_MODE_SUBCONNECTOR_Unknown; - } -} - -static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) -{ - struct dc_link *link = aconnector->dc_link; - struct drm_connector *connector = &aconnector->base; - enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; - - if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) - return; - - if (aconnector->dc_sink) - subconnector = get_subconnector_type(link); - - drm_object_property_set_value(&connector->base, - connector->dev->mode_config.dp_subconnector_property, - subconnector); -} /* * initializes drm_device display related structures, based on the information @@ -177,18 +139,9 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev); /* removes and deallocates the drm structures, created by the above function */ static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm); -static int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *amdgpu_dm_connector, - u32 link_index, - struct amdgpu_encoder *amdgpu_encoder); -static int amdgpu_dm_encoder_init(struct drm_device *dev, - struct amdgpu_encoder *aencoder, - uint32_t link_index); - -static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); - 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 int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); @@ -196,6 +149,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, static 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) + dc_exit_ips_for_hw_access(dc); +} + /* * dm_vblank_get_counter * @@ -369,45 +329,6 @@ static int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, static int dm_early_init(struct amdgpu_ip_block *ip_block); /* Allocate memory for FBC compressed data */ -static void amdgpu_dm_fbc_init(struct drm_connector *connector) -{ - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct dm_compressor_info *compressor = &adev->dm.compressor; - struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(connector); - struct drm_display_mode *mode; - unsigned long max_size = 0; - - if (adev->dm.dc->fbc_compressor == NULL) - return; - - if (aconn->dc_link->connector_signal != SIGNAL_TYPE_EDP) - return; - - if (compressor->bo_ptr) - return; - - - list_for_each_entry(mode, &connector->modes, head) { - if (max_size < (unsigned long) mode->htotal * mode->vtotal) - max_size = (unsigned long) mode->htotal * mode->vtotal; - } - - if (max_size) { - int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr, - &compressor->gpu_addr, &compressor->cpu_addr); - - if (r) - drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n"); - else { - adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr; - drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4); - } - - } - -} - static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config) { u64 pt_base; @@ -634,40 +555,6 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) return 0; } -static void hdmi_frl_status_polling_work(struct work_struct *work) -{ - struct amdgpu_display_manager *dm = - container_of(to_delayed_work(work), struct amdgpu_display_manager, - hdmi_frl_status_polling_work); - struct dc *dc = dm->dc; - struct dc_link *dc_link; - bool link_update = false; - - for (int i = 0; i < MAX_LINKS; i++) { - dc_link = dc->links[i]; - - if (!dc_link || !dc_link->local_sink) - continue; - - if (!dc_is_hdmi_signal(dc_link->connector_signal)) - continue; - - if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) - continue; - - link_update = dc_link_frl_poll_status_flag(dc_link); - if (link_update) { - mutex_lock(&dm->dc_lock); - dc_link_detect(dc_link, DETECT_REASON_RETRAIN); - mutex_unlock(&dm->dc_lock); - } - } - - queue_delayed_work(dm->hdmi_frl_status_polling_wq, - &dm->hdmi_frl_status_polling_work, - msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms)); -} - static int amdgpu_dm_init(struct amdgpu_device *adev) { struct dc_init_data init_data; @@ -947,8 +834,6 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) create_singlethread_workqueue("hdmi_frl_status_polling_workqueue"); if (!adev->dm.hdmi_frl_status_polling_wq) drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_workqueue\n"); - adev->dm.hdmi_frl_status_polling_delay_ms = 200; - INIT_DELAYED_WORK(&adev->dm.hdmi_frl_status_polling_work, hdmi_frl_status_polling_work); } if (dc_is_dmub_outbox_supported(adev->dm.dc)) { init_completion(&adev->dm.dmub_aux_transfer_done); @@ -981,9 +866,10 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } /* Enable outbox notification only after IRQ handlers are registered and DMUB is alive. * It is expected that DMUB will resend any pending notifications at this point. Note - * that hpd and hpd_irq handler registration are deferred to amdgpu_dm_register_hpd_handlers() to - * align legacy interface initialization sequence. Connection status will be proactivly - * detected once in the amdgpu_dm_initialize_drm_device. + * that hpd and hpd_irq handler registration are deferred to + * amdgpu_dm_register_hpd_handlers() to align legacy interface initialization + * sequence. Connection status will be proactivly detected once in the + * amdgpu_dm_initialize_drm_device. */ dc_enable_dmub_outbox(adev->dm.dc); @@ -1316,41 +1202,6 @@ static int dm_sw_fini(struct amdgpu_ip_block *ip_block) return 0; } -static int detect_mst_link_for_all_connectors(struct drm_device *dev) -{ - struct amdgpu_dm_connector *aconnector; - struct drm_connector *connector; - struct drm_connector_list_iter iter; - int ret = 0; - - drm_connector_list_iter_begin(dev, &iter); - drm_for_each_connector_iter(connector, &iter) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (aconnector->dc_link->type == dc_connection_mst_branch && - aconnector->mst_mgr.aux) { - drm_dbg_kms(dev, "DM_MST: starting TM on aconnector: %p [id: %d]\n", - aconnector, - aconnector->base.base.id); - - ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true); - if (ret < 0) { - drm_err(dev, "DM_MST: Failed to start MST\n"); - aconnector->dc_link->type = - dc_connection_single; - ret = dm_helpers_dp_mst_stop_top_mgr(aconnector->dc_link->ctx, - aconnector->dc_link); - break; - } - } - } - drm_connector_list_iter_end(&iter); - - return ret; -} static void amdgpu_dm_boot_time_crc_init(struct amdgpu_device *adev) { @@ -1448,7 +1299,7 @@ static int dm_late_init(struct amdgpu_ip_block *ip_block) } } - return detect_mst_link_for_all_connectors(adev_to_drm(adev)); + return amdgpu_dm_detect_mst_link_for_all_connectors(adev_to_drm(adev)); } static void resume_mst_branch_status(struct drm_dp_mst_topology_mgr *mgr) @@ -1502,48 +1353,6 @@ out_fail: mutex_unlock(&mgr->lock); } -void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) -{ - struct cec_notifier *n = aconnector->notifier; - - if (!n) - return; - - cec_notifier_phys_addr_invalidate(n); -} - -void hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct cec_notifier *n = aconnector->notifier; - - if (!n) - return; - - cec_notifier_set_phys_addr(n, - connector->display_info.source_physical_address); -} - -static void s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) -{ - struct amdgpu_dm_connector *aconnector; - struct drm_connector *connector; - struct drm_connector_list_iter conn_iter; - - drm_connector_list_iter_begin(ddev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) { - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (suspend) - hdmi_cec_unset_edid(aconnector); - else - hdmi_cec_set_edid(aconnector); - } - drm_connector_list_iter_end(&conn_iter); -} - static void s3_handle_mst(struct drm_device *dev, bool suspend) { struct amdgpu_dm_connector *aconnector; @@ -1646,7 +1455,7 @@ static int dm_oem_i2c_hw_init(struct amdgpu_device *adev) oem_ddc_service = dc_get_oem_i2c_device(adev->dm.dc); if (oem_ddc_service) { - oem_i2c = create_i2c(oem_ddc_service, true); + oem_i2c = amdgpu_dm_create_i2c(oem_ddc_service, true); if (!oem_i2c) { drm_info(adev_to_drm(adev), "Failed to create oem i2c adapter data\n"); return -ENOMEM; @@ -1915,7 +1724,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) return r; } - s3_handle_hdmi_cec(adev_to_drm(adev), true); + amdgpu_dm_s3_handle_hdmi_cec(adev_to_drm(adev), true); s3_handle_mst(adev_to_drm(adev), true); @@ -1941,25 +1750,6 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) return 0; } -struct drm_connector * -amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, - struct drm_crtc *crtc) -{ - u32 i; - struct drm_connector_state *new_con_state; - struct drm_connector *connector; - struct drm_crtc *crtc_from_state; - - for_each_new_connector_in_state(state, connector, new_con_state, i) { - crtc_from_state = new_con_state->crtc; - - if (crtc_from_state == crtc) - return connector; - } - - return NULL; -} - void amdgpu_dm_emulated_link_detect(struct dc_link *link) { struct dc_sink_init_data sink_init_data = { 0 }; @@ -2289,7 +2079,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) */ amdgpu_dm_irq_resume_early(adev); - s3_handle_hdmi_cec(ddev, false); + amdgpu_dm_s3_handle_hdmi_cec(ddev, false); /* On resume we need to rewrite the MSTM control bits to enable MST*/ s3_handle_mst(ddev, false); @@ -2442,216 +2232,6 @@ static struct drm_mode_config_helper_funcs amdgpu_dm_mode_config_helperfuncs = { .atomic_commit_setup = amdgpu_dm_atomic_setup_commit, }; -#define DDC_MANUFACTURERNAME_SAMSUNG 0x2D4C - -static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct drm_display_info *display_info = &connector->display_info; - struct dc_link *link = aconnector->dc_link; - struct amdgpu_device *adev; - - adev = drm_to_adev(connector->dev); - - link->panel_type = PANEL_TYPE_NONE; - - switch (display_info->amd_vsdb.panel_type) { - case AMD_VSDB_PANEL_TYPE_OLED: - link->panel_type = PANEL_TYPE_OLED; - break; - case AMD_VSDB_PANEL_TYPE_MINILED: - link->panel_type = PANEL_TYPE_MINILED; - break; - } - - /* If VSDB didn't determine panel type, check DPCD ext caps */ - if (link->panel_type == PANEL_TYPE_NONE) { - if (link->dpcd_sink_ext_caps.bits.miniled == 1) - link->panel_type = PANEL_TYPE_MINILED; - if (link->dpcd_sink_ext_caps.bits.oled == 1) - link->panel_type = PANEL_TYPE_OLED; - } - - /* - * TODO: get panel type from DID2 that has device technology field - * to specify if it's OLED or not. But we need to wait for DID2 - * support in DC and EDID parser to be able to use it here. - */ - - if (link->panel_type == PANEL_TYPE_NONE) { - struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; - u32 lum1_max = vsdb->luminance_range1.max_luminance; - u32 lum2_max = vsdb->luminance_range2.max_luminance; - - if (vsdb->version && link->local_sink && - link->local_sink->edid_caps.manufacturer_id == - DDC_MANUFACTURERNAME_SAMSUNG && - lum1_max >= ((lum2_max * 3) / 2)) - link->panel_type = PANEL_TYPE_MINILED; - } - - if (link->panel_type == PANEL_TYPE_OLED) - drm_object_property_set_value(&connector->base, - adev_to_drm(adev)->mode_config.panel_type_property, - DRM_MODE_PANEL_TYPE_OLED); - else - drm_object_property_set_value(&connector->base, - adev_to_drm(adev)->mode_config.panel_type_property, - DRM_MODE_PANEL_TYPE_UNKNOWN); - - drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); -} - -DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) - -void amdgpu_dm_update_connector_after_detect( - struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct dc_sink *sink __free(sink_release) = NULL; - struct drm_device *dev = connector->dev; - - /* MST handled by drm_mst framework */ - if (aconnector->mst_mgr.mst_state == true) - return; - - sink = aconnector->dc_link->local_sink; - if (sink) - dc_sink_retain(sink); - - /* - * Edid mgmt connector gets first update only in mode_valid hook and then - * the connector sink is set to either fake or physical sink depends on link status. - * Skip if already done during boot. - */ - if (aconnector->base.force != DRM_FORCE_UNSPECIFIED - && aconnector->dc_em_sink) { - - /* - * For S3 resume with headless use eml_sink to fake stream - * because on resume connector->sink is set to NULL - */ - guard(mutex)(&dev->mode_config.mutex); - - if (sink) { - if (aconnector->dc_sink) { - amdgpu_dm_update_freesync_caps(connector, NULL, true); - /* - * retain and release below are used to - * bump up refcount for sink because the link doesn't point - * to it anymore after disconnect, so on next crtc to connector - * reshuffle by UMD we will get into unwanted dc_sink release - */ - dc_sink_release(aconnector->dc_sink); - } - aconnector->dc_sink = sink; - dc_sink_retain(aconnector->dc_sink); - amdgpu_dm_update_freesync_caps(connector, - aconnector->drm_edid, true); - } else { - amdgpu_dm_update_freesync_caps(connector, NULL, true); - if (!aconnector->dc_sink) { - aconnector->dc_sink = aconnector->dc_em_sink; - dc_sink_retain(aconnector->dc_sink); - } - } - - return; - } - - /* - * TODO: temporary guard to look for proper fix - * if this sink is MST sink, we should not do anything - */ - if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST) - return; - - if (aconnector->dc_sink == sink) { - /* - * We got a DP short pulse (Link Loss, DP CTS, etc...). - * Do nothing!! - */ - drm_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n", - aconnector->connector_id); - return; - } - - drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n", - aconnector->connector_id, aconnector->dc_sink, sink); - - /* When polling, DRM has already locked the mutex for us. */ - if (!drm_kms_helper_is_poll_worker()) - mutex_lock(&dev->mode_config.mutex); - - /* - * 1. Update status of the drm connector - * 2. Send an event and let userspace tell us what to do - */ - if (sink) { - /* - * TODO: check if we still need the S3 mode update workaround. - * If yes, put it here. - */ - if (aconnector->dc_sink) { - amdgpu_dm_update_freesync_caps(connector, NULL, true); - dc_sink_release(aconnector->dc_sink); - } - - aconnector->dc_sink = sink; - dc_sink_retain(aconnector->dc_sink); - drm_edid_free(aconnector->drm_edid); - aconnector->drm_edid = NULL; - if (sink->dc_edid.length == 0) { - hdmi_cec_unset_edid(aconnector); - if (aconnector->dc_link->aux_mode) { - drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); - } - } else { - const struct edid *edid = (const struct edid *)sink->dc_edid.raw_edid; - - aconnector->drm_edid = drm_edid_alloc(edid, sink->dc_edid.length); - drm_edid_connector_update(connector, aconnector->drm_edid); - - hdmi_cec_set_edid(aconnector); - if (aconnector->dc_link->aux_mode) - drm_dp_cec_attach(&aconnector->dm_dp_aux.aux, - connector->display_info.source_physical_address); - } - - if (!aconnector->timing_requested) { - aconnector->timing_requested = - kzalloc_obj(struct dc_crtc_timing); - if (!aconnector->timing_requested) - drm_err(dev, - "failed to create aconnector->requested_timing\n"); - } - - amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); - amdgpu_dm_update_connector_ext_caps(aconnector); - dm_set_panel_type(aconnector); - } else { - hdmi_cec_unset_edid(aconnector); - drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); - amdgpu_dm_update_freesync_caps(connector, NULL, true); - aconnector->num_modes = 0; - dc_sink_release(aconnector->dc_sink); - aconnector->dc_sink = NULL; - drm_edid_free(aconnector->drm_edid); - aconnector->drm_edid = NULL; - kfree(aconnector->timing_requested); - aconnector->timing_requested = NULL; - /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ - if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) - connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; - } - - update_subconnector_property(aconnector); - - /* When polling, the mutex will be unlocked for us by DRM. */ - if (!drm_kms_helper_is_poll_worker()) - mutex_unlock(&dev->mode_config.mutex); -} - /* * Acquires the lock for the atomic state object and returns * the new atomic state. @@ -2842,10 +2422,6 @@ static int initialize_plane(struct amdgpu_display_manager *dm, } -static void amdgpu_set_panel_orientation(struct drm_connector *connector); - - - /* * In this architecture, the association * connector -> encoder -> crtc @@ -3410,16 +2986,6 @@ static bool modereset_required(struct drm_crtc_state *crtc_state) return !crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state); } -static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder) -{ - drm_encoder_cleanup(encoder); - kfree(encoder); -} - -static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { - .destroy = amdgpu_dm_encoder_destroy, -}; - static int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, @@ -3803,7 +3369,7 @@ ffu: &flip_addrs->dirty_rect_count, true); } -static void update_stream_scaling_settings(struct drm_device *dev, +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) @@ -3859,1952 +3425,37 @@ static void update_stream_scaling_settings(struct drm_device *dev, } -static enum dc_color_depth -convert_color_depth_from_display_info(const struct drm_connector *connector, - bool is_y420, int requested_bpc) +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) { - u8 bpc; - - if (is_y420) { - bpc = 8; - - /* Cap display bpc based on HDMI 2.0 HF-VSDB */ - if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) - bpc = 16; - else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) - bpc = 12; - else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) - bpc = 10; - } else { - bpc = (uint8_t)connector->display_info.bpc; - /* Assume 8 bpc by default if no bpc is specified. */ - bpc = bpc ? bpc : 8; - } + struct dc_stream_state *stream = NULL; + struct drm_connector *connector; + struct drm_connector_state *new_con_state; + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *dm_conn_state; + int i, j, ret; + int vcpi, pbn_div, pbn = 0, slot_num = 0; - if (requested_bpc > 0) { - /* - * Cap display bpc based on the user requested value. - * - * The value for state->max_bpc may not correctly updated - * depending on when the connector gets added to the state - * or if this was called outside of atomic check, so it - * can't be used directly. - */ - bpc = min_t(u8, bpc, requested_bpc); + for_each_new_connector_in_state(state, connector, new_con_state, i) { - /* Round down to the nearest even number. */ - bpc = bpc - (bpc & 1); - } + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; - switch (bpc) { - case 0: - /* - * Temporary Work around, DRM doesn't parse color depth for - * EDID revision before 1.4 - * TODO: Fix edid parsing - */ - return COLOR_DEPTH_888; - case 6: - return COLOR_DEPTH_666; - case 8: - return COLOR_DEPTH_888; - case 10: - return COLOR_DEPTH_101010; - case 12: - return COLOR_DEPTH_121212; - case 14: - return COLOR_DEPTH_141414; - case 16: - return COLOR_DEPTH_161616; - default: - return COLOR_DEPTH_UNDEFINED; - } -} + aconnector = to_amdgpu_dm_connector(connector); -static enum dc_aspect_ratio -get_aspect_ratio(const struct drm_display_mode *mode_in) -{ - /* 1-1 mapping, since both enums follow the HDMI spec. */ - return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio; -} + if (!aconnector->mst_output_port) + continue; -static enum dc_color_space -get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, - const struct drm_connector_state *connector_state) -{ - enum dc_color_space color_space = COLOR_SPACE_SRGB; + if (!new_con_state || !new_con_state->crtc) + continue; - switch (connector_state->colorspace) { - case DRM_MODE_COLORIMETRY_BT601_YCC: - if (dc_crtc_timing->flags.Y_ONLY) - color_space = COLOR_SPACE_YCBCR601_LIMITED; - else - color_space = COLOR_SPACE_YCBCR601; - break; - case DRM_MODE_COLORIMETRY_BT709_YCC: - if (dc_crtc_timing->flags.Y_ONLY) - color_space = COLOR_SPACE_YCBCR709_LIMITED; - else - color_space = COLOR_SPACE_YCBCR709; - break; - case DRM_MODE_COLORIMETRY_OPRGB: - color_space = COLOR_SPACE_ADOBERGB; - break; - case DRM_MODE_COLORIMETRY_BT2020_RGB: - case DRM_MODE_COLORIMETRY_BT2020_YCC: - if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) - color_space = COLOR_SPACE_2020_RGB_FULLRANGE; - else - color_space = COLOR_SPACE_2020_YCBCR_LIMITED; - break; - case DRM_MODE_COLORIMETRY_DEFAULT: // ITU601 - default: - if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { - color_space = COLOR_SPACE_SRGB; - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) - color_space = COLOR_SPACE_SRGB_LIMITED; - /* - * 27030khz is the separation point between HDTV and SDTV - * according to HDMI spec, we use YCbCr709 and YCbCr601 - * respectively - */ - } else if (dc_crtc_timing->pix_clk_100hz > 270300) { - if (dc_crtc_timing->flags.Y_ONLY) - color_space = - COLOR_SPACE_YCBCR709_LIMITED; - else - color_space = COLOR_SPACE_YCBCR709; - } else { - if (dc_crtc_timing->flags.Y_ONLY) - color_space = - COLOR_SPACE_YCBCR601_LIMITED; - else - color_space = COLOR_SPACE_YCBCR601; - } - break; - } + dm_conn_state = to_dm_connector_state(new_con_state); - return color_space; -} - -static enum display_content_type -get_output_content_type(const struct drm_connector_state *connector_state) -{ - switch (connector_state->content_type) { - default: - case DRM_MODE_CONTENT_TYPE_NO_DATA: - return DISPLAY_CONTENT_TYPE_NO_DATA; - case DRM_MODE_CONTENT_TYPE_GRAPHICS: - return DISPLAY_CONTENT_TYPE_GRAPHICS; - case DRM_MODE_CONTENT_TYPE_PHOTO: - return DISPLAY_CONTENT_TYPE_PHOTO; - case DRM_MODE_CONTENT_TYPE_CINEMA: - return DISPLAY_CONTENT_TYPE_CINEMA; - case DRM_MODE_CONTENT_TYPE_GAME: - return DISPLAY_CONTENT_TYPE_GAME; - } -} - -static bool adjust_colour_depth_from_display_info( - struct dc_crtc_timing *timing_out, - const struct drm_display_info *info) -{ - enum dc_color_depth depth = timing_out->display_color_depth; - int normalized_clk; - - do { - normalized_clk = timing_out->pix_clk_100hz / 10; - /* YCbCr 4:2:0 requires additional adjustment of 1/2 */ - if (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420) - normalized_clk /= 2; - /* Adjusting pix clock following on HDMI spec based on colour depth */ - switch (depth) { - case COLOR_DEPTH_888: - break; - case COLOR_DEPTH_101010: - normalized_clk = (normalized_clk * 30) / 24; - break; - case COLOR_DEPTH_121212: - normalized_clk = (normalized_clk * 36) / 24; - break; - case COLOR_DEPTH_161616: - normalized_clk = (normalized_clk * 48) / 24; - break; - default: - /* The above depths are the only ones valid for HDMI. */ - return false; - } - if (normalized_clk <= info->max_tmds_clock) { - timing_out->display_color_depth = depth; - return true; - } - } while (--depth > COLOR_DEPTH_666); - return false; -} - -static void fill_stream_properties_from_drm_display_mode( - struct dc_stream_state *stream, - const struct drm_display_mode *mode_in, - const struct drm_connector *connector, - const struct drm_connector_state *connector_state, - const struct dc_stream_state *old_stream, - int requested_bpc) -{ - struct dc_crtc_timing *timing_out = &stream->timing; - const struct drm_display_info *info = &connector->display_info; - struct amdgpu_dm_connector *aconnector = NULL; - struct hdmi_vendor_infoframe hv_frame; - struct hdmi_avi_infoframe avi_frame; - ssize_t err; - - if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) - aconnector = to_amdgpu_dm_connector(connector); - - memset(&hv_frame, 0, sizeof(hv_frame)); - memset(&avi_frame, 0, sizeof(avi_frame)); - - timing_out->h_border_left = 0; - timing_out->h_border_right = 0; - timing_out->v_border_top = 0; - timing_out->v_border_bottom = 0; - /* TODO: un-hardcode */ - if (drm_mode_is_420_only(info, mode_in) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if (drm_mode_is_420_also(info, mode_in) - && aconnector - && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 - || aconnector->force_yuv420_output)) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) - && aconnector - && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422 - || aconnector->force_yuv422_output)) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422; - else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR444) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444; - else - timing_out->pixel_encoding = PIXEL_ENCODING_RGB; - - timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE; - timing_out->display_color_depth = convert_color_depth_from_display_info( - connector, - (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420), - requested_bpc); - timing_out->scan_type = SCANNING_TYPE_NODATA; - timing_out->hdmi_vic = 0; - - if (old_stream) { - timing_out->vic = old_stream->timing.vic; - timing_out->flags.HSYNC_POSITIVE_POLARITY = old_stream->timing.flags.HSYNC_POSITIVE_POLARITY; - timing_out->flags.VSYNC_POSITIVE_POLARITY = old_stream->timing.flags.VSYNC_POSITIVE_POLARITY; - } else { - timing_out->vic = drm_match_cea_mode(mode_in); - if (mode_in->flags & DRM_MODE_FLAG_PHSYNC) - timing_out->flags.HSYNC_POSITIVE_POLARITY = 1; - if (mode_in->flags & DRM_MODE_FLAG_PVSYNC) - timing_out->flags.VSYNC_POSITIVE_POLARITY = 1; - } - - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) { - err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, - (struct drm_connector *)connector, - mode_in); - if (err < 0) - drm_warn_once(connector->dev, "Failed to setup avi infoframe on connector %s: %zd\n", - connector->name, err); - timing_out->vic = avi_frame.video_code; - err = drm_hdmi_vendor_infoframe_from_display_mode(&hv_frame, - (struct drm_connector *)connector, - mode_in); - if (err < 0) - drm_warn_once(connector->dev, "Failed to setup vendor infoframe on connector %s: %zd\n", - connector->name, err); - timing_out->hdmi_vic = hv_frame.vic; - } - - if (aconnector && is_freesync_video_mode(mode_in, aconnector)) { - timing_out->h_addressable = mode_in->hdisplay; - timing_out->h_total = mode_in->htotal; - timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start; - timing_out->h_front_porch = mode_in->hsync_start - mode_in->hdisplay; - timing_out->v_total = mode_in->vtotal; - timing_out->v_addressable = mode_in->vdisplay; - timing_out->v_front_porch = mode_in->vsync_start - mode_in->vdisplay; - timing_out->v_sync_width = mode_in->vsync_end - mode_in->vsync_start; - timing_out->pix_clk_100hz = mode_in->clock * 10; - } else { - timing_out->h_addressable = mode_in->crtc_hdisplay; - timing_out->h_total = mode_in->crtc_htotal; - timing_out->h_sync_width = mode_in->crtc_hsync_end - mode_in->crtc_hsync_start; - timing_out->h_front_porch = mode_in->crtc_hsync_start - mode_in->crtc_hdisplay; - timing_out->v_total = mode_in->crtc_vtotal; - timing_out->v_addressable = mode_in->crtc_vdisplay; - timing_out->v_front_porch = mode_in->crtc_vsync_start - mode_in->crtc_vdisplay; - timing_out->v_sync_width = mode_in->crtc_vsync_end - mode_in->crtc_vsync_start; - timing_out->pix_clk_100hz = mode_in->crtc_clock * 10; - } - - timing_out->aspect_ratio = get_aspect_ratio(mode_in); - - stream->out_transfer_func.type = TF_TYPE_PREDEFINED; - stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB; - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) { - if (!adjust_colour_depth_from_display_info(timing_out, info) && - drm_mode_is_420_also(info, mode_in) && - timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - adjust_colour_depth_from_display_info(timing_out, info); - } - } - - stream->output_color_space = get_output_color_space(timing_out, connector_state); - stream->content_type = get_output_content_type(connector_state); -} - -static void -copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, - struct drm_display_mode *dst_mode) -{ - dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay; - dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay; - dst_mode->crtc_clock = src_mode->crtc_clock; - dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start; - dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end; - dst_mode->crtc_hsync_start = src_mode->crtc_hsync_start; - dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end; - dst_mode->crtc_htotal = src_mode->crtc_htotal; - dst_mode->crtc_hskew = src_mode->crtc_hskew; - dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start; - dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end; - dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start; - dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end; - dst_mode->crtc_vtotal = src_mode->crtc_vtotal; -} - -static void -decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, - const struct drm_display_mode *native_mode, - bool scale_enabled) -{ - if (scale_enabled || ( - native_mode->clock == drm_mode->clock && - native_mode->htotal == drm_mode->htotal && - native_mode->vtotal == drm_mode->vtotal)) { - if (native_mode->crtc_clock) - copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); - } else { - /* no scaling nor amdgpu inserted, no need to patch */ - } -} - -static struct dc_sink * -create_fake_sink(struct drm_device *dev, struct dc_link *link) -{ - struct dc_sink_init_data sink_init_data = { 0 }; - struct dc_sink *sink = NULL; - - sink_init_data.link = link; - sink_init_data.sink_signal = link->connector_signal; - - sink = dc_sink_create(&sink_init_data); - if (!sink) { - drm_err(dev, "Failed to create sink!\n"); - return NULL; - } - sink->sink_signal = SIGNAL_TYPE_VIRTUAL; - - return sink; -} - -static void set_multisync_trigger_params( - struct dc_stream_state *stream) -{ - struct dc_stream_state *master = NULL; - - if (stream->triggered_crtc_reset.enabled) { - master = stream->triggered_crtc_reset.event_source; - stream->triggered_crtc_reset.event = - master->timing.flags.VSYNC_POSITIVE_POLARITY ? - CRTC_EVENT_VSYNC_RISING : CRTC_EVENT_VSYNC_FALLING; - stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL; - } -} - -static void set_master_stream(struct dc_stream_state *stream_set[], - int stream_count) -{ - int j, highest_rfr = 0, master_stream = 0; - - for (j = 0; j < stream_count; j++) { - if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) { - int refresh_rate = 0; - - refresh_rate = (stream_set[j]->timing.pix_clk_100hz*100)/ - (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total); - if (refresh_rate > highest_rfr) { - highest_rfr = refresh_rate; - master_stream = j; - } - } - } - for (j = 0; j < stream_count; j++) { - if (stream_set[j]) - stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream]; - } -} - -static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) -{ - int i = 0; - struct dc_stream_state *stream; - - if (context->stream_count < 2) - return; - for (i = 0; i < context->stream_count ; i++) { - if (!context->streams[i]) - continue; - /* - * TODO: add a function to read AMD VSDB bits and set - * crtc_sync_master.multi_sync_enabled flag - * For now it's set to false - */ - } - - set_master_stream(context->streams, context->stream_count); - - for (i = 0; i < context->stream_count ; i++) { - stream = context->streams[i]; - - if (!stream) - continue; - - set_multisync_trigger_params(stream); - } -} - -/** - * DOC: FreeSync Video - * - * When a userspace application wants to play a video, the content follows a - * standard format definition that usually specifies the FPS for that format. - * The below list illustrates some video format and the expected FPS, - * respectively: - * - * - TV/NTSC (23.976 FPS) - * - Cinema (24 FPS) - * - TV/PAL (25 FPS) - * - TV/NTSC (29.97 FPS) - * - TV/NTSC (30 FPS) - * - Cinema HFR (48 FPS) - * - TV/PAL (50 FPS) - * - Commonly used (60 FPS) - * - Multiples of 24 (48,72,96 FPS) - * - * The list of standards video format is not huge and can be added to the - * connector modeset list beforehand. With that, userspace can leverage - * FreeSync to extends the front porch in order to attain the target refresh - * rate. Such a switch will happen seamlessly, without screen blanking or - * reprogramming of the output in any other way. If the userspace requests a - * modesetting change compatible with FreeSync modes that only differ in the - * refresh rate, DC will skip the full update and avoid blink during the - * transition. For example, the video player can change the modesetting from - * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without - * causing any display blink. This same concept can be applied to a mode - * setting change. - */ -static struct drm_display_mode * -get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, - bool use_probed_modes) -{ - struct drm_display_mode *m, *m_pref = NULL; - u16 current_refresh, highest_refresh; - struct list_head *list_head = use_probed_modes ? - &aconnector->base.probed_modes : - &aconnector->base.modes; - - if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - return NULL; - - if (aconnector->freesync_vid_base.clock != 0) - return &aconnector->freesync_vid_base; - - /* Find the preferred mode */ - list_for_each_entry(m, list_head, head) { - if (m->type & DRM_MODE_TYPE_PREFERRED) { - m_pref = m; - break; - } - } - - if (!m_pref) { - /* Probably an EDID with no preferred mode. Fallback to first entry */ - m_pref = list_first_entry_or_null( - &aconnector->base.modes, struct drm_display_mode, head); - if (!m_pref) { - drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n"); - return NULL; - } - } - - highest_refresh = drm_mode_vrefresh(m_pref); - - /* - * Find the mode with highest refresh rate with same resolution. - * For some monitors, preferred mode is not the mode with highest - * supported refresh rate. - */ - list_for_each_entry(m, list_head, head) { - current_refresh = drm_mode_vrefresh(m); - - if (m->hdisplay == m_pref->hdisplay && - m->vdisplay == m_pref->vdisplay && - highest_refresh < current_refresh) { - highest_refresh = current_refresh; - m_pref = m; - } - } - - drm_mode_copy(&aconnector->freesync_vid_base, m_pref); - return m_pref; -} - -static bool is_freesync_video_mode(const struct drm_display_mode *mode, - struct amdgpu_dm_connector *aconnector) -{ - struct drm_display_mode *high_mode; - int timing_diff; - - high_mode = get_highest_refresh_rate_mode(aconnector, false); - if (!high_mode || !mode) - return false; - - timing_diff = high_mode->vtotal - mode->vtotal; - - if (high_mode->clock == 0 || high_mode->clock != mode->clock || - high_mode->hdisplay != mode->hdisplay || - high_mode->vdisplay != mode->vdisplay || - high_mode->hsync_start != mode->hsync_start || - high_mode->hsync_end != mode->hsync_end || - high_mode->htotal != mode->htotal || - high_mode->hskew != mode->hskew || - high_mode->vscan != mode->vscan || - high_mode->vsync_start - mode->vsync_start != timing_diff || - high_mode->vsync_end - mode->vsync_end != timing_diff) - return false; - else - return true; -} - -#if defined(CONFIG_DRM_AMD_DC_FP) -static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, - struct dc_sink *sink, struct dc_stream_state *stream, - struct dsc_dec_dpcd_caps *dsc_caps) -{ - stream->timing.flags.DSC = 0; - dsc_caps->is_dsc_supported = false; - - if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || - sink->sink_signal == SIGNAL_TYPE_EDP)) { - if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) - dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, - dsc_caps); - else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { - if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT && - !aconnector->dsc_settings.dsc_force_disable_passthrough && - aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 && - sink->edid_caps.frl_dsc_support && - sink->edid_caps.max_frl_rate > 0 && - sink->edid_caps.frl_dsc_max_frl_rate > 0) - dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); - else - dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, - dsc_caps); - } - } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { - if (sink->edid_caps.frl_dsc_support && - sink->edid_caps.max_frl_rate > 0 && - sink->edid_caps.frl_dsc_max_frl_rate > 0) - dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); - } -} - -static void apply_dsc_policy_for_edp(struct amdgpu_dm_connector *aconnector, - struct dc_sink *sink, struct dc_stream_state *stream, - struct dsc_dec_dpcd_caps *dsc_caps, - uint32_t max_dsc_target_bpp_limit_override) -{ - const struct dc_link_settings *verified_link_cap = NULL; - u32 link_bw_in_kbps; - u32 edp_min_bpp_x16, edp_max_bpp_x16; - struct dc *dc = sink->ctx->dc; - struct dc_dsc_bw_range bw_range = {0}; - struct dc_dsc_config dsc_cfg = {0}; - struct dc_dsc_config_options dsc_options = {0}; - - dc_dsc_get_default_config_option(dc, &dsc_options); - dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; - - verified_link_cap = dc_link_get_link_cap(stream->link); - link_bw_in_kbps = dc_link_bandwidth_kbps(stream->link, verified_link_cap); - edp_min_bpp_x16 = 8 * 16; - edp_max_bpp_x16 = 8 * 16; - - if (edp_max_bpp_x16 > dsc_caps->edp_max_bits_per_pixel) - edp_max_bpp_x16 = dsc_caps->edp_max_bits_per_pixel; - - if (edp_max_bpp_x16 < edp_min_bpp_x16) - edp_min_bpp_x16 = edp_max_bpp_x16; - - if (dc_dsc_compute_bandwidth_range(dc->res_pool->dscs[0], - dc->debug.dsc_min_slice_height_override, - edp_min_bpp_x16, edp_max_bpp_x16, - dsc_caps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &bw_range)) { - - if (bw_range.max_kbps < link_bw_in_kbps) { - if (dc_dsc_compute_config(dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - 0, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &dsc_cfg)) { - stream->timing.dsc_cfg = dsc_cfg; - stream->timing.flags.DSC = 1; - stream->timing.dsc_cfg.bits_per_pixel = edp_max_bpp_x16; - } - return; - } - } - - if (dc_dsc_compute_config(dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - link_bw_in_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &dsc_cfg)) { - stream->timing.dsc_cfg = dsc_cfg; - stream->timing.flags.DSC = 1; - } -} - -static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, - struct dc_sink *sink, struct dc_stream_state *stream, - struct dsc_dec_dpcd_caps *dsc_caps) -{ - struct drm_connector *drm_connector = &aconnector->base; - u32 link_bandwidth_kbps; - struct dc *dc = sink->ctx->dc; - const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL; - u32 converter_bw_in_kbps; - u32 sink_bw_in_kbps; - u32 dsc_sink_bw_in_kbps; - u32 max_supported_bw_in_kbps, timing_bw_in_kbps; - u32 dsc_max_supported_bw_in_kbps; - u32 max_dsc_target_bpp_limit_override = - drm_connector->display_info.max_dsc_bpp; - struct dc_dsc_config_options dsc_options = {0}; - - dc_dsc_get_default_config_option(dc, &dsc_options); - dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; - - link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link, - dc_link_get_link_cap(aconnector->dc_link)); - - /* Set DSC policy according to dsc_clock_en */ - dc_dsc_policy_set_enable_dsc_when_not_needed( - aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE); - - if (sink->sink_signal == SIGNAL_TYPE_EDP && - !aconnector->dc_link->panel_config.dsc.disable_dsc_edp && - dc->caps.edp_dsc_support && aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE) { - - apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override); - - } else if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) { - if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) { - if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - link_bandwidth_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &stream->timing.dsc_cfg)) { - stream->timing.flags.DSC = 1; - drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from SST RX\n", - __func__, drm_connector->name); - } - } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { - timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link)); - converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps; - sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate); - dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); - - if (dsc_caps->is_frl) { - max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps); - max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps); - dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps); - } else { - max_supported_bw_in_kbps = link_bandwidth_kbps; - dsc_max_supported_bw_in_kbps = link_bandwidth_kbps; - } - - if (timing_bw_in_kbps > max_supported_bw_in_kbps && - max_supported_bw_in_kbps > 0 && - dsc_max_supported_bw_in_kbps > 0) - if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - dsc_max_supported_bw_in_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &stream->timing.dsc_cfg)) { - stream->timing.flags.DSC = 1; - drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n", - __func__, drm_connector->name, - (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); - } - } - } - else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { - struct dc_dsc_policy dsc_policy = {0}; - - frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); - if (frl_verified_link_cap->frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE && - aconnector->dc_link->frl_flags.force_frl_dsc) { - dc_dsc_policy_set_enable_dsc_when_not_needed(true); - dc_dsc_get_policy_for_timing(&stream->timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); - } - - timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL); - link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate); - dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); - - if ((timing_bw_in_kbps > link_bandwidth_kbps && dsc_sink_bw_in_kbps > 0) || - (dsc_policy.enable_dsc_when_not_needed || dsc_options.force_dsc_when_not_needed)) { - if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - dsc_sink_bw_in_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &stream->timing.dsc_cfg)) { - stream->timing.flags.DSC = 1; - drm_dbg_driver(drm_connector->dev, "%s: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\n", - __func__, drm_connector->name); - } - } - } - - /* Overwrite the stream flag if DSC is enabled through debugfs */ - if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE) - stream->timing.flags.DSC = 1; - - if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h) - stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h; - - if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v) - stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v; - - if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel) - stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel; -} -#endif - -static 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) -{ - struct drm_device *dev = connector->dev; - struct amdgpu_dm_connector *aconnector = NULL; - struct drm_display_mode *preferred_mode = NULL; - const struct drm_connector_state *con_state = &dm_state->base; - struct dc_stream_state *stream = NULL; - struct drm_display_mode mode; - struct drm_display_mode saved_mode; - struct drm_display_mode *freesync_mode = NULL; - bool native_mode_found = false; - bool recalculate_timing = false; - bool scale = dm_state->scaling != RMX_OFF; - int mode_refresh; - int preferred_refresh = 0; - enum color_transfer_func tf = TRANSFER_FUNC_UNKNOWN; -#if defined(CONFIG_DRM_AMD_DC_FP) - struct dsc_dec_dpcd_caps dsc_caps = {0}; -#endif - struct dc_link *link = NULL; - struct dc_sink *sink = NULL; - - drm_mode_init(&mode, drm_mode); - memset(&saved_mode, 0, sizeof(saved_mode)); - - if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) { - aconnector = NULL; - aconnector = to_amdgpu_dm_connector(connector); - link = aconnector->dc_link; - } else { - struct drm_writeback_connector *wbcon = NULL; - struct amdgpu_dm_wb_connector *dm_wbcon = NULL; - - wbcon = drm_connector_to_writeback(connector); - dm_wbcon = to_amdgpu_dm_wb_connector(wbcon); - link = dm_wbcon->link; - } - - if (!aconnector || !aconnector->dc_sink) { - sink = create_fake_sink(dev, link); - if (!sink) - return stream; - - } else { - sink = aconnector->dc_sink; - dc_sink_retain(sink); - } - - stream = dc_create_stream_for_sink(sink); - - if (stream == NULL) { - drm_err(dev, "Failed to create stream for sink!\n"); - goto finish; - } - - /* We leave this NULL for writeback connectors */ - stream->dm_stream_context = aconnector; - - stream->timing.flags.LTE_340MCSC_SCRAMBLE = - connector->display_info.hdmi.scdc.scrambling.low_rates; - - list_for_each_entry(preferred_mode, &connector->modes, head) { - /* Search for preferred mode */ - if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) { - native_mode_found = true; - break; - } - } - if (!native_mode_found) - preferred_mode = list_first_entry_or_null( - &connector->modes, - struct drm_display_mode, - head); - - mode_refresh = drm_mode_vrefresh(&mode); - - if (preferred_mode == NULL) { - /* - * This may not be an error, the use case is when we have no - * usermode calls to reset and set mode upon hotplug. In this - * case, we call set mode ourselves to restore the previous mode - * and the modelist may not be filled in time. - */ - drm_dbg_driver(dev, "No preferred mode found\n"); - } else if (aconnector) { - recalculate_timing = amdgpu_freesync_vid_mode && - is_freesync_video_mode(&mode, aconnector); - if (recalculate_timing) { - freesync_mode = get_highest_refresh_rate_mode(aconnector, false); - drm_mode_copy(&saved_mode, &mode); - saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio; - drm_mode_copy(&mode, freesync_mode); - mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio; - } else { - decide_crtc_timing_for_drm_display_mode( - &mode, preferred_mode, scale); - - preferred_refresh = drm_mode_vrefresh(preferred_mode); - } - } - - if (recalculate_timing) - drm_mode_set_crtcinfo(&saved_mode, 0); - - /* - * If scaling is enabled and refresh rate didn't change - * we copy the vic and polarities of the old timings - */ - if (!scale || mode_refresh != preferred_refresh) - fill_stream_properties_from_drm_display_mode( - stream, &mode, connector, con_state, NULL, - requested_bpc); - else - fill_stream_properties_from_drm_display_mode( - stream, &mode, connector, con_state, old_stream, - requested_bpc); - - /* The rest isn't needed for writeback connectors */ - if (!aconnector) - goto finish; - - if (aconnector->timing_changed) { - drm_dbg(aconnector->base.dev, - "overriding timing for automated test, bpc %d, changing to %d\n", - stream->timing.display_color_depth, - aconnector->timing_requested->display_color_depth); - stream->timing = *aconnector->timing_requested; - } - -#if defined(CONFIG_DRM_AMD_DC_FP) - /* SST DSC determination policy */ - update_dsc_caps(aconnector, sink, stream, &dsc_caps); - if (aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE && dsc_caps.is_dsc_supported) - apply_dsc_policy_for_stream(aconnector, sink, stream, &dsc_caps); -#endif - - update_stream_scaling_settings(dev, &mode, dm_state, stream); - - amdgpu_dm_fill_audio_info( - &stream->audio_info, - connector, - sink); - - update_stream_signal(stream, sink); - - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false); - - if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT || - stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST || - stream->signal == SIGNAL_TYPE_EDP) { - const struct dc_edid_caps *edid_caps; - unsigned int disable_colorimetry = 0; - - if (aconnector->dc_sink) { - edid_caps = &aconnector->dc_sink->edid_caps; - disable_colorimetry = edid_caps->panel_patch.disable_colorimetry; - } - - // - // should decide stream support vsc sdp colorimetry capability - // before building vsc info packet - // - stream->use_vsc_sdp_for_colorimetry = stream->link->dpcd_caps.dpcd_rev.raw >= 0x14 && - stream->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED && - !disable_colorimetry; - - if (stream->out_transfer_func.tf == TRANSFER_FUNCTION_GAMMA22) - tf = TRANSFER_FUNC_GAMMA_22; - mod_build_vsc_infopacket(stream, &stream->vsc_infopacket, stream->output_color_space, tf); - aconnector->sr_skip_count = AMDGPU_DM_PSR_ENTRY_DELAY; - - } -finish: - dc_sink_release(sink); - - return stream; -} - -/** - * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display - * @aconnector: DM connector to poll (owns @base drm_connector and @dc_link) - * @force: if true, force polling even when DAC load detection was used - * - * Used for connectors that don't support HPD (hotplug detection) to - * periodically check whether the connector is connected to a display. - * - * When connection was determined via DAC load detection, we avoid - * re-running it on normal polls to prevent visible glitches, unless - * @force is set. - * - * Return: The probed connector status (connected/disconnected/unknown). - */ -static enum drm_connector_status -amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) -{ - struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dc_link *link = aconnector->dc_link; - enum dc_connection_type conn_type = dc_connection_none; - enum drm_connector_status status = connector_status_disconnected; - - /* When we determined the connection using DAC load detection, - * do NOT poll the connector do detect disconnect because - * that would run DAC load detection again which can cause - * visible visual glitches. - * - * Only allow to poll such a connector again when forcing. - */ - if (!force && link->local_sink && link->type == dc_connection_analog_load) - return connector->status; - - mutex_lock(&aconnector->hpd_lock); - - if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) && - conn_type != dc_connection_none) { - mutex_lock(&adev->dm.dc_lock); - - /* Only call full link detection when a sink isn't created yet, - * ie. just when the display is plugged in, otherwise we risk flickering. - */ - if (link->local_sink || - dc_link_detect(link, DETECT_REASON_HPD)) - status = connector_status_connected; - - mutex_unlock(&adev->dm.dc_lock); - } - - if (connector->status != status) { - if (status == connector_status_disconnected) { - if (link->local_sink) - dc_sink_release(link->local_sink); - - link->local_sink = NULL; - link->dpcd_sink_count = 0; - link->type = dc_connection_none; - } - - amdgpu_dm_update_connector_after_detect(aconnector); - } - - mutex_unlock(&aconnector->hpd_lock); - return status; -} - -/** - * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display - * - * A connector is considered connected when it has a sink that is not NULL. - * For connectors that support HPD (hotplug detection), the connection is - * handled in the HPD interrupt. - * For connectors that may not support HPD, such as analog connectors, - * DRM will call this function repeatedly to poll them. - * - * Notes: - * 1. This interface is NOT called in context of HPD irq. - * 2. This interface *is called* in context of user-mode ioctl. Which - * makes it a bad place for *any* MST-related activity. - * - * @connector: The DRM connector we are checking. We convert it to - * amdgpu_dm_connector so we can read the DC link and state. - * @force: If true, do a full detect again. This is used even when - * a lighter check would normally be used to avoid flicker. - * - * Return: The connector status (connected, disconnected, or unknown). - * - */ -static enum drm_connector_status -amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) -{ - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - - update_subconnector_property(aconnector); - - if (aconnector->base.force == DRM_FORCE_ON || - aconnector->base.force == DRM_FORCE_ON_DIGITAL) - return connector_status_connected; - else if (aconnector->base.force == DRM_FORCE_OFF) - return connector_status_disconnected; - - /* Poll analog connectors and only when either - * disconnected or connected to an analog display. - */ - if (drm_kms_helper_is_poll_worker() && - dc_connector_supports_analog(aconnector->dc_link->link_id.id) && - (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog)) - return amdgpu_dm_connector_poll(aconnector, force); - - return (aconnector->dc_sink ? connector_status_connected : - connector_status_disconnected); -} - -int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, - struct drm_connector_state *connector_state, - struct drm_property *property, - uint64_t val) -{ - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dm_connector_state *dm_old_state = - to_dm_connector_state(connector->state); - struct dm_connector_state *dm_new_state = - to_dm_connector_state(connector_state); - - int ret = -EINVAL; - - if (property == dev->mode_config.scaling_mode_property) { - enum amdgpu_rmx_type rmx_type; - - switch (val) { - case DRM_MODE_SCALE_CENTER: - rmx_type = RMX_CENTER; - break; - case DRM_MODE_SCALE_ASPECT: - rmx_type = RMX_ASPECT; - break; - case DRM_MODE_SCALE_FULLSCREEN: - rmx_type = RMX_FULL; - break; - case DRM_MODE_SCALE_NONE: - default: - rmx_type = RMX_OFF; - break; - } - - if (dm_old_state->scaling == rmx_type) - return 0; - - dm_new_state->scaling = rmx_type; - ret = 0; - } else if (property == adev->mode_info.underscan_hborder_property) { - dm_new_state->underscan_hborder = val; - ret = 0; - } else if (property == adev->mode_info.underscan_vborder_property) { - dm_new_state->underscan_vborder = val; - ret = 0; - } else if (property == adev->mode_info.underscan_property) { - dm_new_state->underscan_enable = val; - ret = 0; - } else if (property == adev->mode_info.abm_level_property) { - switch (val) { - case ABM_SYSFS_CONTROL: - dm_new_state->abm_sysfs_forbidden = false; - break; - case ABM_LEVEL_OFF: - dm_new_state->abm_sysfs_forbidden = true; - dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; - break; - default: - dm_new_state->abm_sysfs_forbidden = true; - dm_new_state->abm_level = val; - } - ret = 0; - } - - return ret; -} - -int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, - const struct drm_connector_state *state, - struct drm_property *property, - uint64_t *val) -{ - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dm_connector_state *dm_state = - to_dm_connector_state(state); - int ret = -EINVAL; - - if (property == dev->mode_config.scaling_mode_property) { - switch (dm_state->scaling) { - case RMX_CENTER: - *val = DRM_MODE_SCALE_CENTER; - break; - case RMX_ASPECT: - *val = DRM_MODE_SCALE_ASPECT; - break; - case RMX_FULL: - *val = DRM_MODE_SCALE_FULLSCREEN; - break; - case RMX_OFF: - default: - *val = DRM_MODE_SCALE_NONE; - break; - } - ret = 0; - } else if (property == adev->mode_info.underscan_hborder_property) { - *val = dm_state->underscan_hborder; - ret = 0; - } else if (property == adev->mode_info.underscan_vborder_property) { - *val = dm_state->underscan_vborder; - ret = 0; - } else if (property == adev->mode_info.underscan_property) { - *val = dm_state->underscan_enable; - ret = 0; - } else if (property == adev->mode_info.abm_level_property) { - if (!dm_state->abm_sysfs_forbidden) - *val = ABM_SYSFS_CONTROL; - else - *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ? - dm_state->abm_level : 0; - ret = 0; - } - - return ret; -} - -static void amdgpu_dm_connector_unregister(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); - - if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) - sysfs_remove_group(&connector->kdev->kobj, &amdgpu_group); - - cec_notifier_conn_unregister(amdgpu_dm_connector->notifier); - drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux); -} - -static 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); - struct amdgpu_display_manager *dm = &adev->dm; - - /* - * Call only if mst_mgr was initialized before since it's not done - * for all connector types. - */ - if (aconnector->mst_mgr.dev) - drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); - - /* Cancel and flush any pending HDMI HPD debounce work */ - if (aconnector->hdmi_hpd_debounce_delay_ms) { - cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); - if (aconnector->hdmi_prev_sink) { - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = NULL; - } - } - - if (aconnector->bl_idx != -1) { - backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]); - dm->backlight_dev[aconnector->bl_idx] = NULL; - } - - if (aconnector->dc_em_sink) - dc_sink_release(aconnector->dc_em_sink); - aconnector->dc_em_sink = NULL; - if (aconnector->dc_sink) - dc_sink_release(aconnector->dc_sink); - aconnector->dc_sink = NULL; - - drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); - drm_connector_unregister(connector); - drm_connector_cleanup(connector); - kfree(aconnector->dm_dp_aux.aux.name); - - kfree(connector); -} - -void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) -{ - struct dm_connector_state *state = - to_dm_connector_state(connector->state); - - if (connector->state) - __drm_atomic_helper_connector_destroy_state(connector->state); - - kfree(state); - - state = kzalloc_obj(*state); - - if (state) { - state->scaling = RMX_OFF; - state->underscan_enable = false; - state->underscan_hborder = 0; - state->underscan_vborder = 0; - state->base.max_requested_bpc = 8; - state->vcpi_slots = 0; - state->pbn = 0; - - if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { - if (amdgpu_dm_abm_level <= 0) - state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; - else - state->abm_level = amdgpu_dm_abm_level; - } - - __drm_atomic_helper_connector_reset(connector, &state->base); - } -} - -struct drm_connector_state * -amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) -{ - struct dm_connector_state *state = - to_dm_connector_state(connector->state); - - struct dm_connector_state *new_state = - kmemdup(state, sizeof(*state), GFP_KERNEL); - - if (!new_state) - return NULL; - - __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base); - - new_state->freesync_capable = state->freesync_capable; - new_state->abm_level = state->abm_level; - new_state->scaling = state->scaling; - new_state->underscan_enable = state->underscan_enable; - new_state->underscan_hborder = state->underscan_hborder; - new_state->underscan_vborder = state->underscan_vborder; - new_state->vcpi_slots = state->vcpi_slots; - new_state->pbn = state->pbn; - return &new_state->base; -} - -static int -amdgpu_dm_connector_late_register(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - int r; - - if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) { - r = sysfs_create_group(&connector->kdev->kobj, - &amdgpu_group); - if (r) - return r; - } - - amdgpu_dm_register_backlight_device(amdgpu_dm_connector); - - if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || - (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { - amdgpu_dm_connector->dm_dp_aux.aux.dev = connector->kdev; - r = drm_dp_aux_register(&amdgpu_dm_connector->dm_dp_aux.aux); - if (r) - return r; - } - -#if defined(CONFIG_DEBUG_FS) - connector_debugfs_init(amdgpu_dm_connector); -#endif - - return 0; -} - -static 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; - struct dc_sink *dc_em_sink = aconnector->dc_em_sink; - const struct drm_edid *drm_edid; - struct i2c_adapter *ddc; - struct drm_device *dev = connector->dev; - - if (dc_link && dc_link->aux_mode) - ddc = &aconnector->dm_dp_aux.aux.ddc; - else - ddc = &aconnector->i2c->base; - - drm_edid = drm_edid_read_ddc(connector, ddc); - drm_edid_connector_update(connector, drm_edid); - if (!drm_edid) { - drm_err(dev, "No EDID found on connector: %s.\n", connector->name); - return; - } - - aconnector->drm_edid = drm_edid; - /* Update emulated (virtual) sink's EDID */ - if (dc_em_sink && dc_link) { - // FIXME: Get rid of drm_edid_raw() - const struct edid *edid = drm_edid_raw(drm_edid); - - memset(&dc_em_sink->edid_caps, 0, sizeof(struct dc_edid_caps)); - memmove(dc_em_sink->dc_edid.raw_edid, edid, - (edid->extensions + 1) * EDID_LENGTH); - dm_helpers_parse_edid_caps( - dc_link, - &dc_em_sink->dc_edid, - &dc_em_sink->edid_caps); - } -} - -static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { - .reset = amdgpu_dm_connector_funcs_reset, - .detect = amdgpu_dm_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = amdgpu_dm_connector_destroy, - .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, - .atomic_set_property = amdgpu_dm_connector_atomic_set_property, - .atomic_get_property = amdgpu_dm_connector_atomic_get_property, - .late_register = amdgpu_dm_connector_late_register, - .early_unregister = amdgpu_dm_connector_unregister, - .force = amdgpu_dm_connector_funcs_force -}; - -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) -{ - struct drm_connector *connector = &aconnector->base; - struct dc_link *dc_link = aconnector->dc_link; - struct dc_sink_init_data init_params = { - .link = aconnector->dc_link, - .sink_signal = SIGNAL_TYPE_VIRTUAL - }; - const struct drm_edid *drm_edid; - const struct edid *edid; - struct i2c_adapter *ddc; - - if (dc_link && dc_link->aux_mode) - ddc = &aconnector->dm_dp_aux.aux.ddc; - else - ddc = &aconnector->i2c->base; - - drm_edid = drm_edid_read_ddc(connector, ddc); - drm_edid_connector_update(connector, drm_edid); - if (!drm_edid) { - drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name); - return; - } - - if (connector->display_info.is_hdmi) - init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; - - aconnector->drm_edid = drm_edid; - - edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - aconnector->dc_em_sink = dc_link_add_remote_sink( - aconnector->dc_link, - (uint8_t *)edid, - (edid->extensions + 1) * EDID_LENGTH, - &init_params); - - if (aconnector->base.force == DRM_FORCE_ON) { - aconnector->dc_sink = aconnector->dc_link->local_sink ? - aconnector->dc_link->local_sink : - aconnector->dc_em_sink; - if (aconnector->dc_sink) - dc_sink_retain(aconnector->dc_sink); - } -} - -static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) -{ - struct dc_link *link = (struct dc_link *)aconnector->dc_link; - - /* - * In case of headless boot with force on for DP managed connector - * Those settings have to be != 0 to get initial modeset - */ - if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) { - link->verified_link_cap.lane_count = LANE_COUNT_FOUR; - link->verified_link_cap.link_rate = LINK_RATE_HIGH2; - } - - create_eml_sink(aconnector); -} - -static enum dc_status dm_validate_stream_and_context(struct dc *dc, - struct dc_stream_state *stream) -{ - enum dc_status dc_result = DC_ERROR_UNEXPECTED; - struct dc_plane_state *dc_plane_state = NULL; - struct dc_state *dc_state = NULL; - - if (!stream) - goto cleanup; - - dc_plane_state = dc_create_plane_state(dc); - if (!dc_plane_state) - goto cleanup; - - dc_state = dc_state_create(dc, NULL); - if (!dc_state) - goto cleanup; - - /* populate stream to plane */ - dc_plane_state->src_rect.height = stream->src.height; - dc_plane_state->src_rect.width = stream->src.width; - dc_plane_state->dst_rect.height = stream->src.height; - dc_plane_state->dst_rect.width = stream->src.width; - dc_plane_state->clip_rect.height = stream->src.height; - dc_plane_state->clip_rect.width = stream->src.width; - dc_plane_state->plane_size.surface_pitch = ((stream->src.width + 255) / 256) * 256; - dc_plane_state->plane_size.surface_size.height = stream->src.height; - dc_plane_state->plane_size.surface_size.width = stream->src.width; - dc_plane_state->plane_size.chroma_size.height = stream->src.height; - dc_plane_state->plane_size.chroma_size.width = stream->src.width; - dc_plane_state->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; - dc_plane_state->tiling_info.gfx9.swizzle = DC_SW_UNKNOWN; - dc_plane_state->rotation = ROTATION_ANGLE_0; - dc_plane_state->is_tiling_rotated = false; - dc_plane_state->tiling_info.gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL; - - dc_result = dc_validate_stream(dc, stream); - if (dc_result == DC_OK) - dc_result = dc_validate_plane(dc, dc_plane_state); - - if (dc_result == DC_OK) - dc_result = dc_state_add_stream(dc, dc_state, stream); - - if (dc_result == DC_OK && !dc_state_add_plane( - dc, - stream, - dc_plane_state, - dc_state)) - dc_result = DC_FAIL_ATTACH_SURFACES; - - if (dc_result == DC_OK) - dc_result = dc_validate_global_state(dc, dc_state, DC_VALIDATE_MODE_ONLY); - -cleanup: - if (dc_state) - dc_state_release(dc_state); - - if (dc_plane_state) - dc_plane_state_release(dc_plane_state); - - return dc_result; -} - -struct dc_stream_state * -create_validate_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) -{ - struct amdgpu_dm_connector *aconnector = NULL; - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct dc_stream_state *stream; - const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL; - int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8; - enum dc_status dc_result = DC_OK; - uint8_t bpc_limit = 6; - - if (!dm_state) - return NULL; - - if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) - aconnector = to_amdgpu_dm_connector(connector); - - if (aconnector && - (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A || - aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL || - aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)) - bpc_limit = 8; - - do { - drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc); - stream = create_stream_for_sink(connector, drm_mode, - dm_state, old_stream, - requested_bpc); - if (stream == NULL) { - drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n"); - break; - } - - dc_result = dc_validate_stream(adev->dm.dc, stream); - - if (!aconnector) /* writeback connector */ - return stream; - - if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) - dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream); - - if (dc_result == DC_OK) - dc_result = dm_validate_stream_and_context(adev->dm.dc, stream); - - if (dc_result != DC_OK) { - drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n", - drm_mode->hdisplay, - drm_mode->vdisplay, - drm_mode->clock, - dc_pixel_encoding_to_str(stream->timing.pixel_encoding), - dc_color_depth_to_str(stream->timing.display_color_depth), - dc_status_to_str(dc_result)); - - dc_stream_release(stream); - stream = NULL; - requested_bpc -= 2; /* lower bpc to retry validation */ - } - - } while (stream == NULL && requested_bpc >= bpc_limit); - - switch (dc_result) { - /* - * If we failed to validate DP bandwidth stream with the requested RGB color depth, - * we try to fallback and configure in order: - * YUV422 (8bpc, 6bpc) - * YUV420 (8bpc, 6bpc) - */ - case DC_FAIL_ENC_VALIDATE: - case DC_EXCEED_DONGLE_CAP: - case DC_NO_DP_LINK_BANDWIDTH: - /* recursively entered twice and already tried both YUV422 and YUV420 */ - if (aconnector->force_yuv422_output && aconnector->force_yuv420_output) - break; - /* first failure; try YUV422 */ - if (!aconnector->force_yuv422_output) { - drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n", - __func__, __LINE__, dc_result); - aconnector->force_yuv422_output = true; - /* recursively entered and YUV422 failed, try YUV420 */ - } else if (!aconnector->force_yuv420_output) { - drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n", - __func__, __LINE__, dc_result); - aconnector->force_yuv420_output = true; - } - stream = create_validate_stream_for_sink(connector, drm_mode, - dm_state, old_stream); - aconnector->force_yuv422_output = false; - aconnector->force_yuv420_output = false; - break; - case DC_OK: - break; - default: - drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n", - __func__, __LINE__, dc_result); - break; - } - - return stream; -} - -enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, - const struct drm_display_mode *mode) -{ - int result = MODE_ERROR; - struct dc_sink *dc_sink; - struct drm_display_mode *test_mode; - /* TODO: Unhardcode stream count */ - struct dc_stream_state *stream; - /* we always have an amdgpu_dm_connector here since we got - * here via the amdgpu_dm_connector_helper_funcs - */ - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - - if ((mode->flags & DRM_MODE_FLAG_INTERLACE) || - (mode->flags & DRM_MODE_FLAG_DBLSCAN)) - return result; - - /* - * Only run this the first time mode_valid is called to initilialize - * EDID mgmt - */ - if (aconnector->base.force != DRM_FORCE_UNSPECIFIED && - !aconnector->dc_em_sink) - handle_edid_mgmt(aconnector); - - dc_sink = to_amdgpu_dm_connector(connector)->dc_sink; - - if (dc_sink == NULL && aconnector->base.force != DRM_FORCE_ON_DIGITAL && - aconnector->base.force != DRM_FORCE_ON) { - drm_err(connector->dev, "dc_sink is NULL!\n"); - goto fail; - } - - test_mode = drm_mode_duplicate(connector->dev, mode); - if (!test_mode) - goto fail; - - drm_mode_set_crtcinfo(test_mode, 0); - - stream = create_validate_stream_for_sink(connector, test_mode, - to_dm_connector_state(connector->state), - NULL); - drm_mode_destroy(connector->dev, test_mode); - if (stream) { - dc_stream_release(stream); - result = MODE_OK; - } - -fail: - /* TODO: error handling*/ - return result; -} - -static int fill_hdr_info_packet(const struct drm_connector_state *state, - struct dc_info_packet *out) -{ - struct hdmi_drm_infoframe frame; - unsigned char buf[30]; /* 26 + 4 */ - ssize_t len; - int ret, i; - - memset(out, 0, sizeof(*out)); - - if (!state->hdr_output_metadata) - return 0; - - ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, state); - if (ret) - return ret; - - len = hdmi_drm_infoframe_pack_only(&frame, buf, sizeof(buf)); - if (len < 0) - return (int)len; - - /* Static metadata is a fixed 26 bytes + 4 byte header. */ - if (len != 30) - return -EINVAL; - - /* Prepare the infopacket for DC. */ - switch (state->connector->connector_type) { - case DRM_MODE_CONNECTOR_HDMIA: - out->hb0 = 0x87; /* type */ - out->hb1 = 0x01; /* version */ - out->hb2 = 0x1A; /* length */ - out->sb[0] = buf[3]; /* checksum */ - i = 1; - break; - - case DRM_MODE_CONNECTOR_DisplayPort: - case DRM_MODE_CONNECTOR_eDP: - out->hb0 = 0x00; /* sdp id, zero */ - out->hb1 = 0x87; /* type */ - out->hb2 = 0x1D; /* payload len - 1 */ - out->hb3 = (0x13 << 2); /* sdp version */ - out->sb[0] = 0x01; /* version */ - out->sb[1] = 0x1A; /* length */ - i = 2; - break; - - default: - return -EINVAL; - } - - memcpy(&out->sb[i], &buf[4], 26); - out->valid = true; - - print_hex_dump(KERN_DEBUG, "HDR SB:", DUMP_PREFIX_NONE, 16, 1, out->sb, - sizeof(out->sb), false); - - return 0; -} - -static int -amdgpu_dm_connector_atomic_check(struct drm_connector *conn, - struct drm_atomic_commit *state) -{ - struct drm_connector_state *new_con_state = - drm_atomic_get_new_connector_state(state, conn); - struct drm_connector_state *old_con_state = - drm_atomic_get_old_connector_state(state, conn); - struct drm_crtc *crtc = new_con_state->crtc; - struct drm_crtc_state *new_crtc_state; - struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); - int ret; - - if (WARN_ON(unlikely(!old_con_state || !new_con_state))) - return -EINVAL; - - trace_amdgpu_dm_connector_atomic_check(new_con_state); - - if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { - ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr); - if (ret < 0) - return ret; - } - - if (!crtc) - return 0; - - if (new_con_state->privacy_screen_sw_state != old_con_state->privacy_screen_sw_state) { - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - new_crtc_state->mode_changed = true; - } - - if (new_con_state->colorspace != old_con_state->colorspace) { - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - new_crtc_state->mode_changed = true; - } - - if (new_con_state->content_type != old_con_state->content_type) { - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - new_crtc_state->mode_changed = true; - } - - if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) { - struct dc_info_packet hdr_infopacket; - - ret = fill_hdr_info_packet(new_con_state, &hdr_infopacket); - if (ret) - return ret; - - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - /* - * DC considers the stream backends changed if the - * static metadata changes. Forcing the modeset also - * gives a simple way for userspace to switch from - * 8bpc to 10bpc when setting the metadata to enter - * or exit HDR. - * - * Changing the static metadata after it's been - * set is permissible, however. So only force a - * modeset if we're entering or exiting HDR. - */ - new_crtc_state->mode_changed = new_crtc_state->mode_changed || - !old_con_state->hdr_output_metadata || - !new_con_state->hdr_output_metadata; - } - - return 0; -} - -static const struct drm_connector_helper_funcs -amdgpu_dm_connector_helper_funcs = { - /* - * If hotplugging a second bigger display in FB Con mode, bigger resolution - * modes will be filtered by drm_mode_validate_size(), and those modes - * are missing after user start lightdm. So we need to renew modes list. - * in get_modes call back, not just return the modes count - */ - .get_modes = get_modes, - .mode_valid = amdgpu_dm_connector_mode_valid, - .atomic_check = amdgpu_dm_connector_atomic_check, -}; - -static void dm_encoder_helper_disable(struct drm_encoder *encoder) -{ - -} - -int convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth) -{ - switch (display_color_depth) { - case COLOR_DEPTH_666: - return 6; - case COLOR_DEPTH_888: - return 8; - case COLOR_DEPTH_101010: - return 10; - case COLOR_DEPTH_121212: - return 12; - case COLOR_DEPTH_141414: - return 14; - case COLOR_DEPTH_161616: - return 16; - default: - break; - } - return 0; -} - -static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - struct drm_atomic_commit *state = crtc_state->state; - struct drm_connector *connector = conn_state->connector; - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - struct dm_connector_state *dm_new_connector_state = to_dm_connector_state(conn_state); - const struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; - struct drm_dp_mst_topology_mgr *mst_mgr; - struct drm_dp_mst_port *mst_port; - struct drm_dp_mst_topology_state *mst_state; - enum dc_color_depth color_depth; - int clock, bpp = 0; - bool is_y420 = false; - - if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || - (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { - struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); - struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; - enum drm_mode_status result; - - result = drm_crtc_helper_mode_valid_fixed(encoder->crtc, adjusted_mode, native_mode); - if (result != MODE_OK && dm_new_connector_state->scaling == RMX_OFF) { - drm_dbg_driver(encoder->dev, - "mode %dx%d@%dHz is not native, enabling scaling\n", - adjusted_mode->hdisplay, adjusted_mode->vdisplay, - drm_mode_vrefresh(adjusted_mode)); - dm_new_connector_state->scaling = RMX_ASPECT; - } - return 0; - } - - if (!aconnector->mst_output_port) - return 0; - - mst_port = aconnector->mst_output_port; - mst_mgr = &aconnector->mst_root->mst_mgr; - - if (!crtc_state->connectors_changed && !crtc_state->mode_changed) - return 0; - - mst_state = drm_atomic_get_mst_topology_state(state, mst_mgr); - if (IS_ERR(mst_state)) - return PTR_ERR(mst_state); - - mst_state->pbn_div.full = dm_mst_get_pbn_divider(aconnector->mst_root->dc_link); - - if (!state->duplicated) { - int max_bpc = conn_state->max_requested_bpc; - - is_y420 = drm_mode_is_420_also(&connector->display_info, adjusted_mode) && - aconnector->force_yuv420_output; - color_depth = convert_color_depth_from_display_info(connector, - is_y420, - max_bpc); - bpp = convert_dc_color_depth_into_bpc(color_depth) * 3; - clock = adjusted_mode->clock; - dm_new_connector_state->pbn = drm_dp_calc_pbn_mode(clock, bpp << 4); - } - - dm_new_connector_state->vcpi_slots = - drm_dp_atomic_find_time_slots(state, mst_mgr, mst_port, - dm_new_connector_state->pbn); - if (dm_new_connector_state->vcpi_slots < 0) { - drm_dbg_atomic(connector->dev, "failed finding vcpi slots: %d\n", (int)dm_new_connector_state->vcpi_slots); - return dm_new_connector_state->vcpi_slots; - } - return 0; -} - -const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { - .disable = dm_encoder_helper_disable, - .atomic_check = dm_encoder_helper_atomic_check -}; - -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) -{ - struct dc_stream_state *stream = NULL; - struct drm_connector *connector; - struct drm_connector_state *new_con_state; - struct amdgpu_dm_connector *aconnector; - struct dm_connector_state *dm_conn_state; - int i, j, ret; - int vcpi, pbn_div, pbn = 0, slot_num = 0; - - for_each_new_connector_in_state(state, connector, new_con_state, i) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - - if (!aconnector->mst_output_port) - continue; - - if (!new_con_state || !new_con_state->crtc) - continue; - - dm_conn_state = to_dm_connector_state(new_con_state); - - for (j = 0; j < dc_state->stream_count; j++) { - stream = dc_state->streams[j]; - if (!stream) - continue; + for (j = 0; j < dc_state->stream_count; j++) { + stream = dc_state->streams[j]; + if (!stream) + continue; if ((struct amdgpu_dm_connector *)stream->dm_stream_context == aconnector) break; @@ -5843,759 +3494,12 @@ static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, vcpi = drm_dp_mst_atomic_enable_dsc(state, aconnector->mst_output_port, pbn, true); if (vcpi < 0) - return vcpi; - - dm_conn_state->pbn = pbn; - dm_conn_state->vcpi_slots = vcpi; - } - return 0; -} - -static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) -{ - switch (st) { - case SIGNAL_TYPE_HDMI_TYPE_A: - return DRM_MODE_CONNECTOR_HDMIA; - case SIGNAL_TYPE_EDP: - return DRM_MODE_CONNECTOR_eDP; - case SIGNAL_TYPE_LVDS: - return DRM_MODE_CONNECTOR_LVDS; - case SIGNAL_TYPE_RGB: - return DRM_MODE_CONNECTOR_VGA; - case SIGNAL_TYPE_DISPLAY_PORT: - case SIGNAL_TYPE_DISPLAY_PORT_MST: - /* External DP bridges have a different connector type. */ - if (connector_id == CONNECTOR_ID_VGA) - return DRM_MODE_CONNECTOR_VGA; - else if (connector_id == CONNECTOR_ID_LVDS) - return DRM_MODE_CONNECTOR_LVDS; - - return DRM_MODE_CONNECTOR_DisplayPort; - case SIGNAL_TYPE_DVI_DUAL_LINK: - case SIGNAL_TYPE_DVI_SINGLE_LINK: - if (connector_id == CONNECTOR_ID_SINGLE_LINK_DVII || - connector_id == CONNECTOR_ID_DUAL_LINK_DVII) - return DRM_MODE_CONNECTOR_DVII; - - return DRM_MODE_CONNECTOR_DVID; - case SIGNAL_TYPE_VIRTUAL: - return DRM_MODE_CONNECTOR_VIRTUAL; - - default: - return DRM_MODE_CONNECTOR_Unknown; - } -} - -static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) -{ - struct drm_encoder *encoder; - - /* There is only one encoder per connector */ - drm_connector_for_each_possible_encoder(connector, encoder) - return encoder; - - return NULL; -} - -static void amdgpu_dm_get_native_mode(struct drm_connector *connector) -{ - struct drm_encoder *encoder; - struct amdgpu_encoder *amdgpu_encoder; - - encoder = amdgpu_dm_connector_to_encoder(connector); - - if (encoder == NULL) - return; - - amdgpu_encoder = to_amdgpu_encoder(encoder); - - amdgpu_encoder->native_mode.clock = 0; - - if (!list_empty(&connector->probed_modes)) { - struct drm_display_mode *preferred_mode = NULL; - - list_for_each_entry(preferred_mode, - &connector->probed_modes, - head) { - if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) - amdgpu_encoder->native_mode = *preferred_mode; - - break; - } - - } -} - -static struct drm_display_mode * -amdgpu_dm_create_common_mode(struct drm_encoder *encoder, - const char *name, - int hdisplay, int vdisplay) -{ - struct drm_device *dev = encoder->dev; - struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); - struct drm_display_mode *mode = NULL; - struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; - - mode = drm_mode_duplicate(dev, native_mode); - - if (mode == NULL) - return NULL; - - mode->hdisplay = hdisplay; - mode->vdisplay = vdisplay; - mode->type &= ~DRM_MODE_TYPE_PREFERRED; - strscpy(mode->name, name, DRM_DISPLAY_MODE_LEN); - - return mode; - -} - -static const struct amdgpu_dm_mode_size { - char name[DRM_DISPLAY_MODE_LEN]; - int w; - int h; -} common_modes[] = { - { "640x480", 640, 480}, - { "800x600", 800, 600}, - { "1024x768", 1024, 768}, - { "1280x720", 1280, 720}, - { "1280x800", 1280, 800}, - {"1280x1024", 1280, 1024}, - { "1440x900", 1440, 900}, - {"1680x1050", 1680, 1050}, - {"1600x1200", 1600, 1200}, - {"1920x1080", 1920, 1080}, - {"1920x1200", 1920, 1200} -}; - -static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, - struct drm_connector *connector) -{ - struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); - struct drm_display_mode *mode = NULL; - struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - int i; - int n; - - if ((connector->connector_type != DRM_MODE_CONNECTOR_eDP) && - (connector->connector_type != DRM_MODE_CONNECTOR_LVDS)) - return; - - n = ARRAY_SIZE(common_modes); - - for (i = 0; i < n; i++) { - struct drm_display_mode *curmode = NULL; - bool mode_existed = false; - - if (common_modes[i].w > native_mode->hdisplay || - common_modes[i].h > native_mode->vdisplay || - (common_modes[i].w == native_mode->hdisplay && - common_modes[i].h == native_mode->vdisplay)) - continue; - - list_for_each_entry(curmode, &connector->probed_modes, head) { - if (common_modes[i].w == curmode->hdisplay && - common_modes[i].h == curmode->vdisplay) { - mode_existed = true; - break; - } - } - - if (mode_existed) - continue; - - mode = amdgpu_dm_create_common_mode(encoder, - common_modes[i].name, common_modes[i].w, - common_modes[i].h); - if (!mode) - continue; - - drm_mode_probed_add(connector, mode); - amdgpu_dm_connector->num_modes++; - } -} - -static void amdgpu_set_panel_orientation(struct drm_connector *connector) -{ - struct drm_encoder *encoder; - struct amdgpu_encoder *amdgpu_encoder; - const struct drm_display_mode *native_mode; - - if (connector->connector_type != DRM_MODE_CONNECTOR_eDP && - connector->connector_type != DRM_MODE_CONNECTOR_LVDS) - return; - - mutex_lock(&connector->dev->mode_config.mutex); - amdgpu_dm_connector_get_modes(connector); - mutex_unlock(&connector->dev->mode_config.mutex); - - encoder = amdgpu_dm_connector_to_encoder(connector); - if (!encoder) - return; - - amdgpu_encoder = to_amdgpu_encoder(encoder); - - native_mode = &amdgpu_encoder->native_mode; - if (native_mode->hdisplay == 0 || native_mode->vdisplay == 0) - return; - - drm_connector_set_panel_orientation_with_quirk(connector, - DRM_MODE_PANEL_ORIENTATION_UNKNOWN, - native_mode->hdisplay, - native_mode->vdisplay); -} - -static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, - const struct drm_edid *drm_edid) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - - if (drm_edid) { - /* empty probed_modes */ - INIT_LIST_HEAD(&connector->probed_modes); - amdgpu_dm_connector->num_modes = - drm_edid_connector_add_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 - * later in the probed mode list could be of higher - * and preferred resolution. For example, 3840x2160 - * resolution in base EDID preferred timing and 4096x2160 - * preferred resolution in DID extension block later. - */ - drm_mode_sort(&connector->probed_modes); - amdgpu_dm_get_native_mode(connector); - - /* Freesync capabilities are reset by calling - * drm_edid_connector_add_modes() and need to be - * restored here. - */ - amdgpu_dm_update_freesync_caps(connector, drm_edid, false); - } else { - amdgpu_dm_connector->num_modes = 0; - } -} - -static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, - struct drm_display_mode *mode) -{ - struct drm_display_mode *m; - - list_for_each_entry(m, &aconnector->base.probed_modes, head) { - if (drm_mode_equal(m, mode)) - return true; - } - - return false; -} - -static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) -{ - const struct drm_display_mode *m; - struct drm_display_mode *new_mode; - uint i; - u32 new_modes_count = 0; - - /* Standard FPS values - * - * 23.976 - TV/NTSC - * 24 - Cinema - * 25 - TV/PAL - * 29.97 - TV/NTSC - * 30 - TV/NTSC - * 48 - Cinema HFR - * 50 - TV/PAL - * 60 - Commonly used - * 48,72,96,120 - Multiples of 24 - */ - static const u32 common_rates[] = { - 23976, 24000, 25000, 29970, 30000, - 48000, 50000, 60000, 72000, 96000, 120000 - }; - - /* - * Find mode with highest refresh rate with the same resolution - * as the preferred mode. Some monitors report a preferred mode - * with lower resolution than the highest refresh rate supported. - */ - - m = get_highest_refresh_rate_mode(aconnector, true); - if (!m) - return 0; - - for (i = 0; i < ARRAY_SIZE(common_rates); i++) { - u64 target_vtotal, target_vtotal_diff; - u64 num, den; - - if (drm_mode_vrefresh(m) * 1000 < common_rates[i]) - continue; - - if (common_rates[i] < aconnector->min_vfreq * 1000 || - common_rates[i] > aconnector->max_vfreq * 1000) - continue; - - num = (unsigned long long)m->clock * 1000 * 1000; - den = common_rates[i] * (unsigned long long)m->htotal; - target_vtotal = div_u64(num, den); - target_vtotal_diff = target_vtotal - m->vtotal; - - /* Check for illegal modes */ - if (m->vsync_start + target_vtotal_diff < m->vdisplay || - m->vsync_end + target_vtotal_diff < m->vsync_start || - m->vtotal + target_vtotal_diff < m->vsync_end) - continue; - - new_mode = drm_mode_duplicate(aconnector->base.dev, m); - if (!new_mode) - goto out; - - new_mode->vtotal += (u16)target_vtotal_diff; - new_mode->vsync_start += (u16)target_vtotal_diff; - new_mode->vsync_end += (u16)target_vtotal_diff; - new_mode->type &= ~DRM_MODE_TYPE_PREFERRED; - new_mode->type |= DRM_MODE_TYPE_DRIVER; - - if (!is_duplicate_mode(aconnector, new_mode)) { - drm_mode_probed_add(&aconnector->base, new_mode); - new_modes_count += 1; - } else - drm_mode_destroy(aconnector->base.dev, new_mode); - } - out: - return new_modes_count; -} - -static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, - const struct drm_edid *drm_edid) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - - if (!(amdgpu_freesync_vid_mode && drm_edid)) - return; - - if (!amdgpu_dm_connector->dc_sink || !amdgpu_dm_connector->dc_link) - return; - - if (!dc_supports_vrr(amdgpu_dm_connector->dc_sink->ctx->dce_version)) - return; - - if (dc_connector_supports_analog(amdgpu_dm_connector->dc_link->link_id.id) && - amdgpu_dm_connector->dc_sink->edid_caps.analog) - return; - - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - amdgpu_dm_connector->num_modes += - add_fs_modes(amdgpu_dm_connector); -} - -static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - struct dc_link *dc_link = amdgpu_dm_connector->dc_link; - struct drm_encoder *encoder; - const struct drm_edid *drm_edid = amdgpu_dm_connector->drm_edid; - struct dc_link_settings *verified_link_cap = &dc_link->verified_link_cap; - const struct dc *dc = dc_link->dc; - - encoder = amdgpu_dm_connector_to_encoder(connector); - - if (!drm_edid) { - amdgpu_dm_connector->num_modes = - drm_add_modes_noedid(connector, 640, 480); - if (dc->link_srv->dp_get_encoding_format(verified_link_cap) == DP_128b_132b_ENCODING) - amdgpu_dm_connector->num_modes += - drm_add_modes_noedid(connector, 1920, 1080); - - if (amdgpu_dm_connector->dc_sink && - amdgpu_dm_connector->dc_sink->edid_caps.analog && - dc_connector_supports_analog(dc_link->link_id.id)) { - /* Analog monitor connected by DAC load detection. - * Add common modes. It will be up to the user to select one that works. - */ - for (int i = 0; i < ARRAY_SIZE(common_modes); i++) - amdgpu_dm_connector->num_modes += drm_add_modes_noedid( - connector, common_modes[i].w, common_modes[i].h); - } - } else { - amdgpu_dm_connector_ddc_get_modes(connector, drm_edid); - if (encoder) - amdgpu_dm_connector_add_common_modes(encoder, connector); - amdgpu_dm_connector_add_freesync_modes(connector, drm_edid); - } - amdgpu_dm_fbc_init(connector); - - return amdgpu_dm_connector->num_modes; -} - -static const u32 supported_colorspaces = - BIT(DRM_MODE_COLORIMETRY_BT709_YCC) | - BIT(DRM_MODE_COLORIMETRY_OPRGB) | - BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) | - BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); - -void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector, - int connector_type, - struct dc_link *link, - int link_index) -{ - struct amdgpu_device *adev = drm_to_adev(dm->ddev); - - /* - * Some of the properties below require access to state, like bpc. - * Allocate some default initial connector state with our reset helper. - */ - if (aconnector->base.funcs->reset) - aconnector->base.funcs->reset(&aconnector->base); - - aconnector->connector_id = link_index; - aconnector->bl_idx = -1; - aconnector->dc_link = link; - aconnector->base.interlace_allowed = false; - aconnector->base.doublescan_allowed = false; - aconnector->base.stereo_allowed = false; - aconnector->base.dpms = DRM_MODE_DPMS_OFF; - aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */ - aconnector->audio_inst = -1; - aconnector->pack_sdp_v1_3 = false; - aconnector->as_type = ADAPTIVE_SYNC_TYPE_NONE; - memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info)); - mutex_init(&aconnector->hpd_lock); - mutex_init(&aconnector->handle_mst_msg_ready); - - /* - * If HDMI HPD debounce delay is set, use the minimum between selected - * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS - */ - if (amdgpu_hdmi_hpd_debounce_delay_ms) { - aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, - AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); - INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work); - aconnector->hdmi_prev_sink = NULL; - } else { - aconnector->hdmi_hpd_debounce_delay_ms = 0; - } - - /* - * configure support HPD hot plug connector_>polled default value is 0 - * which means HPD hot plug not supported - */ - switch (connector_type) { - case DRM_MODE_CONNECTOR_HDMIA: - aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; - aconnector->base.ycbcr_420_allowed = - link->link_enc->features.hdmi_ycbcr420_supported ? true : false; - break; - case DRM_MODE_CONNECTOR_DisplayPort: - aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; - link->link_enc = link_enc_cfg_get_link_enc(link); - ASSERT(link->link_enc); - if (link->link_enc) - aconnector->base.ycbcr_420_allowed = - link->link_enc->features.dp_ycbcr420_supported ? true : false; - break; - case DRM_MODE_CONNECTOR_DVID: - aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; - break; - case DRM_MODE_CONNECTOR_DVII: - case DRM_MODE_CONNECTOR_VGA: - aconnector->base.polled = - DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; - break; - default: - break; - } - - drm_object_attach_property(&aconnector->base.base, - dm->ddev->mode_config.scaling_mode_property, - DRM_MODE_SCALE_NONE); - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA - || (connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root)) - drm_connector_attach_broadcast_rgb_property(&aconnector->base); - - drm_object_attach_property(&aconnector->base.base, - adev->mode_info.underscan_property, - UNDERSCAN_OFF); - drm_object_attach_property(&aconnector->base.base, - adev->mode_info.underscan_hborder_property, - 0); - drm_object_attach_property(&aconnector->base.base, - adev->mode_info.underscan_vborder_property, - 0); - - if (!aconnector->mst_root) - drm_connector_attach_max_bpc_property(&aconnector->base, 8, 16); - - aconnector->base.state->max_bpc = 16; - aconnector->base.state->max_requested_bpc = aconnector->base.state->max_bpc; - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { - /* Content Type is currently only implemented for HDMI. */ - drm_connector_attach_content_type_property(&aconnector->base); - } - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { - if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces)) - drm_connector_attach_colorspace_property(&aconnector->base); - } else if ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) || - connector_type == DRM_MODE_CONNECTOR_eDP) { - if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces)) - drm_connector_attach_colorspace_property(&aconnector->base); - } - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA || - connector_type == DRM_MODE_CONNECTOR_DisplayPort || - connector_type == DRM_MODE_CONNECTOR_eDP) { - drm_connector_attach_hdr_output_metadata_property(&aconnector->base); - - if (!aconnector->mst_root) - drm_connector_attach_vrr_capable_property(&aconnector->base); - - if (adev->dm.hdcp_workqueue) - drm_connector_attach_content_protection_property(&aconnector->base, true); - } - - if (connector_type == DRM_MODE_CONNECTOR_eDP) { - struct drm_privacy_screen *privacy_screen; - - drm_connector_attach_panel_type_property(&aconnector->base); - - privacy_screen = drm_privacy_screen_get(adev_to_drm(adev)->dev, NULL); - if (!IS_ERR(privacy_screen)) { - drm_connector_attach_privacy_screen_provider(&aconnector->base, - privacy_screen); - } else if (PTR_ERR(privacy_screen) != -ENODEV) { - drm_warn(adev_to_drm(adev), "Error getting privacy-screen\n"); - } - } -} - -static 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); - struct ddc_service *ddc_service = i2c->ddc_service; - struct i2c_command cmd; - int i; - int result = -EIO; - - if (!ddc_service->ddc_pin) - return result; - - cmd.payloads = kzalloc_objs(struct i2c_payload, num); - - if (!cmd.payloads) - return result; - - cmd.number_of_payloads = num; - cmd.engine = I2C_COMMAND_ENGINE_DEFAULT; - cmd.speed = 100; - - for (i = 0; i < num; i++) { - cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD); - cmd.payloads[i].address = msgs[i].addr; - cmd.payloads[i].length = msgs[i].len; - cmd.payloads[i].data = msgs[i].buf; - } - - if (i2c->oem) { - if (dc_submit_i2c_oem( - ddc_service->ctx->dc, - &cmd)) - result = num; - } else { - if (dc_submit_i2c( - ddc_service->ctx->dc, - ddc_service->link->link_index, - &cmd)) - result = num; - } - - kfree(cmd.payloads); - return result; -} - -static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} - -static const struct i2c_algorithm amdgpu_dm_i2c_algo = { - .master_xfer = amdgpu_dm_i2c_xfer, - .functionality = amdgpu_dm_i2c_func, -}; - -static struct amdgpu_i2c_adapter * -create_i2c(struct ddc_service *ddc_service, bool oem) -{ - struct amdgpu_device *adev = ddc_service->ctx->driver_context; - struct amdgpu_i2c_adapter *i2c; - - i2c = kzalloc_obj(struct amdgpu_i2c_adapter); - if (!i2c) - return NULL; - i2c->base.owner = THIS_MODULE; - i2c->base.dev.parent = &adev->pdev->dev; - i2c->base.algo = &amdgpu_dm_i2c_algo; - if (oem) - snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c OEM bus"); - else - snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c hw bus %d", - ddc_service->link->link_index); - i2c_set_adapdata(&i2c->base, i2c); - i2c->ddc_service = ddc_service; - i2c->oem = oem; - - return i2c; -} - -int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector) -{ - struct cec_connector_info conn_info; - struct drm_device *ddev = aconnector->base.dev; - struct device *hdmi_dev = ddev->dev; - - if (amdgpu_dc_debug_mask & DC_DISABLE_HDMI_CEC) { - drm_info(ddev, "HDMI-CEC feature masked\n"); - return -EINVAL; - } - - cec_fill_conn_info_from_drm(&conn_info, &aconnector->base); - aconnector->notifier = - cec_notifier_conn_register(hdmi_dev, NULL, &conn_info); - if (!aconnector->notifier) { - drm_err(ddev, "Failed to create cec notifier\n"); - return -ENOMEM; - } - - return 0; -} - -/* - * Note: this function assumes that dc_link_detect() was called for the - * dc_link which will be represented by this aconnector. - */ -static int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector, - u32 link_index, - struct amdgpu_encoder *aencoder) -{ - int res = 0; - int connector_type; - struct dc *dc = dm->dc; - struct dc_link *link = dc_get_link_at_index(dc, link_index); - struct amdgpu_i2c_adapter *i2c; - - /* Not needed for writeback connector */ - link->priv = aconnector; - - - i2c = create_i2c(link->ddc, false); - if (!i2c) { - drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n"); - return -ENOMEM; - } - - aconnector->i2c = i2c; - res = devm_i2c_add_adapter(dm->adev->dev, &i2c->base); - - if (res) { - drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index); - goto out_free; - } - - connector_type = to_drm_connector_type(link->connector_signal, link->link_id.id); - - res = drm_connector_init_with_ddc( - dm->ddev, - &aconnector->base, - &amdgpu_dm_connector_funcs, - connector_type, - &i2c->base); - - if (res) { - drm_err(adev_to_drm(dm->adev), "connector_init failed\n"); - aconnector->connector_id = -1; - goto out_free; - } - - drm_connector_helper_add( - &aconnector->base, - &amdgpu_dm_connector_helper_funcs); - - amdgpu_dm_connector_init_helper( - dm, - aconnector, - connector_type, - link, - link_index); - - drm_connector_attach_encoder( - &aconnector->base, &aencoder->base); - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA || - connector_type == DRM_MODE_CONNECTOR_HDMIB) - amdgpu_dm_initialize_hdmi_connector(aconnector); - - if (dc_is_dp_signal(link->connector_signal)) - amdgpu_dm_initialize_dp_connector(dm, aconnector, link->link_index); - -out_free: - if (res) { - kfree(i2c); - aconnector->i2c = NULL; - } - return res; -} - -int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev) -{ - switch (adev->mode_info.num_crtc) { - case 1: - return 0x1; - case 2: - return 0x3; - case 3: - return 0x7; - case 4: - return 0xf; - case 5: - return 0x1f; - case 6: - default: - return 0x3f; - } -} - -static int amdgpu_dm_encoder_init(struct drm_device *dev, - struct amdgpu_encoder *aencoder, - uint32_t link_index) -{ - struct amdgpu_device *adev = drm_to_adev(dev); - - int res = drm_encoder_init(dev, - &aencoder->base, - &amdgpu_dm_encoder_funcs, - DRM_MODE_ENCODER_TMDS, - NULL); - - aencoder->base.possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev); - - if (!res) - aencoder->encoder_id = link_index; - else - aencoder->encoder_id = -1; - - drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs); + return vcpi; - return res; + dm_conn_state->pbn = pbn; + dm_conn_state->vcpi_slots = vcpi; + } + return 0; } static void manage_dm_interrupts(struct amdgpu_device *adev, @@ -8176,6 +5080,72 @@ static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state) return 0; } +static void set_multisync_trigger_params( + struct dc_stream_state *stream) +{ + struct dc_stream_state *master = NULL; + + if (stream->triggered_crtc_reset.enabled) { + master = stream->triggered_crtc_reset.event_source; + stream->triggered_crtc_reset.event = + master->timing.flags.VSYNC_POSITIVE_POLARITY ? + CRTC_EVENT_VSYNC_RISING : CRTC_EVENT_VSYNC_FALLING; + stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL; + } +} + +static void set_master_stream(struct dc_stream_state *stream_set[], + int stream_count) +{ + int j, highest_rfr = 0, master_stream = 0; + + for (j = 0; j < stream_count; j++) { + if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) { + int refresh_rate = 0; + + refresh_rate = (stream_set[j]->timing.pix_clk_100hz*100)/ + (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total); + if (refresh_rate > highest_rfr) { + highest_rfr = refresh_rate; + master_stream = j; + } + } + } + for (j = 0; j < stream_count; j++) { + if (stream_set[j]) + stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream]; + } +} + +static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) +{ + int i = 0; + struct dc_stream_state *stream; + + if (context->stream_count < 2) + return; + for (i = 0; i < context->stream_count ; i++) { + if (!context->streams[i]) + continue; + /* + * TODO: add a function to read AMD VSDB bits and set + * crtc_sync_master.multi_sync_enabled flag + * For now it's set to false + */ + } + + set_master_stream(context->streams, context->stream_count); + + for (i = 0; i < context->stream_count ; i++) { + stream = context->streams[i]; + + if (!stream) + continue; + + set_multisync_trigger_params(stream); + } +} + /** * amdgpu_dm_atomic_commit_tail() - AMDgpu DM's commit tail implementation. * @state: The atomic state to commit @@ -8244,7 +5214,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) if ((new_con_state->hdmi.broadcast_rgb != old_con_state->hdmi.broadcast_rgb) && (dm_old_crtc_state->stream->output_color_space != - get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state))) + amdgpu_dm_get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state))) output_color_space_changed = true; abm_changed = dm_new_crtc_state->abm_level != @@ -8258,7 +5228,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) stream_update.stream = dm_new_crtc_state->stream; if (scaling_changed) { - update_stream_scaling_settings(dev, &dm_new_con_state->base.crtc->mode, + amdgpu_dm_update_stream_scaling_settings(dev, &dm_new_con_state->base.crtc->mode, dm_new_con_state, dm_new_crtc_state->stream); stream_update.src = dm_new_crtc_state->stream->src; @@ -8267,7 +5237,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) if (output_color_space_changed) { dm_new_crtc_state->stream->output_color_space - = get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state); + = amdgpu_dm_get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state); stream_update.output_color_space = &dm_new_crtc_state->stream->output_color_space; } @@ -8279,7 +5249,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) } if (hdr_changed) { - fill_hdr_info_packet(new_con_state, &hdr_packet); + amdgpu_dm_fill_hdr_info_packet(new_con_state, &hdr_packet); stream_update.hdr_static_metadata = &hdr_packet; } @@ -8487,104 +5457,6 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) trace_amdgpu_dm_atomic_commit_tail_finish(state); } -static int dm_force_atomic_commit(struct drm_connector *connector) -{ - int ret = 0; - struct drm_device *ddev = connector->dev; - struct drm_atomic_commit *state = drm_atomic_commit_alloc(ddev); - struct amdgpu_crtc *disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); - struct drm_plane *plane = disconnected_acrtc->base.primary; - struct drm_connector_state *conn_state; - struct drm_crtc_state *crtc_state; - struct drm_plane_state *plane_state; - - if (!state) - return -ENOMEM; - - state->acquire_ctx = ddev->mode_config.acquire_ctx; - - /* Construct an atomic state to restore previous display setting */ - - /* - * Attach connectors to drm_atomic_commit - */ - conn_state = drm_atomic_get_connector_state(state, connector); - - /* Check for error in getting connector state */ - if (IS_ERR(conn_state)) { - ret = PTR_ERR(conn_state); - goto out; - } - - /* Attach crtc to drm_atomic_commit*/ - crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base); - - /* Check for error in getting crtc state */ - if (IS_ERR(crtc_state)) { - ret = PTR_ERR(crtc_state); - goto out; - } - - /* force a restore */ - crtc_state->mode_changed = true; - - /* Attach plane to drm_atomic_commit */ - plane_state = drm_atomic_get_plane_state(state, plane); - - /* Check for error in getting plane state */ - if (IS_ERR(plane_state)) { - ret = PTR_ERR(plane_state); - goto out; - } - - /* Call commit internally with the state we just constructed */ - ret = drm_atomic_commit(state); - -out: - drm_atomic_commit_put(state); - if (ret) - drm_err(ddev, "Restoring old state failed with %i\n", ret); - - return ret; -} - -/* - * This function handles all cases when set mode does not come upon hotplug. - * This includes when a display is unplugged then plugged back into the - * same port and when running without usermode desktop manager supprot - */ -void dm_restore_drm_connector_state(struct drm_device *dev, - struct drm_connector *connector) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_crtc *disconnected_acrtc; - struct dm_crtc_state *acrtc_state; - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - return; - - aconnector = to_amdgpu_dm_connector(connector); - - if (!aconnector->dc_sink || !connector->state || !connector->encoder) - return; - - disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); - if (!disconnected_acrtc) - return; - - acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state); - if (!acrtc_state->stream) - return; - - /* - * If the previous sink is not released and different from the current, - * we deduce we are in a state where we can not rely on usermode call - * to turn on the display, so we do it here - */ - if (acrtc_state->stream->sink != aconnector->dc_sink) - dm_force_atomic_commit(&aconnector->base); -} - /* * Grabs all modesetting locks to serialize against any blocking commits, * Waits for completion of all non blocking commits. @@ -8786,7 +5658,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) goto skip_modeset; - new_stream = create_validate_stream_for_sink(connector, + new_stream = amdgpu_dm_create_validate_stream_for_sink(connector, &new_crtc_state->mode, dm_new_conn_state, dm_old_crtc_state->stream); @@ -8814,7 +5686,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, dm_new_crtc_state->abm_level = dm_new_conn_state->abm_level; - ret = fill_hdr_info_packet(drm_new_conn_state, + ret = amdgpu_dm_fill_hdr_info_packet(drm_new_conn_state, &new_stream->hdr_static_metadata); if (ret) goto fail; @@ -8883,11 +5755,11 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, goto skip_modeset; } else if (amdgpu_freesync_vid_mode && aconnector && - is_freesync_video_mode(&new_crtc_state->mode, + amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode, aconnector)) { struct drm_display_mode *high_mode; - high_mode = get_highest_refresh_rate_mode(aconnector, false); + 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); } @@ -8979,7 +5851,7 @@ skip_modeset: /* Scaling or underscan settings */ if (is_scaling_state_different(dm_old_conn_state, dm_new_conn_state) || drm_atomic_crtc_needs_modeset(new_crtc_state)) - update_stream_scaling_settings(adev_to_drm(adev), + amdgpu_dm_update_stream_scaling_settings(adev_to_drm(adev), &new_crtc_state->mode, dm_new_conn_state, dm_new_crtc_state->stream); /* ABM settings */ @@ -10358,373 +7230,6 @@ fail: return ret; } -static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, - unsigned int offset, - unsigned int total_length, - u8 *data, - unsigned int length, - struct amdgpu_hdmi_vsdb_info *vsdb) -{ - bool res; - union dmub_rb_cmd cmd; - struct dmub_cmd_send_edid_cea *input; - struct dmub_cmd_edid_cea_output *output; - - if (length > DMUB_EDID_CEA_DATA_CHUNK_BYTES) - return false; - - memset(&cmd, 0, sizeof(cmd)); - - input = &cmd.edid_cea.data.input; - - cmd.edid_cea.header.type = DMUB_CMD__EDID_CEA; - cmd.edid_cea.header.sub_type = 0; - cmd.edid_cea.header.payload_bytes = - sizeof(cmd.edid_cea) - sizeof(cmd.edid_cea.header); - input->offset = offset; - input->length = length; - input->cea_total_length = total_length; - memcpy(input->payload, data, length); - - res = dc_wake_and_execute_dmub_cmd(dm->dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY); - if (!res) { - drm_err(adev_to_drm(dm->adev), "EDID CEA parser failed\n"); - return false; - } - - output = &cmd.edid_cea.data.output; - - if (output->type == DMUB_CMD__EDID_CEA_ACK) { - if (!output->ack.success) { - drm_err(adev_to_drm(dm->adev), "EDID CEA ack failed at offset %d\n", - output->ack.offset); - } - } else if (output->type == DMUB_CMD__EDID_CEA_AMD_VSDB) { - if (!output->amd_vsdb.vsdb_found) - return false; - - vsdb->freesync_supported = output->amd_vsdb.freesync_supported; - vsdb->amd_vsdb_version = output->amd_vsdb.amd_vsdb_version; - vsdb->min_refresh_rate_hz = output->amd_vsdb.min_frame_rate; - vsdb->max_refresh_rate_hz = output->amd_vsdb.max_frame_rate; - vsdb->freesync_mccs_vcp_code = output->amd_vsdb.freesync_mccs_vcp_code; - } else { - drm_warn(adev_to_drm(dm->adev), "Unknown EDID CEA parser results\n"); - return false; - } - - return true; -} - -static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, - u8 *edid_ext, int len, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - int i; - - /* send extension block to DMCU for parsing */ - for (i = 0; i < len; i += 8) { - bool res; - int offset; - - /* send 8 bytes a time */ - if (!dc_edid_parser_send_cea(dm->dc, i, len, &edid_ext[i], 8)) - return false; - - if (i+8 == len) { - /* EDID block sent completed, expect result */ - int version, min_rate, max_rate; - - res = dc_edid_parser_recv_amd_vsdb(dm->dc, &version, &min_rate, &max_rate); - if (res) { - /* amd vsdb found */ - vsdb_info->freesync_supported = 1; - vsdb_info->amd_vsdb_version = version; - vsdb_info->min_refresh_rate_hz = min_rate; - vsdb_info->max_refresh_rate_hz = max_rate; - /* Not enabled on DMCU*/ - vsdb_info->freesync_mccs_vcp_code = 0; - return true; - } - /* not amd vsdb */ - return false; - } - - /* check for ack*/ - res = dc_edid_parser_recv_cea_ack(dm->dc, &offset); - if (!res) - return false; - } - - return false; -} - -static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, - u8 *edid_ext, int len, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - int i; - - /* send extension block to DMCU for parsing */ - for (i = 0; i < len; i += 8) { - /* send 8 bytes a time */ - if (!dm_edid_parser_send_cea(dm, i, len, &edid_ext[i], 8, vsdb_info)) - return false; - } - - return vsdb_info->freesync_supported; -} - -static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, - u8 *edid_ext, int len, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); - bool ret; - - mutex_lock(&adev->dm.dc_lock); - if (adev->dm.dmub_srv) - ret = parse_edid_cea_dmub(&adev->dm, edid_ext, len, vsdb_info); - else - ret = parse_edid_cea_dmcu(&adev->dm, edid_ext, len, vsdb_info); - mutex_unlock(&adev->dm.dc_lock); - return ret; -} - -static void parse_edid_displayid_vrr(struct drm_connector *connector, - const struct edid *edid) -{ - u8 *edid_ext = NULL; - int i; - int j = 0; - u16 min_vfreq; - u16 max_vfreq; - - if (!edid || !edid->extensions) - return; - - /* Find DisplayID extension */ - for (i = 0; i < edid->extensions; i++) { - edid_ext = (void *)(edid + (i + 1)); - if (edid_ext[0] == DISPLAYID_EXT) - break; - } - - if (i == edid->extensions) - return; - - while (j < EDID_LENGTH) { - /* Get dynamic video timing range from DisplayID if available */ - if (EDID_LENGTH - j > 13 && edid_ext[j] == 0x25 && - (edid_ext[j+1] & 0xFE) == 0 && (edid_ext[j+2] == 9)) { - min_vfreq = edid_ext[j+9]; - if (edid_ext[j+1] & 7) - max_vfreq = edid_ext[j+10] + ((edid_ext[j+11] & 3) << 8); - else - max_vfreq = edid_ext[j+10]; - - if (max_vfreq && min_vfreq) { - connector->display_info.monitor_range.max_vfreq = max_vfreq; - connector->display_info.monitor_range.min_vfreq = min_vfreq; - - return; - } - } - j++; - } -} - -static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - struct drm_connector *connector = &aconnector->base; - - vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode; - vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version; - - return connector->display_info.amd_vsdb.version != 0; -} - -static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, - const struct edid *edid, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - u8 *edid_ext = NULL; - int i; - bool valid_vsdb_found = false; - - /*----- drm_find_cea_extension() -----*/ - /* No EDID or EDID extensions */ - if (edid == NULL || edid->extensions == 0) - return -ENODEV; - - /* Find CEA extension */ - for (i = 0; i < edid->extensions; i++) { - edid_ext = (uint8_t *)edid + EDID_LENGTH * (i + 1); - if (edid_ext[0] == CEA_EXT) - break; - } - - if (i == edid->extensions) - return -ENODEV; - - /*----- cea_db_offsets() -----*/ - if (edid_ext[0] != CEA_EXT) - return -ENODEV; - - valid_vsdb_found = parse_edid_cea(aconnector, edid_ext, EDID_LENGTH, vsdb_info); - - return valid_vsdb_found ? i : -ENODEV; -} - -/** - * amdgpu_dm_update_freesync_caps - Update Freesync capabilities - * - * @connector: Connector to query. - * @drm_edid: DRM EDID from monitor - * @do_mccs: Controls whether MCCS (Monitor Control Command Set) over - * DDC (Display Data Channel) transactions are performed. When true, - * the driver queries the monitor to get or update additional FreeSync - * capability information. When false, these transactions are skipped. - * - * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep - * track of some of the display information in the internal data struct used by - * amdgpu_dm. This function checks which type of connector we need to set the - * FreeSync parameters. - */ -void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, - const struct drm_edid *drm_edid, bool do_mccs) -{ - int i = 0; - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - struct dm_connector_state *dm_con_state = NULL; - struct dc_sink *sink; - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; - const struct edid *edid; - bool freesync_capable = false; - enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; - - if (!connector->state) { - drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__); - goto update; - } - - sink = amdgpu_dm_connector->dc_sink ? - amdgpu_dm_connector->dc_sink : - amdgpu_dm_connector->dc_em_sink; - - drm_edid_connector_update(connector, drm_edid); - - if (!drm_edid || !sink) { - dm_con_state = to_dm_connector_state(connector->state); - - amdgpu_dm_connector->min_vfreq = 0; - amdgpu_dm_connector->max_vfreq = 0; - freesync_capable = false; - - goto update; - } - - dm_con_state = to_dm_connector_state(connector->state); - - if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) - goto update; - - edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - - /* Some eDP panels only have the refresh rate range info in DisplayID */ - if ((connector->display_info.monitor_range.min_vfreq == 0 || - connector->display_info.monitor_range.max_vfreq == 0)) - parse_edid_displayid_vrr(connector, edid); - - if (edid && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || - sink->sink_signal == SIGNAL_TYPE_EDP)) { - if (amdgpu_dm_connector->dc_link && - amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param) { - amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq; - amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - } - - get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); - - if (vsdb_info.replay_mode) { - amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_info.replay_mode; - amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_info.amd_vsdb_version; - amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; - } - - } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0) { - amdgpu_dm_connector->vsdb_info = vsdb_info; - sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; - - if (vsdb_info.freesync_supported) { - amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; - amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - - connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; - connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; - } - } - } - - if (amdgpu_dm_connector->dc_link) - as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link); - - if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0) { - amdgpu_dm_connector->vsdb_info = vsdb_info; - sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; - - if (vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) { - amdgpu_dm_connector->pack_sdp_v1_3 = true; - amdgpu_dm_connector->as_type = as_type; - - amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; - amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - - connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; - connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; - } - } - } - - /* Handle MCCS */ - if (do_mccs) { - dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - - if (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported) - freesync_capable = false; - - if (sink->mccs_caps.freesync_supported && freesync_capable) - dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - } - -update: - if (dm_con_state) - dm_con_state->freesync_capable = freesync_capable; - - if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && - amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { - amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; - amdgpu_dm_connector->dc_link->replay_settings.replay_feature_enabled = false; - } - - if (connector->vrr_capable_property) - drm_connector_set_vrr_capable_property(connector, - freesync_capable); -} - void amdgpu_dm_trigger_timing_sync(struct drm_device *dev) { struct amdgpu_device *adev = drm_to_adev(dev); @@ -10744,12 +7249,6 @@ void amdgpu_dm_trigger_timing_sync(struct drm_device *dev) mutex_unlock(&adev->dm.dc_lock); } -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) - dc_exit_ips_for_hw_access(dc); -} - void dm_write_reg_func(const struct dc_context *ctx, uint32_t address, u32 value, const char *func_name) { 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 505164364e61..c0144d14b793 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1061,35 +1061,7 @@ struct dm_connector_state { #define to_dm_connector_state(x)\ container_of((x), struct dm_connector_state, base) -void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector); -struct drm_connector_state * -amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector); -int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, - struct drm_connector_state *state, - struct drm_property *property, - uint64_t val); - -int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, - const struct drm_connector_state *state, - struct drm_property *property, - uint64_t *val); - -int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev); - -void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector, - int connector_type, - struct dc_link *link, - int link_index); - -enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, - const struct drm_display_mode *mode); - -void dm_restore_drm_connector_state(struct drm_device *dev, - struct drm_connector *connector); - -void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, - const struct drm_edid *drm_edid, bool do_mccs); +#include "amdgpu_dm_connector.h" void amdgpu_dm_trigger_timing_sync(struct drm_device *dev); @@ -1113,14 +1085,9 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state); -void amdgpu_dm_update_connector_after_detect( - struct amdgpu_dm_connector *aconnector); - void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *info, struct dc_edid_caps *edid_caps); -extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs; - int amdgpu_dm_process_dmub_aux_transfer_sync(struct dc_context *ctx, unsigned int link_index, struct aux_payload *payload, enum aux_return_code_type *operation_result); @@ -1135,20 +1102,9 @@ bool amdgpu_dm_execute_fused_io( int amdgpu_dm_process_dmub_set_config_sync(struct dc_context *ctx, unsigned int link_index, struct set_config_cmd_payload *payload, enum set_config_status *operation_result); -struct dc_stream_state * - create_validate_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 dm_atomic_get_state(struct drm_atomic_commit *state, struct dm_atomic_state **dm_state); -struct drm_connector * -amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, - struct drm_crtc *crtc); - -int convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth); struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev); void *dm_allocate_gpu_mem(struct amdgpu_device *adev, @@ -1161,10 +1117,6 @@ void dm_free_gpu_mem(struct amdgpu_device *adev, bool amdgpu_dm_is_headless(struct amdgpu_device *adev); -void hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector); -void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector); -int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); - void retrieve_dmi_info(struct amdgpu_display_manager *dm); void amdgpu_dm_emulated_link_detect(struct dc_link *link); 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 new file mode 100644 index 000000000000..f239ce767bff --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -0,0 +1,3575 @@ +// 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 "dm_services_types.h" +#include "dc.h" +#include "dc/dc_dmub_srv.h" +#include "dc/dc_edid_parser.h" +#include "dc/dc_stat.h" +#include "dc/dc_state.h" +#include "dc/dc_stream.h" +#include "dc/inc/core_types.h" +#include "link_enc_cfg.h" +#include "link/protocols/link_dpcd.h" +#include "link_service_types.h" +#include "link/protocols/link_dp_capability.h" +#include "link/protocols/link_ddc.h" + +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_connector.h" +#include "amdgpu_dm_plane.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_wb.h" +#include "amdgpu_dm_mst_types.h" +#if defined(CONFIG_DEBUG_FS) +#include "amdgpu_dm_debugfs.h" +#endif +#include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_audio.h" +#include "amdgpu_dm_irq.h" +#include "amdgpu_dm_psr.h" +#include "dm_helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "modules/inc/mod_freesync.h" +#include "modules/inc/mod_power.h" + +#include "amdgpu_dm_trace.h" + +/* Encoder functions */ + +static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +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 int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_atomic_commit *state = crtc_state->state; + struct drm_connector *connector = conn_state->connector; + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + struct dm_connector_state *dm_new_connector_state = to_dm_connector_state(conn_state); + const struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + struct drm_dp_mst_topology_mgr *mst_mgr; + struct drm_dp_mst_port *mst_port; + struct drm_dp_mst_topology_state *mst_state; + enum dc_color_depth color_depth; + int clock, bpp = 0; + bool is_y420 = false; + + if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || + (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + enum drm_mode_status result; + + result = drm_crtc_helper_mode_valid_fixed(encoder->crtc, adjusted_mode, native_mode); + if (result != MODE_OK && dm_new_connector_state->scaling == RMX_OFF) { + drm_dbg_driver(encoder->dev, + "mode %dx%d@%dHz is not native, enabling scaling\n", + adjusted_mode->hdisplay, adjusted_mode->vdisplay, + drm_mode_vrefresh(adjusted_mode)); + dm_new_connector_state->scaling = RMX_ASPECT; + } + return 0; + } + + if (!aconnector->mst_output_port) + return 0; + + mst_port = aconnector->mst_output_port; + mst_mgr = &aconnector->mst_root->mst_mgr; + + if (!crtc_state->connectors_changed && !crtc_state->mode_changed) + return 0; + + mst_state = drm_atomic_get_mst_topology_state(state, mst_mgr); + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + mst_state->pbn_div.full = dm_mst_get_pbn_divider(aconnector->mst_root->dc_link); + + if (!state->duplicated) { + int max_bpc = conn_state->max_requested_bpc; + + is_y420 = drm_mode_is_420_also(&connector->display_info, adjusted_mode) && + aconnector->force_yuv420_output; + color_depth = amdgpu_dm_convert_color_depth_from_display_info(connector, + is_y420, + max_bpc); + bpp = amdgpu_dm_convert_dc_color_depth_into_bpc(color_depth) * 3; + clock = adjusted_mode->clock; + dm_new_connector_state->pbn = drm_dp_calc_pbn_mode(clock, bpp << 4); + } + + dm_new_connector_state->vcpi_slots = + drm_dp_atomic_find_time_slots(state, mst_mgr, mst_port, + dm_new_connector_state->pbn); + if (dm_new_connector_state->vcpi_slots < 0) { + drm_dbg_atomic(connector->dev, "failed finding vcpi slots: %d\n", (int)dm_new_connector_state->vcpi_slots); + return dm_new_connector_state->vcpi_slots; + } + return 0; +} + +const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { + .disable = dm_encoder_helper_disable, + .atomic_check = dm_encoder_helper_atomic_check +}; + +int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev) +{ + switch (adev->mode_info.num_crtc) { + case 1: + return 0x1; + case 2: + return 0x3; + case 3: + return 0x7; + case 4: + return 0xf; + case 5: + return 0x1f; + case 6: + default: + return 0x3f; + } +} + +int amdgpu_dm_encoder_init(struct drm_device *dev, + struct amdgpu_encoder *aencoder, + uint32_t link_index) +{ + struct amdgpu_device *adev = drm_to_adev(dev); + + int res = drm_encoder_init(dev, + &aencoder->base, + &amdgpu_dm_encoder_funcs, + DRM_MODE_ENCODER_TMDS, + NULL); + + aencoder->base.possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev); + + if (!res) + aencoder->encoder_id = link_index; + else + aencoder->encoder_id = -1; + + drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs); + + return res; +} + +static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) +{ + switch (link->dpcd_caps.dongle_type) { + case DISPLAY_DONGLE_NONE: + return DRM_MODE_SUBCONNECTOR_Native; + case DISPLAY_DONGLE_DP_VGA_CONVERTER: + return DRM_MODE_SUBCONNECTOR_VGA; + case DISPLAY_DONGLE_DP_DVI_CONVERTER: + case DISPLAY_DONGLE_DP_DVI_DONGLE: + return DRM_MODE_SUBCONNECTOR_DVID; + case DISPLAY_DONGLE_DP_HDMI_CONVERTER: + case DISPLAY_DONGLE_DP_HDMI_DONGLE: + return DRM_MODE_SUBCONNECTOR_HDMIA; + case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE: + default: + return DRM_MODE_SUBCONNECTOR_Unknown; + } +} + +static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) +{ + struct dc_link *link = aconnector->dc_link; + struct drm_connector *connector = &aconnector->base; + enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; + + if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) + return; + + if (aconnector->dc_sink) + subconnector = get_subconnector_type(link); + + drm_object_property_set_value(&connector->base, + connector->dev->mode_config.dp_subconnector_property, + subconnector); +} + +static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); + +static void amdgpu_dm_fbc_init(struct drm_connector *connector) +{ + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct dm_compressor_info *compressor = &adev->dm.compressor; + struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(connector); + struct drm_display_mode *mode; + unsigned long max_size = 0; + + if (adev->dm.dc->fbc_compressor == NULL) + return; + + if (aconn->dc_link->connector_signal != SIGNAL_TYPE_EDP) + return; + + if (compressor->bo_ptr) + return; + + + list_for_each_entry(mode, &connector->modes, head) { + if (max_size < (unsigned long) mode->htotal * mode->vtotal) + max_size = (unsigned long) mode->htotal * mode->vtotal; + } + + if (max_size) { + int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr, + &compressor->gpu_addr, &compressor->cpu_addr); + + if (r) + drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n"); + else { + adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr; + drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4); + } + + } + +} + + +int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_connector *connector; + struct drm_connector_list_iter iter; + int ret = 0; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (aconnector->dc_link->type == dc_connection_mst_branch && + aconnector->mst_mgr.aux) { + drm_dbg_kms(dev, "DM_MST: starting TM on aconnector: %p [id: %d]\n", + aconnector, + aconnector->base.base.id); + + ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true); + if (ret < 0) { + drm_err(dev, "DM_MST: Failed to start MST\n"); + aconnector->dc_link->type = + dc_connection_single; + ret = dm_helpers_dp_mst_stop_top_mgr(aconnector->dc_link->ctx, + aconnector->dc_link); + break; + } + } + } + drm_connector_list_iter_end(&iter); + + return ret; +} + +static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) +{ + struct cec_notifier *n = aconnector->notifier; + + if (!n) + return; + + cec_notifier_phys_addr_invalidate(n); +} + +void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct cec_notifier *n = aconnector->notifier; + + if (!n) + return; + + cec_notifier_set_phys_addr(n, + connector->display_info.source_physical_address); +} + +void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + + drm_connector_list_iter_begin(ddev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (suspend) + hdmi_cec_unset_edid(aconnector); + else + amdgpu_dm_hdmi_cec_set_edid(aconnector); + } + drm_connector_list_iter_end(&conn_iter); +} + + +struct drm_connector * +amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, + struct drm_crtc *crtc) +{ + u32 i; + struct drm_connector_state *new_con_state; + struct drm_connector *connector; + struct drm_crtc *crtc_from_state; + + for_each_new_connector_in_state(state, connector, new_con_state, i) { + crtc_from_state = new_con_state->crtc; + + if (crtc_from_state == crtc) + return connector; + } + + return NULL; +} + +static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct drm_display_info *display_info = &connector->display_info; + struct dc_link *link = aconnector->dc_link; + struct amdgpu_device *adev; + + adev = drm_to_adev(connector->dev); + + link->panel_type = PANEL_TYPE_NONE; + + switch (display_info->amd_vsdb.panel_type) { + case AMD_VSDB_PANEL_TYPE_OLED: + link->panel_type = PANEL_TYPE_OLED; + break; + case AMD_VSDB_PANEL_TYPE_MINILED: + link->panel_type = PANEL_TYPE_MINILED; + break; + } + + /* If VSDB didn't determine panel type, check DPCD ext caps */ + if (link->panel_type == PANEL_TYPE_NONE) { + if (link->dpcd_sink_ext_caps.bits.miniled == 1) + link->panel_type = PANEL_TYPE_MINILED; + if (link->dpcd_sink_ext_caps.bits.oled == 1) + link->panel_type = PANEL_TYPE_OLED; + } + + /* + * TODO: get panel type from DID2 that has device technology field + * to specify if it's OLED or not. But we need to wait for DID2 + * support in DC and EDID parser to be able to use it here. + */ + + if (link->panel_type == PANEL_TYPE_NONE) { + struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; + u32 lum1_max = vsdb->luminance_range1.max_luminance; + u32 lum2_max = vsdb->luminance_range2.max_luminance; + + if (vsdb->version && link->local_sink && + link->local_sink->edid_caps.manufacturer_id == + DDC_MANUFACTURERNAME_SAMSUNG && + lum1_max >= ((lum2_max * 3) / 2)) + link->panel_type = PANEL_TYPE_MINILED; + } + + if (link->panel_type == PANEL_TYPE_OLED) + drm_object_property_set_value(&connector->base, + adev_to_drm(adev)->mode_config.panel_type_property, + DRM_MODE_PANEL_TYPE_OLED); + else + drm_object_property_set_value(&connector->base, + adev_to_drm(adev)->mode_config.panel_type_property, + DRM_MODE_PANEL_TYPE_UNKNOWN); + + drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); +} + +DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) + +void amdgpu_dm_update_connector_after_detect( + struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct dc_sink *sink __free(sink_release) = NULL; + struct drm_device *dev = connector->dev; + + /* MST handled by drm_mst framework */ + if (aconnector->mst_mgr.mst_state == true) + return; + + sink = aconnector->dc_link->local_sink; + if (sink) + dc_sink_retain(sink); + + /* + * Edid mgmt connector gets first update only in mode_valid hook and then + * the connector sink is set to either fake or physical sink depends on link status. + * Skip if already done during boot. + */ + if (aconnector->base.force != DRM_FORCE_UNSPECIFIED + && aconnector->dc_em_sink) { + + /* + * For S3 resume with headless use eml_sink to fake stream + * because on resume connector->sink is set to NULL + */ + guard(mutex)(&dev->mode_config.mutex); + + if (sink) { + if (aconnector->dc_sink) { + amdgpu_dm_update_freesync_caps(connector, NULL, true); + /* + * retain and release below are used to + * bump up refcount for sink because the link doesn't point + * to it anymore after disconnect, so on next crtc to connector + * reshuffle by UMD we will get into unwanted dc_sink release + */ + dc_sink_release(aconnector->dc_sink); + } + aconnector->dc_sink = sink; + dc_sink_retain(aconnector->dc_sink); + amdgpu_dm_update_freesync_caps(connector, + aconnector->drm_edid, true); + } else { + amdgpu_dm_update_freesync_caps(connector, NULL, true); + if (!aconnector->dc_sink) { + aconnector->dc_sink = aconnector->dc_em_sink; + dc_sink_retain(aconnector->dc_sink); + } + } + + return; + } + + /* + * TODO: temporary guard to look for proper fix + * if this sink is MST sink, we should not do anything + */ + if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + return; + + if (aconnector->dc_sink == sink) { + /* + * We got a DP short pulse (Link Loss, DP CTS, etc...). + * Do nothing!! + */ + drm_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n", + aconnector->connector_id); + return; + } + + drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n", + aconnector->connector_id, aconnector->dc_sink, sink); + + /* When polling, DRM has already locked the mutex for us. */ + if (!drm_kms_helper_is_poll_worker()) + mutex_lock(&dev->mode_config.mutex); + + /* + * 1. Update status of the drm connector + * 2. Send an event and let userspace tell us what to do + */ + if (sink) { + /* + * TODO: check if we still need the S3 mode update workaround. + * If yes, put it here. + */ + if (aconnector->dc_sink) { + amdgpu_dm_update_freesync_caps(connector, NULL, true); + dc_sink_release(aconnector->dc_sink); + } + + aconnector->dc_sink = sink; + dc_sink_retain(aconnector->dc_sink); + drm_edid_free(aconnector->drm_edid); + aconnector->drm_edid = NULL; + if (sink->dc_edid.length == 0) { + hdmi_cec_unset_edid(aconnector); + if (aconnector->dc_link->aux_mode) + drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); + } else { + const struct edid *edid = (const struct edid *)sink->dc_edid.raw_edid; + + aconnector->drm_edid = drm_edid_alloc(edid, sink->dc_edid.length); + drm_edid_connector_update(connector, aconnector->drm_edid); + + amdgpu_dm_hdmi_cec_set_edid(aconnector); + if (aconnector->dc_link->aux_mode) + drm_dp_cec_attach(&aconnector->dm_dp_aux.aux, + connector->display_info.source_physical_address); + } + + if (!aconnector->timing_requested) { + aconnector->timing_requested = + kzalloc_obj(struct dc_crtc_timing); + if (!aconnector->timing_requested) + drm_err(dev, + "failed to create aconnector->requested_timing\n"); + } + + amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); + amdgpu_dm_update_connector_ext_caps(aconnector); + dm_set_panel_type(aconnector); + } else { + hdmi_cec_unset_edid(aconnector); + drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); + amdgpu_dm_update_freesync_caps(connector, NULL, true); + aconnector->num_modes = 0; + dc_sink_release(aconnector->dc_sink); + aconnector->dc_sink = NULL; + drm_edid_free(aconnector->drm_edid); + aconnector->drm_edid = NULL; + kfree(aconnector->timing_requested); + aconnector->timing_requested = NULL; + /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ + if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) + connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + } + + update_subconnector_property(aconnector); + + /* When polling, the mutex will be unlocked for us by DRM. */ + if (!drm_kms_helper_is_poll_worker()) + mutex_unlock(&dev->mode_config.mutex); +} + +enum dc_color_depth +amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector, + bool is_y420, int requested_bpc) +{ + u8 bpc; + + if (is_y420) { + bpc = 8; + + /* Cap display bpc based on HDMI 2.0 HF-VSDB */ + if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) + bpc = 16; + else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) + bpc = 12; + else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + bpc = 10; + } else { + bpc = (uint8_t)connector->display_info.bpc; + /* Assume 8 bpc by default if no bpc is specified. */ + bpc = bpc ? bpc : 8; + } + + if (requested_bpc > 0) { + /* + * Cap display bpc based on the user requested value. + * + * The value for state->max_bpc may not correctly updated + * depending on when the connector gets added to the state + * or if this was called outside of atomic check, so it + * can't be used directly. + */ + bpc = min_t(u8, bpc, requested_bpc); + + /* Round down to the nearest even number. */ + bpc = bpc - (bpc & 1); + } + + switch (bpc) { + case 0: + /* + * Temporary Work around, DRM doesn't parse color depth for + * EDID revision before 1.4 + * TODO: Fix edid parsing + */ + return COLOR_DEPTH_888; + case 6: + return COLOR_DEPTH_666; + case 8: + return COLOR_DEPTH_888; + case 10: + return COLOR_DEPTH_101010; + case 12: + return COLOR_DEPTH_121212; + case 14: + return COLOR_DEPTH_141414; + case 16: + return COLOR_DEPTH_161616; + default: + return COLOR_DEPTH_UNDEFINED; + } +} + +static enum dc_aspect_ratio +get_aspect_ratio(const struct drm_display_mode *mode_in) +{ + /* 1-1 mapping, since both enums follow the HDMI spec. */ + return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio; +} + +enum dc_color_space +amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state) +{ + enum dc_color_space color_space = COLOR_SPACE_SRGB; + + switch (connector_state->colorspace) { + case DRM_MODE_COLORIMETRY_BT601_YCC: + if (dc_crtc_timing->flags.Y_ONLY) + color_space = COLOR_SPACE_YCBCR601_LIMITED; + else + color_space = COLOR_SPACE_YCBCR601; + break; + case DRM_MODE_COLORIMETRY_BT709_YCC: + if (dc_crtc_timing->flags.Y_ONLY) + color_space = COLOR_SPACE_YCBCR709_LIMITED; + else + color_space = COLOR_SPACE_YCBCR709; + break; + case DRM_MODE_COLORIMETRY_OPRGB: + color_space = COLOR_SPACE_ADOBERGB; + break; + case DRM_MODE_COLORIMETRY_BT2020_RGB: + case DRM_MODE_COLORIMETRY_BT2020_YCC: + if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) + color_space = COLOR_SPACE_2020_RGB_FULLRANGE; + else + color_space = COLOR_SPACE_2020_YCBCR_LIMITED; + break; + case DRM_MODE_COLORIMETRY_DEFAULT: /* ITU601 */ + default: + if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { + color_space = COLOR_SPACE_SRGB; + if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + color_space = COLOR_SPACE_SRGB_LIMITED; + /* + * 27030khz is the separation point between HDTV and SDTV + * according to HDMI spec, we use YCbCr709 and YCbCr601 + * respectively + */ + } else if (dc_crtc_timing->pix_clk_100hz > 270300) { + if (dc_crtc_timing->flags.Y_ONLY) + color_space = + COLOR_SPACE_YCBCR709_LIMITED; + else + color_space = COLOR_SPACE_YCBCR709; + } else { + if (dc_crtc_timing->flags.Y_ONLY) + color_space = + COLOR_SPACE_YCBCR601_LIMITED; + else + color_space = COLOR_SPACE_YCBCR601; + } + break; + } + + return color_space; +} + +static enum display_content_type +get_output_content_type(const struct drm_connector_state *connector_state) +{ + switch (connector_state->content_type) { + default: + case DRM_MODE_CONTENT_TYPE_NO_DATA: + return DISPLAY_CONTENT_TYPE_NO_DATA; + case DRM_MODE_CONTENT_TYPE_GRAPHICS: + return DISPLAY_CONTENT_TYPE_GRAPHICS; + case DRM_MODE_CONTENT_TYPE_PHOTO: + return DISPLAY_CONTENT_TYPE_PHOTO; + case DRM_MODE_CONTENT_TYPE_CINEMA: + return DISPLAY_CONTENT_TYPE_CINEMA; + case DRM_MODE_CONTENT_TYPE_GAME: + return DISPLAY_CONTENT_TYPE_GAME; + } +} + +static bool adjust_colour_depth_from_display_info( + struct dc_crtc_timing *timing_out, + const struct drm_display_info *info) +{ + enum dc_color_depth depth = timing_out->display_color_depth; + int normalized_clk; + + do { + normalized_clk = timing_out->pix_clk_100hz / 10; + /* YCbCr 4:2:0 requires additional adjustment of 1/2 */ + if (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420) + normalized_clk /= 2; + /* Adjusting pix clock following on HDMI spec based on colour depth */ + switch (depth) { + case COLOR_DEPTH_888: + break; + case COLOR_DEPTH_101010: + normalized_clk = (normalized_clk * 30) / 24; + break; + case COLOR_DEPTH_121212: + normalized_clk = (normalized_clk * 36) / 24; + break; + case COLOR_DEPTH_161616: + normalized_clk = (normalized_clk * 48) / 24; + break; + default: + /* The above depths are the only ones valid for HDMI. */ + return false; + } + if (normalized_clk <= info->max_tmds_clock) { + timing_out->display_color_depth = depth; + return true; + } + } while (--depth > COLOR_DEPTH_666); + return false; +} + +static void fill_stream_properties_from_drm_display_mode( + struct dc_stream_state *stream, + const struct drm_display_mode *mode_in, + const struct drm_connector *connector, + const struct drm_connector_state *connector_state, + const struct dc_stream_state *old_stream, + int requested_bpc) +{ + struct dc_crtc_timing *timing_out = &stream->timing; + const struct drm_display_info *info = &connector->display_info; + struct amdgpu_dm_connector *aconnector = NULL; + struct hdmi_vendor_infoframe hv_frame; + struct hdmi_avi_infoframe avi_frame; + ssize_t err; + + if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) + aconnector = to_amdgpu_dm_connector(connector); + + memset(&hv_frame, 0, sizeof(hv_frame)); + memset(&avi_frame, 0, sizeof(avi_frame)); + + timing_out->h_border_left = 0; + timing_out->h_border_right = 0; + timing_out->v_border_top = 0; + timing_out->v_border_bottom = 0; + /* TODO: un-hardcode */ + if (drm_mode_is_420_only(info, mode_in) + && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + && aconnector + && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if (drm_mode_is_420_also(info, mode_in) + && aconnector + && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 + || aconnector->force_yuv420_output)) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + && aconnector + && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422 + || aconnector->force_yuv422_output)) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) + && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + && aconnector + && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR444) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444; + else + timing_out->pixel_encoding = PIXEL_ENCODING_RGB; + + timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE; + timing_out->display_color_depth = amdgpu_dm_convert_color_depth_from_display_info( + connector, + (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420), + requested_bpc); + timing_out->scan_type = SCANNING_TYPE_NODATA; + timing_out->hdmi_vic = 0; + + if (old_stream) { + timing_out->vic = old_stream->timing.vic; + timing_out->flags.HSYNC_POSITIVE_POLARITY = old_stream->timing.flags.HSYNC_POSITIVE_POLARITY; + timing_out->flags.VSYNC_POSITIVE_POLARITY = old_stream->timing.flags.VSYNC_POSITIVE_POLARITY; + } else { + timing_out->vic = drm_match_cea_mode(mode_in); + if (mode_in->flags & DRM_MODE_FLAG_PHSYNC) + timing_out->flags.HSYNC_POSITIVE_POLARITY = 1; + if (mode_in->flags & DRM_MODE_FLAG_PVSYNC) + timing_out->flags.VSYNC_POSITIVE_POLARITY = 1; + } + + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) { + err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, + (struct drm_connector *)connector, + mode_in); + if (err < 0) + drm_warn_once(connector->dev, "Failed to setup avi infoframe on connector %s: %zd\n", + connector->name, err); + timing_out->vic = avi_frame.video_code; + err = drm_hdmi_vendor_infoframe_from_display_mode(&hv_frame, + (struct drm_connector *)connector, + mode_in); + if (err < 0) + drm_warn_once(connector->dev, "Failed to setup vendor infoframe on connector %s: %zd\n", + connector->name, err); + timing_out->hdmi_vic = hv_frame.vic; + } + + if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector)) { + timing_out->h_addressable = mode_in->hdisplay; + timing_out->h_total = mode_in->htotal; + timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start; + timing_out->h_front_porch = mode_in->hsync_start - mode_in->hdisplay; + timing_out->v_total = mode_in->vtotal; + timing_out->v_addressable = mode_in->vdisplay; + timing_out->v_front_porch = mode_in->vsync_start - mode_in->vdisplay; + timing_out->v_sync_width = mode_in->vsync_end - mode_in->vsync_start; + timing_out->pix_clk_100hz = mode_in->clock * 10; + } else { + timing_out->h_addressable = mode_in->crtc_hdisplay; + timing_out->h_total = mode_in->crtc_htotal; + timing_out->h_sync_width = mode_in->crtc_hsync_end - mode_in->crtc_hsync_start; + timing_out->h_front_porch = mode_in->crtc_hsync_start - mode_in->crtc_hdisplay; + timing_out->v_total = mode_in->crtc_vtotal; + timing_out->v_addressable = mode_in->crtc_vdisplay; + timing_out->v_front_porch = mode_in->crtc_vsync_start - mode_in->crtc_vdisplay; + timing_out->v_sync_width = mode_in->crtc_vsync_end - mode_in->crtc_vsync_start; + timing_out->pix_clk_100hz = mode_in->crtc_clock * 10; + } + + timing_out->aspect_ratio = get_aspect_ratio(mode_in); + + stream->out_transfer_func.type = TF_TYPE_PREDEFINED; + stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB; + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) { + if (!adjust_colour_depth_from_display_info(timing_out, info) && + drm_mode_is_420_also(info, mode_in) && + timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) { + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + adjust_colour_depth_from_display_info(timing_out, info); + } + } + + stream->output_color_space = amdgpu_dm_get_output_color_space(timing_out, connector_state); + stream->content_type = get_output_content_type(connector_state); +} + +static void +copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, + struct drm_display_mode *dst_mode) +{ + dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay; + dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay; + dst_mode->crtc_clock = src_mode->crtc_clock; + dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start; + dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end; + dst_mode->crtc_hsync_start = src_mode->crtc_hsync_start; + dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end; + dst_mode->crtc_htotal = src_mode->crtc_htotal; + dst_mode->crtc_hskew = src_mode->crtc_hskew; + dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start; + dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end; + dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start; + dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end; + dst_mode->crtc_vtotal = src_mode->crtc_vtotal; +} + +static void +decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, + const struct drm_display_mode *native_mode, + bool scale_enabled) +{ + if (scale_enabled || ( + native_mode->clock == drm_mode->clock && + native_mode->htotal == drm_mode->htotal && + native_mode->vtotal == drm_mode->vtotal)) { + if (native_mode->crtc_clock) + copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); + } else { + /* no scaling nor amdgpu inserted, no need to patch */ + } +} + +static struct dc_sink * +create_fake_sink(struct drm_device *dev, struct dc_link *link) +{ + struct dc_sink_init_data sink_init_data = { 0 }; + struct dc_sink *sink = NULL; + + sink_init_data.link = link; + sink_init_data.sink_signal = link->connector_signal; + + sink = dc_sink_create(&sink_init_data); + if (!sink) { + drm_err(dev, "Failed to create sink!\n"); + return NULL; + } + sink->sink_signal = SIGNAL_TYPE_VIRTUAL; + + return sink; +} + +/** + * DOC: FreeSync Video + * + * When a userspace application wants to play a video, the content follows a + * standard format definition that usually specifies the FPS for that format. + * The below list illustrates some video format and the expected FPS, + * respectively: + * + * - TV/NTSC (23.976 FPS) + * - Cinema (24 FPS) + * - TV/PAL (25 FPS) + * - TV/NTSC (29.97 FPS) + * - TV/NTSC (30 FPS) + * - Cinema HFR (48 FPS) + * - TV/PAL (50 FPS) + * - Commonly used (60 FPS) + * - Multiples of 24 (48,72,96 FPS) + * + * The list of standards video format is not huge and can be added to the + * connector modeset list beforehand. With that, userspace can leverage + * FreeSync to extends the front porch in order to attain the target refresh + * rate. Such a switch will happen seamlessly, without screen blanking or + * reprogramming of the output in any other way. If the userspace requests a + * modesetting change compatible with FreeSync modes that only differ in the + * refresh rate, DC will skip the full update and avoid blink during the + * transition. For example, the video player can change the modesetting from + * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without + * causing any display blink. This same concept can be applied to a mode + * setting change. + */ +struct drm_display_mode * +amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, + bool use_probed_modes) +{ + struct drm_display_mode *m, *m_pref = NULL; + u16 current_refresh, highest_refresh; + struct list_head *list_head = use_probed_modes ? + &aconnector->base.probed_modes : + &aconnector->base.modes; + + if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + return NULL; + + if (aconnector->freesync_vid_base.clock != 0) + return &aconnector->freesync_vid_base; + + /* Find the preferred mode */ + list_for_each_entry(m, list_head, head) { + if (m->type & DRM_MODE_TYPE_PREFERRED) { + m_pref = m; + break; + } + } + + if (!m_pref) { + /* Probably an EDID with no preferred mode. Fallback to first entry */ + m_pref = list_first_entry_or_null( + &aconnector->base.modes, struct drm_display_mode, head); + if (!m_pref) { + drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n"); + return NULL; + } + } + + highest_refresh = drm_mode_vrefresh(m_pref); + + /* + * Find the mode with highest refresh rate with same resolution. + * For some monitors, preferred mode is not the mode with highest + * supported refresh rate. + */ + list_for_each_entry(m, list_head, head) { + current_refresh = drm_mode_vrefresh(m); + + if (m->hdisplay == m_pref->hdisplay && + m->vdisplay == m_pref->vdisplay && + highest_refresh < current_refresh) { + highest_refresh = current_refresh; + m_pref = m; + } + } + + drm_mode_copy(&aconnector->freesync_vid_base, m_pref); + return m_pref; +} + +bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, + struct amdgpu_dm_connector *aconnector) +{ + struct drm_display_mode *high_mode; + int timing_diff; + + high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); + if (!high_mode || !mode) + return false; + + timing_diff = high_mode->vtotal - mode->vtotal; + + if (high_mode->clock == 0 || high_mode->clock != mode->clock || + high_mode->hdisplay != mode->hdisplay || + high_mode->vdisplay != mode->vdisplay || + high_mode->hsync_start != mode->hsync_start || + high_mode->hsync_end != mode->hsync_end || + high_mode->htotal != mode->htotal || + high_mode->hskew != mode->hskew || + high_mode->vscan != mode->vscan || + high_mode->vsync_start - mode->vsync_start != timing_diff || + high_mode->vsync_end - mode->vsync_end != timing_diff) + return false; + else + return true; +} + +#if defined(CONFIG_DRM_AMD_DC_FP) +static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, + struct dc_sink *sink, struct dc_stream_state *stream, + struct dsc_dec_dpcd_caps *dsc_caps) +{ + stream->timing.flags.DSC = 0; + dsc_caps->is_dsc_supported = false; + + if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || + sink->sink_signal == SIGNAL_TYPE_EDP)) { + if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) + dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, + dsc_caps); + else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { + if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT && + !aconnector->dsc_settings.dsc_force_disable_passthrough && + aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 && + sink->edid_caps.frl_dsc_support && + sink->edid_caps.max_frl_rate > 0 && + sink->edid_caps.frl_dsc_max_frl_rate > 0) + dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); + else + dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, + dsc_caps); + } + } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + if (sink->edid_caps.frl_dsc_support && + sink->edid_caps.max_frl_rate > 0 && + sink->edid_caps.frl_dsc_max_frl_rate > 0) + dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); + } +} + +static void apply_dsc_policy_for_edp(struct amdgpu_dm_connector *aconnector, + struct dc_sink *sink, struct dc_stream_state *stream, + struct dsc_dec_dpcd_caps *dsc_caps, + uint32_t max_dsc_target_bpp_limit_override) +{ + const struct dc_link_settings *verified_link_cap = NULL; + u32 link_bw_in_kbps; + u32 edp_min_bpp_x16, edp_max_bpp_x16; + struct dc *dc = sink->ctx->dc; + struct dc_dsc_bw_range bw_range = {0}; + struct dc_dsc_config dsc_cfg = {0}; + struct dc_dsc_config_options dsc_options = {0}; + + dc_dsc_get_default_config_option(dc, &dsc_options); + dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; + + verified_link_cap = dc_link_get_link_cap(stream->link); + link_bw_in_kbps = dc_link_bandwidth_kbps(stream->link, verified_link_cap); + edp_min_bpp_x16 = 8 * 16; + edp_max_bpp_x16 = 8 * 16; + + if (edp_max_bpp_x16 > dsc_caps->edp_max_bits_per_pixel) + edp_max_bpp_x16 = dsc_caps->edp_max_bits_per_pixel; + + if (edp_max_bpp_x16 < edp_min_bpp_x16) + edp_min_bpp_x16 = edp_max_bpp_x16; + + if (dc_dsc_compute_bandwidth_range(dc->res_pool->dscs[0], + dc->debug.dsc_min_slice_height_override, + edp_min_bpp_x16, edp_max_bpp_x16, + dsc_caps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &bw_range)) { + + if (bw_range.max_kbps < link_bw_in_kbps) { + if (dc_dsc_compute_config(dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + 0, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &dsc_cfg)) { + stream->timing.dsc_cfg = dsc_cfg; + stream->timing.flags.DSC = 1; + stream->timing.dsc_cfg.bits_per_pixel = edp_max_bpp_x16; + } + return; + } + } + + if (dc_dsc_compute_config(dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + link_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &dsc_cfg)) { + stream->timing.dsc_cfg = dsc_cfg; + stream->timing.flags.DSC = 1; + } +} + +static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, + struct dc_sink *sink, struct dc_stream_state *stream, + struct dsc_dec_dpcd_caps *dsc_caps) +{ + struct drm_connector *drm_connector = &aconnector->base; + u32 link_bandwidth_kbps; + struct dc *dc = sink->ctx->dc; + const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL; + u32 converter_bw_in_kbps; + u32 sink_bw_in_kbps; + u32 dsc_sink_bw_in_kbps; + u32 max_supported_bw_in_kbps, timing_bw_in_kbps; + u32 dsc_max_supported_bw_in_kbps; + u32 max_dsc_target_bpp_limit_override = + drm_connector->display_info.max_dsc_bpp; + struct dc_dsc_config_options dsc_options = {0}; + + dc_dsc_get_default_config_option(dc, &dsc_options); + dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; + + link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link, + dc_link_get_link_cap(aconnector->dc_link)); + + /* Set DSC policy according to dsc_clock_en */ + dc_dsc_policy_set_enable_dsc_when_not_needed( + aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE); + + if (sink->sink_signal == SIGNAL_TYPE_EDP && + !aconnector->dc_link->panel_config.dsc.disable_dsc_edp && + dc->caps.edp_dsc_support && aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE) { + + apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override); + + } else if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) { + if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) { + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + link_bandwidth_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + stream->timing.flags.DSC = 1; + drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from SST RX\n", + __func__, drm_connector->name); + } + } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { + timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link)); + converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps; + sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate); + dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); + + if (dsc_caps->is_frl) { + max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps); + max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps); + dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps); + } else { + max_supported_bw_in_kbps = link_bandwidth_kbps; + dsc_max_supported_bw_in_kbps = link_bandwidth_kbps; + } + + if (timing_bw_in_kbps > max_supported_bw_in_kbps && + max_supported_bw_in_kbps > 0 && + dsc_max_supported_bw_in_kbps > 0) + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + dsc_max_supported_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + stream->timing.flags.DSC = 1; + drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n", + __func__, drm_connector->name, + (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); + } + } + } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + struct dc_dsc_policy dsc_policy = {0}; + + frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); + if (frl_verified_link_cap->frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE && + aconnector->dc_link->frl_flags.force_frl_dsc) { + dc_dsc_policy_set_enable_dsc_when_not_needed(true); + dc_dsc_get_policy_for_timing(&stream->timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); + } + + timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL); + link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate); + dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); + + if ((timing_bw_in_kbps > link_bandwidth_kbps && dsc_sink_bw_in_kbps > 0) || + (dsc_policy.enable_dsc_when_not_needed || dsc_options.force_dsc_when_not_needed)) { + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + dsc_sink_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + stream->timing.flags.DSC = 1; + drm_dbg_driver(drm_connector->dev, "%s: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\n", + __func__, drm_connector->name); + } + } + } + + /* Overwrite the stream flag if DSC is enabled through debugfs */ + if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE) + stream->timing.flags.DSC = 1; + + if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h) + stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h; + + if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v) + stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v; + + if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel) + stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel; +} +#endif + +static 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) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_dm_connector *aconnector = NULL; + struct drm_display_mode *preferred_mode = NULL; + const struct drm_connector_state *con_state = &dm_state->base; + struct dc_stream_state *stream = NULL; + struct drm_display_mode mode; + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; + int mode_refresh; + int preferred_refresh = 0; + enum color_transfer_func tf = TRANSFER_FUNC_UNKNOWN; +#if defined(CONFIG_DRM_AMD_DC_FP) + struct dsc_dec_dpcd_caps dsc_caps = {0}; +#endif + struct dc_link *link = NULL; + struct dc_sink *sink = NULL; + + drm_mode_init(&mode, drm_mode); + memset(&saved_mode, 0, sizeof(saved_mode)); + + if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) { + aconnector = NULL; + aconnector = to_amdgpu_dm_connector(connector); + link = aconnector->dc_link; + } else { + struct drm_writeback_connector *wbcon = NULL; + struct amdgpu_dm_wb_connector *dm_wbcon = NULL; + + wbcon = drm_connector_to_writeback(connector); + dm_wbcon = to_amdgpu_dm_wb_connector(wbcon); + link = dm_wbcon->link; + } + + if (!aconnector || !aconnector->dc_sink) { + sink = create_fake_sink(dev, link); + if (!sink) + return stream; + + } else { + sink = aconnector->dc_sink; + dc_sink_retain(sink); + } + + stream = dc_create_stream_for_sink(sink); + + if (stream == NULL) { + drm_err(dev, "Failed to create stream for sink!\n"); + goto finish; + } + + /* We leave this NULL for writeback connectors */ + stream->dm_stream_context = aconnector; + + stream->timing.flags.LTE_340MCSC_SCRAMBLE = + connector->display_info.hdmi.scdc.scrambling.low_rates; + + list_for_each_entry(preferred_mode, &connector->modes, head) { + /* Search for preferred mode */ + if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) { + native_mode_found = true; + break; + } + } + if (!native_mode_found) + preferred_mode = list_first_entry_or_null( + &connector->modes, + struct drm_display_mode, + head); + + mode_refresh = drm_mode_vrefresh(&mode); + + if (preferred_mode == NULL) { + /* + * This may not be an error, the use case is when we have no + * usermode calls to reset and set mode upon hotplug. In this + * case, we call set mode ourselves to restore the previous mode + * and the modelist may not be filled in time. + */ + drm_dbg_driver(dev, "No preferred mode found\n"); + } else if (aconnector) { + recalculate_timing = amdgpu_freesync_vid_mode && + amdgpu_dm_is_freesync_video_mode(&mode, aconnector); + if (recalculate_timing) { + freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); + drm_mode_copy(&saved_mode, &mode); + saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio; + drm_mode_copy(&mode, freesync_mode); + mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio; + } else { + decide_crtc_timing_for_drm_display_mode( + &mode, preferred_mode, scale); + + preferred_refresh = drm_mode_vrefresh(preferred_mode); + } + } + + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + + /* + * If scaling is enabled and refresh rate didn't change + * we copy the vic and polarities of the old timings + */ + if (!scale || mode_refresh != preferred_refresh) + fill_stream_properties_from_drm_display_mode( + stream, &mode, connector, con_state, NULL, + requested_bpc); + else + fill_stream_properties_from_drm_display_mode( + stream, &mode, connector, con_state, old_stream, + requested_bpc); + + /* The rest isn't needed for writeback connectors */ + if (!aconnector) + goto finish; + + if (aconnector->timing_changed) { + drm_dbg(aconnector->base.dev, + "overriding timing for automated test, bpc %d, changing to %d\n", + stream->timing.display_color_depth, + aconnector->timing_requested->display_color_depth); + stream->timing = *aconnector->timing_requested; + } + +#if defined(CONFIG_DRM_AMD_DC_FP) + /* SST DSC determination policy */ + update_dsc_caps(aconnector, sink, stream, &dsc_caps); + if (aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE && dsc_caps.is_dsc_supported) + apply_dsc_policy_for_stream(aconnector, sink, stream, &dsc_caps); +#endif + + amdgpu_dm_update_stream_scaling_settings(dev, &mode, dm_state, stream); + + amdgpu_dm_fill_audio_info( + &stream->audio_info, + connector, + sink); + + update_stream_signal(stream, sink); + + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false); + + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT || + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST || + stream->signal == SIGNAL_TYPE_EDP) { + const struct dc_edid_caps *edid_caps; + unsigned int disable_colorimetry = 0; + + if (aconnector->dc_sink) { + edid_caps = &aconnector->dc_sink->edid_caps; + disable_colorimetry = edid_caps->panel_patch.disable_colorimetry; + } + + /* + * should decide stream support vsc sdp colorimetry capability + * before building vsc info packet + */ + stream->use_vsc_sdp_for_colorimetry = stream->link->dpcd_caps.dpcd_rev.raw >= 0x14 && + stream->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED && + !disable_colorimetry; + + if (stream->out_transfer_func.tf == TRANSFER_FUNCTION_GAMMA22) + tf = TRANSFER_FUNC_GAMMA_22; + mod_build_vsc_infopacket(stream, &stream->vsc_infopacket, stream->output_color_space, tf); + aconnector->sr_skip_count = AMDGPU_DM_PSR_ENTRY_DELAY; + + } +finish: + dc_sink_release(sink); + + return stream; +} + +/** + * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display + * @aconnector: DM connector to poll (owns @base drm_connector and @dc_link) + * @force: if true, force polling even when DAC load detection was used + * + * Used for connectors that don't support HPD (hotplug detection) to + * periodically check whether the connector is connected to a display. + * + * When connection was determined via DAC load detection, we avoid + * re-running it on normal polls to prevent visible glitches, unless + * @force is set. + * + * Return: The probed connector status (connected/disconnected/unknown). + */ +static enum drm_connector_status +amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) +{ + struct drm_connector *connector = &aconnector->base; + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dc_link *link = aconnector->dc_link; + enum dc_connection_type conn_type = dc_connection_none; + enum drm_connector_status status = connector_status_disconnected; + + /* When we determined the connection using DAC load detection, + * do NOT poll the connector do detect disconnect because + * that would run DAC load detection again which can cause + * visible visual glitches. + * + * Only allow to poll such a connector again when forcing. + */ + if (!force && link->local_sink && link->type == dc_connection_analog_load) + return connector->status; + + mutex_lock(&aconnector->hpd_lock); + + if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) && + conn_type != dc_connection_none) { + mutex_lock(&adev->dm.dc_lock); + + /* Only call full link detection when a sink isn't created yet, + * ie. just when the display is plugged in, otherwise we risk flickering. + */ + if (link->local_sink || + dc_link_detect(link, DETECT_REASON_HPD)) + status = connector_status_connected; + + mutex_unlock(&adev->dm.dc_lock); + } + + if (connector->status != status) { + if (status == connector_status_disconnected) { + if (link->local_sink) + dc_sink_release(link->local_sink); + + link->local_sink = NULL; + link->dpcd_sink_count = 0; + link->type = dc_connection_none; + } + + amdgpu_dm_update_connector_after_detect(aconnector); + } + + mutex_unlock(&aconnector->hpd_lock); + return status; +} + +/** + * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display + * + * A connector is considered connected when it has a sink that is not NULL. + * For connectors that support HPD (hotplug detection), the connection is + * handled in the HPD interrupt. + * For connectors that may not support HPD, such as analog connectors, + * DRM will call this function repeatedly to poll them. + * + * Notes: + * 1. This interface is NOT called in context of HPD irq. + * 2. This interface *is called* in context of user-mode ioctl. Which + * makes it a bad place for *any* MST-related activity. + * + * @connector: The DRM connector we are checking. We convert it to + * amdgpu_dm_connector so we can read the DC link and state. + * @force: If true, do a full detect again. This is used even when + * a lighter check would normally be used to avoid flicker. + * + * Return: The connector status (connected, disconnected, or unknown). + * + */ +static enum drm_connector_status +amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + update_subconnector_property(aconnector); + + if (aconnector->base.force == DRM_FORCE_ON || + aconnector->base.force == DRM_FORCE_ON_DIGITAL) + return connector_status_connected; + else if (aconnector->base.force == DRM_FORCE_OFF) + return connector_status_disconnected; + + /* Poll analog connectors and only when either + * disconnected or connected to an analog display. + */ + if (drm_kms_helper_is_poll_worker() && + dc_connector_supports_analog(aconnector->dc_link->link_id.id) && + (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog)) + return amdgpu_dm_connector_poll(aconnector, force); + + return (aconnector->dc_sink ? connector_status_connected : + connector_status_disconnected); +} + +int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *connector_state, + struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dm_connector_state *dm_old_state = + to_dm_connector_state(connector->state); + struct dm_connector_state *dm_new_state = + to_dm_connector_state(connector_state); + + int ret = -EINVAL; + + if (property == dev->mode_config.scaling_mode_property) { + enum amdgpu_rmx_type rmx_type; + + switch (val) { + case DRM_MODE_SCALE_CENTER: + rmx_type = RMX_CENTER; + break; + case DRM_MODE_SCALE_ASPECT: + rmx_type = RMX_ASPECT; + break; + case DRM_MODE_SCALE_FULLSCREEN: + rmx_type = RMX_FULL; + break; + case DRM_MODE_SCALE_NONE: + default: + rmx_type = RMX_OFF; + break; + } + + if (dm_old_state->scaling == rmx_type) + return 0; + + dm_new_state->scaling = rmx_type; + ret = 0; + } else if (property == adev->mode_info.underscan_hborder_property) { + dm_new_state->underscan_hborder = val; + ret = 0; + } else if (property == adev->mode_info.underscan_vborder_property) { + dm_new_state->underscan_vborder = val; + ret = 0; + } else if (property == adev->mode_info.underscan_property) { + dm_new_state->underscan_enable = val; + ret = 0; + } else if (property == adev->mode_info.abm_level_property) { + switch (val) { + case ABM_SYSFS_CONTROL: + dm_new_state->abm_sysfs_forbidden = false; + break; + case ABM_LEVEL_OFF: + dm_new_state->abm_sysfs_forbidden = true; + dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + break; + default: + dm_new_state->abm_sysfs_forbidden = true; + dm_new_state->abm_level = val; + } + ret = 0; + } + + return ret; +} + +int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dm_connector_state *dm_state = + to_dm_connector_state(state); + int ret = -EINVAL; + + if (property == dev->mode_config.scaling_mode_property) { + switch (dm_state->scaling) { + case RMX_CENTER: + *val = DRM_MODE_SCALE_CENTER; + break; + case RMX_ASPECT: + *val = DRM_MODE_SCALE_ASPECT; + break; + case RMX_FULL: + *val = DRM_MODE_SCALE_FULLSCREEN; + break; + case RMX_OFF: + default: + *val = DRM_MODE_SCALE_NONE; + break; + } + ret = 0; + } else if (property == adev->mode_info.underscan_hborder_property) { + *val = dm_state->underscan_hborder; + ret = 0; + } else if (property == adev->mode_info.underscan_vborder_property) { + *val = dm_state->underscan_vborder; + ret = 0; + } else if (property == adev->mode_info.underscan_property) { + *val = dm_state->underscan_enable; + ret = 0; + } else if (property == adev->mode_info.abm_level_property) { + if (!dm_state->abm_sysfs_forbidden) + *val = ABM_SYSFS_CONTROL; + else + *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ? + dm_state->abm_level : 0; + ret = 0; + } + + return ret; +} + +static void amdgpu_dm_connector_unregister(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); + + if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) + sysfs_remove_group(&connector->kdev->kobj, &amdgpu_group); + + cec_notifier_conn_unregister(amdgpu_dm_connector->notifier); + drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux); +} + +static 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); + struct amdgpu_display_manager *dm = &adev->dm; + + /* + * Call only if mst_mgr was initialized before since it's not done + * for all connector types. + */ + if (aconnector->mst_mgr.dev) + drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); + + /* Cancel and flush any pending HDMI HPD debounce work */ + if (aconnector->hdmi_hpd_debounce_delay_ms) { + cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); + if (aconnector->hdmi_prev_sink) { + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = NULL; + } + } + + if (aconnector->bl_idx != -1) { + backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]); + dm->backlight_dev[aconnector->bl_idx] = NULL; + } + + if (aconnector->dc_em_sink) + dc_sink_release(aconnector->dc_em_sink); + aconnector->dc_em_sink = NULL; + if (aconnector->dc_sink) + dc_sink_release(aconnector->dc_sink); + aconnector->dc_sink = NULL; + + drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + kfree(aconnector->dm_dp_aux.aux.name); + + kfree(connector); +} + +void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) +{ + struct dm_connector_state *state = + to_dm_connector_state(connector->state); + + if (connector->state) + __drm_atomic_helper_connector_destroy_state(connector->state); + + kfree(state); + + state = kzalloc_obj(*state); + + if (state) { + state->scaling = RMX_OFF; + state->underscan_enable = false; + state->underscan_hborder = 0; + state->underscan_vborder = 0; + state->base.max_requested_bpc = 8; + state->vcpi_slots = 0; + state->pbn = 0; + + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + if (amdgpu_dm_abm_level <= 0) + state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + else + state->abm_level = amdgpu_dm_abm_level; + } + + __drm_atomic_helper_connector_reset(connector, &state->base); + } +} + +struct drm_connector_state * +amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) +{ + struct dm_connector_state *state = + to_dm_connector_state(connector->state); + + struct dm_connector_state *new_state = + kmemdup(state, sizeof(*state), GFP_KERNEL); + + if (!new_state) + return NULL; + + __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base); + + new_state->freesync_capable = state->freesync_capable; + new_state->abm_level = state->abm_level; + new_state->scaling = state->scaling; + new_state->underscan_enable = state->underscan_enable; + new_state->underscan_hborder = state->underscan_hborder; + new_state->underscan_vborder = state->underscan_vborder; + new_state->vcpi_slots = state->vcpi_slots; + new_state->pbn = state->pbn; + return &new_state->base; +} + +static int +amdgpu_dm_connector_late_register(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + int r; + + if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) { + r = sysfs_create_group(&connector->kdev->kobj, + &amdgpu_group); + if (r) + return r; + } + + amdgpu_dm_register_backlight_device(amdgpu_dm_connector); + + if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || + (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { + amdgpu_dm_connector->dm_dp_aux.aux.dev = connector->kdev; + r = drm_dp_aux_register(&amdgpu_dm_connector->dm_dp_aux.aux); + if (r) + return r; + } + +#if defined(CONFIG_DEBUG_FS) + connector_debugfs_init(amdgpu_dm_connector); +#endif + + return 0; +} + +static 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; + struct dc_sink *dc_em_sink = aconnector->dc_em_sink; + const struct drm_edid *drm_edid; + struct i2c_adapter *ddc; + struct drm_device *dev = connector->dev; + + if (dc_link && dc_link->aux_mode) + ddc = &aconnector->dm_dp_aux.aux.ddc; + else + ddc = &aconnector->i2c->base; + + drm_edid = drm_edid_read_ddc(connector, ddc); + drm_edid_connector_update(connector, drm_edid); + if (!drm_edid) { + drm_err(dev, "No EDID found on connector: %s.\n", connector->name); + return; + } + + aconnector->drm_edid = drm_edid; + /* Update emulated (virtual) sink's EDID */ + if (dc_em_sink && dc_link) { + /* FIXME: Get rid of drm_edid_raw() */ + const struct edid *edid = drm_edid_raw(drm_edid); + + memset(&dc_em_sink->edid_caps, 0, sizeof(struct dc_edid_caps)); + memmove(dc_em_sink->dc_edid.raw_edid, edid, + (edid->extensions + 1) * EDID_LENGTH); + dm_helpers_parse_edid_caps( + dc_link, + &dc_em_sink->dc_edid, + &dc_em_sink->edid_caps); + } +} + +static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { + .reset = amdgpu_dm_connector_funcs_reset, + .detect = amdgpu_dm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = amdgpu_dm_connector_destroy, + .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_set_property = amdgpu_dm_connector_atomic_set_property, + .atomic_get_property = amdgpu_dm_connector_atomic_get_property, + .late_register = amdgpu_dm_connector_late_register, + .early_unregister = amdgpu_dm_connector_unregister, + .force = amdgpu_dm_connector_funcs_force +}; + +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) +{ + struct drm_connector *connector = &aconnector->base; + struct dc_link *dc_link = aconnector->dc_link; + struct dc_sink_init_data init_params = { + .link = aconnector->dc_link, + .sink_signal = SIGNAL_TYPE_VIRTUAL + }; + const struct drm_edid *drm_edid; + const struct edid *edid; + struct i2c_adapter *ddc; + + if (dc_link && dc_link->aux_mode) + ddc = &aconnector->dm_dp_aux.aux.ddc; + else + ddc = &aconnector->i2c->base; + + drm_edid = drm_edid_read_ddc(connector, ddc); + drm_edid_connector_update(connector, drm_edid); + if (!drm_edid) { + drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name); + return; + } + + if (connector->display_info.is_hdmi) + init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + + aconnector->drm_edid = drm_edid; + + /* FIXME: Get rid of drm_edid_raw() */ + edid = drm_edid_raw(drm_edid); + aconnector->dc_em_sink = dc_link_add_remote_sink( + aconnector->dc_link, + (uint8_t *)edid, + (edid->extensions + 1) * EDID_LENGTH, + &init_params); + + if (aconnector->base.force == DRM_FORCE_ON) { + aconnector->dc_sink = aconnector->dc_link->local_sink ? + aconnector->dc_link->local_sink : + aconnector->dc_em_sink; + if (aconnector->dc_sink) + dc_sink_retain(aconnector->dc_sink); + } +} + +static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) +{ + struct dc_link *link = (struct dc_link *)aconnector->dc_link; + + /* + * In case of headless boot with force on for DP managed connector + * Those settings have to be != 0 to get initial modeset + */ + if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) { + link->verified_link_cap.lane_count = LANE_COUNT_FOUR; + link->verified_link_cap.link_rate = LINK_RATE_HIGH2; + } + + create_eml_sink(aconnector); +} + +static enum dc_status dm_validate_stream_and_context(struct dc *dc, + struct dc_stream_state *stream) +{ + enum dc_status dc_result = DC_ERROR_UNEXPECTED; + struct dc_plane_state *dc_plane_state = NULL; + struct dc_state *dc_state = NULL; + + if (!stream) + goto cleanup; + + dc_plane_state = dc_create_plane_state(dc); + if (!dc_plane_state) + goto cleanup; + + dc_state = dc_state_create(dc, NULL); + if (!dc_state) + goto cleanup; + + /* populate stream to plane */ + dc_plane_state->src_rect.height = stream->src.height; + dc_plane_state->src_rect.width = stream->src.width; + dc_plane_state->dst_rect.height = stream->src.height; + dc_plane_state->dst_rect.width = stream->src.width; + dc_plane_state->clip_rect.height = stream->src.height; + dc_plane_state->clip_rect.width = stream->src.width; + dc_plane_state->plane_size.surface_pitch = ((stream->src.width + 255) / 256) * 256; + dc_plane_state->plane_size.surface_size.height = stream->src.height; + dc_plane_state->plane_size.surface_size.width = stream->src.width; + dc_plane_state->plane_size.chroma_size.height = stream->src.height; + dc_plane_state->plane_size.chroma_size.width = stream->src.width; + dc_plane_state->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + dc_plane_state->tiling_info.gfx9.swizzle = DC_SW_UNKNOWN; + dc_plane_state->rotation = ROTATION_ANGLE_0; + dc_plane_state->is_tiling_rotated = false; + dc_plane_state->tiling_info.gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL; + + dc_result = dc_validate_stream(dc, stream); + if (dc_result == DC_OK) + dc_result = dc_validate_plane(dc, dc_plane_state); + + if (dc_result == DC_OK) + dc_result = dc_state_add_stream(dc, dc_state, stream); + + if (dc_result == DC_OK && !dc_state_add_plane( + dc, + stream, + dc_plane_state, + dc_state)) + dc_result = DC_FAIL_ATTACH_SURFACES; + + if (dc_result == DC_OK) + dc_result = dc_validate_global_state(dc, dc_state, DC_VALIDATE_MODE_ONLY); + +cleanup: + if (dc_state) + dc_state_release(dc_state); + + if (dc_plane_state) + dc_plane_state_release(dc_plane_state); + + return dc_result; +} + +struct dc_stream_state * +amdgpu_dm_create_validate_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) +{ + struct amdgpu_dm_connector *aconnector = NULL; + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct dc_stream_state *stream; + const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL; + int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8; + enum dc_status dc_result = DC_OK; + uint8_t bpc_limit = 6; + + if (!dm_state) + return NULL; + + if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) + aconnector = to_amdgpu_dm_connector(connector); + + if (aconnector && + (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A || + aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL || + aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)) + bpc_limit = 8; + + do { + drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc); + stream = create_stream_for_sink(connector, drm_mode, + dm_state, old_stream, + requested_bpc); + if (stream == NULL) { + drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n"); + break; + } + + dc_result = dc_validate_stream(adev->dm.dc, stream); + + if (!aconnector) /* writeback connector */ + return stream; + + if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream); + + if (dc_result == DC_OK) + dc_result = dm_validate_stream_and_context(adev->dm.dc, stream); + + if (dc_result != DC_OK) { + drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n", + drm_mode->hdisplay, + drm_mode->vdisplay, + drm_mode->clock, + dc_pixel_encoding_to_str(stream->timing.pixel_encoding), + dc_color_depth_to_str(stream->timing.display_color_depth), + dc_status_to_str(dc_result)); + + dc_stream_release(stream); + stream = NULL; + requested_bpc -= 2; /* lower bpc to retry validation */ + } + + } while (stream == NULL && requested_bpc >= bpc_limit); + + switch (dc_result) { + /* + * If we failed to validate DP bandwidth stream with the requested RGB color depth, + * we try to fallback and configure in order: + * YUV422 (8bpc, 6bpc) + * YUV420 (8bpc, 6bpc) + */ + case DC_FAIL_ENC_VALIDATE: + case DC_EXCEED_DONGLE_CAP: + case DC_NO_DP_LINK_BANDWIDTH: + /* recursively entered twice and already tried both YUV422 and YUV420 */ + if (aconnector->force_yuv422_output && aconnector->force_yuv420_output) + break; + /* first failure; try YUV422 */ + if (!aconnector->force_yuv422_output) { + drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n", + __func__, __LINE__, dc_result); + aconnector->force_yuv422_output = true; + /* recursively entered and YUV422 failed, try YUV420 */ + } else if (!aconnector->force_yuv420_output) { + drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n", + __func__, __LINE__, dc_result); + aconnector->force_yuv420_output = true; + } + stream = amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode, + dm_state, old_stream); + aconnector->force_yuv422_output = false; + aconnector->force_yuv420_output = false; + break; + case DC_OK: + break; + default: + drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n", + __func__, __LINE__, dc_result); + break; + } + + return stream; +} + +enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + int result = MODE_ERROR; + struct dc_sink *dc_sink; + struct drm_display_mode *test_mode; + /* TODO: Unhardcode stream count */ + struct dc_stream_state *stream; + /* we always have an amdgpu_dm_connector here since we got + * here via the amdgpu_dm_connector_helper_funcs + */ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) || + (mode->flags & DRM_MODE_FLAG_DBLSCAN)) + return result; + + /* + * Only run this the first time mode_valid is called to initilialize + * EDID mgmt + */ + if (aconnector->base.force != DRM_FORCE_UNSPECIFIED && + !aconnector->dc_em_sink) + handle_edid_mgmt(aconnector); + + dc_sink = to_amdgpu_dm_connector(connector)->dc_sink; + + if (dc_sink == NULL && aconnector->base.force != DRM_FORCE_ON_DIGITAL && + aconnector->base.force != DRM_FORCE_ON) { + drm_err(connector->dev, "dc_sink is NULL!\n"); + goto fail; + } + + test_mode = drm_mode_duplicate(connector->dev, mode); + if (!test_mode) + goto fail; + + drm_mode_set_crtcinfo(test_mode, 0); + + stream = amdgpu_dm_create_validate_stream_for_sink(connector, test_mode, + to_dm_connector_state(connector->state), + NULL); + drm_mode_destroy(connector->dev, test_mode); + if (stream) { + dc_stream_release(stream); + result = MODE_OK; + } + +fail: + /* TODO: error handling*/ + return result; +} + +int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, + struct dc_info_packet *out) +{ + struct hdmi_drm_infoframe frame; + unsigned char buf[30]; /* 26 + 4 */ + ssize_t len; + int ret, i; + + memset(out, 0, sizeof(*out)); + + if (!state->hdr_output_metadata) + return 0; + + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, state); + if (ret) + return ret; + + len = hdmi_drm_infoframe_pack_only(&frame, buf, sizeof(buf)); + if (len < 0) + return (int)len; + + /* Static metadata is a fixed 26 bytes + 4 byte header. */ + if (len != 30) + return -EINVAL; + + /* Prepare the infopacket for DC. */ + switch (state->connector->connector_type) { + case DRM_MODE_CONNECTOR_HDMIA: + out->hb0 = 0x87; /* type */ + out->hb1 = 0x01; /* version */ + out->hb2 = 0x1A; /* length */ + out->sb[0] = buf[3]; /* checksum */ + i = 1; + break; + + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_eDP: + out->hb0 = 0x00; /* sdp id, zero */ + out->hb1 = 0x87; /* type */ + out->hb2 = 0x1D; /* payload len - 1 */ + out->hb3 = (0x13 << 2); /* sdp version */ + out->sb[0] = 0x01; /* version */ + out->sb[1] = 0x1A; /* length */ + i = 2; + break; + + default: + return -EINVAL; + } + + memcpy(&out->sb[i], &buf[4], 26); + out->valid = true; + + print_hex_dump(KERN_DEBUG, "HDR SB:", DUMP_PREFIX_NONE, 16, 1, out->sb, + sizeof(out->sb), false); + + return 0; +} + +static int +amdgpu_dm_connector_atomic_check(struct drm_connector *conn, + struct drm_atomic_commit *state) +{ + struct drm_connector_state *new_con_state = + drm_atomic_get_new_connector_state(state, conn); + struct drm_connector_state *old_con_state = + drm_atomic_get_old_connector_state(state, conn); + struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc_state *new_crtc_state; + struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); + int ret; + + if (WARN_ON(unlikely(!old_con_state || !new_con_state))) + return -EINVAL; + + trace_amdgpu_dm_connector_atomic_check(new_con_state); + + if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr); + if (ret < 0) + return ret; + } + + if (!crtc) + return 0; + + if (new_con_state->privacy_screen_sw_state != old_con_state->privacy_screen_sw_state) { + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + new_crtc_state->mode_changed = true; + } + + if (new_con_state->colorspace != old_con_state->colorspace) { + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + new_crtc_state->mode_changed = true; + } + + if (new_con_state->content_type != old_con_state->content_type) { + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + new_crtc_state->mode_changed = true; + } + + if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) { + struct dc_info_packet hdr_infopacket; + + ret = amdgpu_dm_fill_hdr_info_packet(new_con_state, &hdr_infopacket); + if (ret) + return ret; + + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + /* + * DC considers the stream backends changed if the + * static metadata changes. Forcing the modeset also + * gives a simple way for userspace to switch from + * 8bpc to 10bpc when setting the metadata to enter + * or exit HDR. + * + * Changing the static metadata after it's been + * set is permissible, however. So only force a + * modeset if we're entering or exiting HDR. + */ + new_crtc_state->mode_changed = new_crtc_state->mode_changed || + !old_con_state->hdr_output_metadata || + !new_con_state->hdr_output_metadata; + } + + return 0; +} + +static const struct drm_connector_helper_funcs +amdgpu_dm_connector_helper_funcs = { + /* + * If hotplugging a second bigger display in FB Con mode, bigger resolution + * modes will be filtered by drm_mode_validate_size(), and those modes + * are missing after user start lightdm. So we need to renew modes list. + * in get_modes call back, not just return the modes count + */ + .get_modes = get_modes, + .mode_valid = amdgpu_dm_connector_mode_valid, + .atomic_check = amdgpu_dm_connector_atomic_check, +}; + +int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth) +{ + switch (display_color_depth) { + case COLOR_DEPTH_666: + return 6; + case COLOR_DEPTH_888: + return 8; + case COLOR_DEPTH_101010: + return 10; + case COLOR_DEPTH_121212: + return 12; + case COLOR_DEPTH_141414: + return 14; + case COLOR_DEPTH_161616: + return 16; + default: + break; + } + return 0; +} + +static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) +{ + switch (st) { + case SIGNAL_TYPE_HDMI_TYPE_A: + return DRM_MODE_CONNECTOR_HDMIA; + case SIGNAL_TYPE_EDP: + return DRM_MODE_CONNECTOR_eDP; + case SIGNAL_TYPE_LVDS: + return DRM_MODE_CONNECTOR_LVDS; + case SIGNAL_TYPE_RGB: + return DRM_MODE_CONNECTOR_VGA; + case SIGNAL_TYPE_DISPLAY_PORT: + case SIGNAL_TYPE_DISPLAY_PORT_MST: + /* External DP bridges have a different connector type. */ + if (connector_id == CONNECTOR_ID_VGA) + return DRM_MODE_CONNECTOR_VGA; + else if (connector_id == CONNECTOR_ID_LVDS) + return DRM_MODE_CONNECTOR_LVDS; + + return DRM_MODE_CONNECTOR_DisplayPort; + case SIGNAL_TYPE_DVI_DUAL_LINK: + case SIGNAL_TYPE_DVI_SINGLE_LINK: + if (connector_id == CONNECTOR_ID_SINGLE_LINK_DVII || + connector_id == CONNECTOR_ID_DUAL_LINK_DVII) + return DRM_MODE_CONNECTOR_DVII; + + return DRM_MODE_CONNECTOR_DVID; + case SIGNAL_TYPE_VIRTUAL: + return DRM_MODE_CONNECTOR_VIRTUAL; + + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + + /* There is only one encoder per connector */ + drm_connector_for_each_possible_encoder(connector, encoder) + return encoder; + + return NULL; +} + +static void amdgpu_dm_get_native_mode(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + + encoder = amdgpu_dm_connector_to_encoder(connector); + + if (encoder == NULL) + return; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + amdgpu_encoder->native_mode.clock = 0; + + if (!list_empty(&connector->probed_modes)) { + struct drm_display_mode *preferred_mode = NULL; + + list_for_each_entry(preferred_mode, + &connector->probed_modes, + head) { + if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) + amdgpu_encoder->native_mode = *preferred_mode; + + break; + } + + } +} + +static struct drm_display_mode * +amdgpu_dm_create_common_mode(struct drm_encoder *encoder, + const char *name, + int hdisplay, int vdisplay) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *mode = NULL; + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + mode = drm_mode_duplicate(dev, native_mode); + + if (mode == NULL) + return NULL; + + mode->hdisplay = hdisplay; + mode->vdisplay = vdisplay; + mode->type &= ~DRM_MODE_TYPE_PREFERRED; + strscpy(mode->name, name, DRM_DISPLAY_MODE_LEN); + + return mode; + +} + +static const struct amdgpu_dm_mode_size { + char name[DRM_DISPLAY_MODE_LEN]; + int w; + int h; +} common_modes[] = { + { "640x480", 640, 480}, + { "800x600", 800, 600}, + { "1024x768", 1024, 768}, + { "1280x720", 1280, 720}, + { "1280x800", 1280, 800}, + {"1280x1024", 1280, 1024}, + { "1440x900", 1440, 900}, + {"1680x1050", 1680, 1050}, + {"1600x1200", 1600, 1200}, + {"1920x1080", 1920, 1080}, + {"1920x1200", 1920, 1200} +}; + +static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *mode = NULL; + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + int i; + int n; + + if ((connector->connector_type != DRM_MODE_CONNECTOR_eDP) && + (connector->connector_type != DRM_MODE_CONNECTOR_LVDS)) + return; + + n = ARRAY_SIZE(common_modes); + + for (i = 0; i < n; i++) { + struct drm_display_mode *curmode = NULL; + bool mode_existed = false; + + if (common_modes[i].w > native_mode->hdisplay || + common_modes[i].h > native_mode->vdisplay || + (common_modes[i].w == native_mode->hdisplay && + common_modes[i].h == native_mode->vdisplay)) + continue; + + list_for_each_entry(curmode, &connector->probed_modes, head) { + if (common_modes[i].w == curmode->hdisplay && + common_modes[i].h == curmode->vdisplay) { + mode_existed = true; + break; + } + } + + if (mode_existed) + continue; + + mode = amdgpu_dm_create_common_mode(encoder, + common_modes[i].name, common_modes[i].w, + common_modes[i].h); + if (!mode) + continue; + + drm_mode_probed_add(connector, mode); + amdgpu_dm_connector->num_modes++; + } +} + +void amdgpu_set_panel_orientation(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + const struct drm_display_mode *native_mode; + + if (connector->connector_type != DRM_MODE_CONNECTOR_eDP && + connector->connector_type != DRM_MODE_CONNECTOR_LVDS) + return; + + mutex_lock(&connector->dev->mode_config.mutex); + amdgpu_dm_connector_get_modes(connector); + mutex_unlock(&connector->dev->mode_config.mutex); + + encoder = amdgpu_dm_connector_to_encoder(connector); + if (!encoder) + return; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + native_mode = &amdgpu_encoder->native_mode; + if (native_mode->hdisplay == 0 || native_mode->vdisplay == 0) + return; + + drm_connector_set_panel_orientation_with_quirk(connector, + DRM_MODE_PANEL_ORIENTATION_UNKNOWN, + native_mode->hdisplay, + native_mode->vdisplay); +} + +static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + + if (drm_edid) { + /* empty probed_modes */ + INIT_LIST_HEAD(&connector->probed_modes); + amdgpu_dm_connector->num_modes = + drm_edid_connector_add_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 + * later in the probed mode list could be of higher + * and preferred resolution. For example, 3840x2160 + * resolution in base EDID preferred timing and 4096x2160 + * preferred resolution in DID extension block later. + */ + drm_mode_sort(&connector->probed_modes); + amdgpu_dm_get_native_mode(connector); + + /* Freesync capabilities are reset by calling + * drm_edid_connector_add_modes() and need to be + * restored here. + */ + amdgpu_dm_update_freesync_caps(connector, drm_edid, false); + } else { + amdgpu_dm_connector->num_modes = 0; + } +} + +static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, + struct drm_display_mode *mode) +{ + struct drm_display_mode *m; + + list_for_each_entry(m, &aconnector->base.probed_modes, head) { + if (drm_mode_equal(m, mode)) + return true; + } + + return false; +} + +static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) +{ + const struct drm_display_mode *m; + struct drm_display_mode *new_mode; + uint i; + u32 new_modes_count = 0; + + /* Standard FPS values + * + * 23.976 - TV/NTSC + * 24 - Cinema + * 25 - TV/PAL + * 29.97 - TV/NTSC + * 30 - TV/NTSC + * 48 - Cinema HFR + * 50 - TV/PAL + * 60 - Commonly used + * 48,72,96,120 - Multiples of 24 + */ + static const u32 common_rates[] = { + 23976, 24000, 25000, 29970, 30000, + 48000, 50000, 60000, 72000, 96000, 120000 + }; + + /* + * Find mode with highest refresh rate with the same resolution + * as the preferred mode. Some monitors report a preferred mode + * with lower resolution than the highest refresh rate supported. + */ + + m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true); + if (!m) + return 0; + + for (i = 0; i < ARRAY_SIZE(common_rates); i++) { + u64 target_vtotal, target_vtotal_diff; + u64 num, den; + + if (drm_mode_vrefresh(m) * 1000 < common_rates[i]) + continue; + + if (common_rates[i] < aconnector->min_vfreq * 1000 || + common_rates[i] > aconnector->max_vfreq * 1000) + continue; + + num = (unsigned long long)m->clock * 1000 * 1000; + den = common_rates[i] * (unsigned long long)m->htotal; + target_vtotal = div_u64(num, den); + target_vtotal_diff = target_vtotal - m->vtotal; + + /* Check for illegal modes */ + if (m->vsync_start + target_vtotal_diff < m->vdisplay || + m->vsync_end + target_vtotal_diff < m->vsync_start || + m->vtotal + target_vtotal_diff < m->vsync_end) + continue; + + new_mode = drm_mode_duplicate(aconnector->base.dev, m); + if (!new_mode) + goto out; + + new_mode->vtotal += (u16)target_vtotal_diff; + new_mode->vsync_start += (u16)target_vtotal_diff; + new_mode->vsync_end += (u16)target_vtotal_diff; + new_mode->type &= ~DRM_MODE_TYPE_PREFERRED; + new_mode->type |= DRM_MODE_TYPE_DRIVER; + + if (!is_duplicate_mode(aconnector, new_mode)) { + drm_mode_probed_add(&aconnector->base, new_mode); + new_modes_count += 1; + } else + drm_mode_destroy(aconnector->base.dev, new_mode); + } + out: + return new_modes_count; +} + +static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + + if (!(amdgpu_freesync_vid_mode && drm_edid)) + return; + + if (!amdgpu_dm_connector->dc_sink || !amdgpu_dm_connector->dc_link) + return; + + if (!dc_supports_vrr(amdgpu_dm_connector->dc_sink->ctx->dce_version)) + return; + + if (dc_connector_supports_analog(amdgpu_dm_connector->dc_link->link_id.id) && + amdgpu_dm_connector->dc_sink->edid_caps.analog) + return; + + if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + amdgpu_dm_connector->num_modes += + add_fs_modes(amdgpu_dm_connector); +} + +static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + struct dc_link *dc_link = amdgpu_dm_connector->dc_link; + struct drm_encoder *encoder; + const struct drm_edid *drm_edid = amdgpu_dm_connector->drm_edid; + struct dc_link_settings *verified_link_cap = &dc_link->verified_link_cap; + const struct dc *dc = dc_link->dc; + + encoder = amdgpu_dm_connector_to_encoder(connector); + + if (!drm_edid) { + amdgpu_dm_connector->num_modes = + drm_add_modes_noedid(connector, 640, 480); + if (dc->link_srv->dp_get_encoding_format(verified_link_cap) == DP_128b_132b_ENCODING) + amdgpu_dm_connector->num_modes += + drm_add_modes_noedid(connector, 1920, 1080); + + if (amdgpu_dm_connector->dc_sink && + amdgpu_dm_connector->dc_sink->edid_caps.analog && + dc_connector_supports_analog(dc_link->link_id.id)) { + /* Analog monitor connected by DAC load detection. + * Add common modes. It will be up to the user to select one that works. + */ + for (int i = 0; i < ARRAY_SIZE(common_modes); i++) + amdgpu_dm_connector->num_modes += drm_add_modes_noedid( + connector, common_modes[i].w, common_modes[i].h); + } + } else { + amdgpu_dm_connector_ddc_get_modes(connector, drm_edid); + if (encoder) + amdgpu_dm_connector_add_common_modes(encoder, connector); + amdgpu_dm_connector_add_freesync_modes(connector, drm_edid); + } + amdgpu_dm_fbc_init(connector); + + return amdgpu_dm_connector->num_modes; +} + +static const u32 supported_colorspaces = + BIT(DRM_MODE_COLORIMETRY_BT709_YCC) | + BIT(DRM_MODE_COLORIMETRY_OPRGB) | + BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) | + BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); + +static void hdmi_frl_status_polling_work(struct work_struct *work) +{ + struct amdgpu_display_manager *dm = + container_of(to_delayed_work(work), struct amdgpu_display_manager, + hdmi_frl_status_polling_work); + struct dc *dc = dm->dc; + struct dc_link *dc_link; + bool link_update = false; + + for (int i = 0; i < MAX_LINKS; i++) { + dc_link = dc->links[i]; + + + if (!dc_link || !dc_link->local_sink) + continue; + + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + + if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); + if (link_update) { + mutex_lock(&dm->dc_lock); + dc_link_detect(dc_link, DETECT_REASON_RETRAIN); + mutex_unlock(&dm->dc_lock); + } + } + + queue_delayed_work(dm->hdmi_frl_status_polling_wq, + &dm->hdmi_frl_status_polling_work, + msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms)); +} + +void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector, + int connector_type, + struct dc_link *link, + int link_index) +{ + struct amdgpu_device *adev = drm_to_adev(dm->ddev); + + /* + * Some of the properties below require access to state, like bpc. + * Allocate some default initial connector state with our reset helper. + */ + if (aconnector->base.funcs->reset) + aconnector->base.funcs->reset(&aconnector->base); + + aconnector->connector_id = link_index; + aconnector->bl_idx = -1; + aconnector->dc_link = link; + aconnector->base.interlace_allowed = false; + aconnector->base.doublescan_allowed = false; + aconnector->base.stereo_allowed = false; + aconnector->base.dpms = DRM_MODE_DPMS_OFF; + aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */ + aconnector->audio_inst = -1; + aconnector->pack_sdp_v1_3 = false; + aconnector->as_type = ADAPTIVE_SYNC_TYPE_NONE; + memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info)); + mutex_init(&aconnector->hpd_lock); + mutex_init(&aconnector->handle_mst_msg_ready); + + /* + * If HDMI HPD debounce delay is set, use the minimum between selected + * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS + */ + if (amdgpu_hdmi_hpd_debounce_delay_ms) { + aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, + AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); + INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work); + aconnector->hdmi_prev_sink = NULL; + } else { + aconnector->hdmi_hpd_debounce_delay_ms = 0; + } + + dm->hdmi_frl_status_polling_delay_ms = 200; + INIT_DELAYED_WORK(&dm->hdmi_frl_status_polling_work, hdmi_frl_status_polling_work); + /* + * configure support HPD hot plug connector_>polled default value is 0 + * which means HPD hot plug not supported + */ + switch (connector_type) { + case DRM_MODE_CONNECTOR_HDMIA: + aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; + aconnector->base.ycbcr_420_allowed = + link->link_enc->features.hdmi_ycbcr420_supported ? true : false; + break; + case DRM_MODE_CONNECTOR_DisplayPort: + aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; + link->link_enc = link_enc_cfg_get_link_enc(link); + ASSERT(link->link_enc); + if (link->link_enc) + aconnector->base.ycbcr_420_allowed = + link->link_enc->features.dp_ycbcr420_supported ? true : false; + break; + case DRM_MODE_CONNECTOR_DVID: + aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; + break; + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_VGA: + aconnector->base.polled = + DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + break; + default: + break; + } + + drm_object_attach_property(&aconnector->base.base, + dm->ddev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA + || (connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root)) + drm_connector_attach_broadcast_rgb_property(&aconnector->base); + + drm_object_attach_property(&aconnector->base.base, + adev->mode_info.underscan_property, + UNDERSCAN_OFF); + drm_object_attach_property(&aconnector->base.base, + adev->mode_info.underscan_hborder_property, + 0); + drm_object_attach_property(&aconnector->base.base, + adev->mode_info.underscan_vborder_property, + 0); + + if (!aconnector->mst_root) + drm_connector_attach_max_bpc_property(&aconnector->base, 8, 16); + + aconnector->base.state->max_bpc = 16; + aconnector->base.state->max_requested_bpc = aconnector->base.state->max_bpc; + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { + /* Content Type is currently only implemented for HDMI. */ + drm_connector_attach_content_type_property(&aconnector->base); + } + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { + if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces)) + drm_connector_attach_colorspace_property(&aconnector->base); + } else if ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) || + connector_type == DRM_MODE_CONNECTOR_eDP) { + if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces)) + drm_connector_attach_colorspace_property(&aconnector->base); + } + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_DisplayPort || + connector_type == DRM_MODE_CONNECTOR_eDP) { + drm_connector_attach_hdr_output_metadata_property(&aconnector->base); + + if (!aconnector->mst_root) + drm_connector_attach_vrr_capable_property(&aconnector->base); + + + if (adev->dm.hdcp_workqueue) + drm_connector_attach_content_protection_property(&aconnector->base, true); + } + + if (connector_type == DRM_MODE_CONNECTOR_eDP) { + struct drm_privacy_screen *privacy_screen; + + drm_connector_attach_panel_type_property(&aconnector->base); + + privacy_screen = drm_privacy_screen_get(adev_to_drm(adev)->dev, NULL); + if (!IS_ERR(privacy_screen)) { + drm_connector_attach_privacy_screen_provider(&aconnector->base, + privacy_screen); + } else if (PTR_ERR(privacy_screen) != -ENODEV) { + drm_warn(adev_to_drm(adev), "Error getting privacy-screen\n"); + } + } +} + +static 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); + struct ddc_service *ddc_service = i2c->ddc_service; + struct i2c_command cmd; + int i; + int result = -EIO; + + if (!ddc_service->ddc_pin) + return result; + + cmd.payloads = kzalloc_objs(struct i2c_payload, num); + + if (!cmd.payloads) + return result; + + cmd.number_of_payloads = num; + cmd.engine = I2C_COMMAND_ENGINE_DEFAULT; + cmd.speed = 100; + + for (i = 0; i < num; i++) { + cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD); + cmd.payloads[i].address = msgs[i].addr; + cmd.payloads[i].length = msgs[i].len; + cmd.payloads[i].data = msgs[i].buf; + } + + if (i2c->oem) { + if (dc_submit_i2c_oem( + ddc_service->ctx->dc, + &cmd)) + result = num; + } else { + if (dc_submit_i2c( + ddc_service->ctx->dc, + ddc_service->link->link_index, + &cmd)) + result = num; + } + + kfree(cmd.payloads); + return result; +} + +static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm amdgpu_dm_i2c_algo = { + .master_xfer = amdgpu_dm_i2c_xfer, + .functionality = amdgpu_dm_i2c_func, +}; + +struct amdgpu_i2c_adapter * +amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem) +{ + struct amdgpu_device *adev = ddc_service->ctx->driver_context; + struct amdgpu_i2c_adapter *i2c; + + i2c = kzalloc_obj(struct amdgpu_i2c_adapter); + if (!i2c) + return NULL; + i2c->base.owner = THIS_MODULE; + i2c->base.dev.parent = &adev->pdev->dev; + i2c->base.algo = &amdgpu_dm_i2c_algo; + if (oem) + snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c OEM bus"); + else + snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c hw bus %d", + ddc_service->link->link_index); + i2c_set_adapdata(&i2c->base, i2c); + i2c->ddc_service = ddc_service; + i2c->oem = oem; + + return i2c; +} + +int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector) +{ + struct cec_connector_info conn_info; + struct drm_device *ddev = aconnector->base.dev; + struct device *hdmi_dev = ddev->dev; + + if (amdgpu_dc_debug_mask & DC_DISABLE_HDMI_CEC) { + drm_info(ddev, "HDMI-CEC feature masked\n"); + return -EINVAL; + } + + cec_fill_conn_info_from_drm(&conn_info, &aconnector->base); + aconnector->notifier = + cec_notifier_conn_register(hdmi_dev, NULL, &conn_info); + if (!aconnector->notifier) { + drm_err(ddev, "Failed to create cec notifier\n"); + return -ENOMEM; + } + + return 0; +} + +/* + * Note: this function assumes that dc_link_detect() was called for the + * dc_link which will be represented by this aconnector. + */ +int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector, + u32 link_index, + struct amdgpu_encoder *aencoder) +{ + int res = 0; + int connector_type; + struct dc *dc = dm->dc; + struct dc_link *link = dc_get_link_at_index(dc, link_index); + struct amdgpu_i2c_adapter *i2c; + + /* Not needed for writeback connector */ + link->priv = aconnector; + + + i2c = amdgpu_dm_create_i2c(link->ddc, false); + if (!i2c) { + drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n"); + return -ENOMEM; + } + + aconnector->i2c = i2c; + res = devm_i2c_add_adapter(dm->adev->dev, &i2c->base); + + if (res) { + drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index); + goto out_free; + } + + connector_type = to_drm_connector_type(link->connector_signal, link->link_id.id); + + res = drm_connector_init_with_ddc( + dm->ddev, + &aconnector->base, + &amdgpu_dm_connector_funcs, + connector_type, + &i2c->base); + + if (res) { + drm_err(adev_to_drm(dm->adev), "connector_init failed\n"); + aconnector->connector_id = -1; + goto out_free; + } + + drm_connector_helper_add( + &aconnector->base, + &amdgpu_dm_connector_helper_funcs); + + amdgpu_dm_connector_init_helper( + dm, + aconnector, + connector_type, + link, + link_index); + + drm_connector_attach_encoder( + &aconnector->base, &aencoder->base); + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_HDMIB) + amdgpu_dm_initialize_hdmi_connector(aconnector); + + if (dc_is_dp_signal(link->connector_signal)) + amdgpu_dm_initialize_dp_connector(dm, aconnector, link->link_index); + +out_free: + if (res) { + kfree(i2c); + aconnector->i2c = NULL; + } + return res; +} + +static int dm_force_atomic_commit(struct drm_connector *connector) +{ + int ret = 0; + struct drm_device *ddev = connector->dev; + struct drm_atomic_commit *state = drm_atomic_commit_alloc(ddev); + struct amdgpu_crtc *disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); + struct drm_plane *plane = disconnected_acrtc->base.primary; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_plane_state *plane_state; + + if (!state) + return -ENOMEM; + + state->acquire_ctx = ddev->mode_config.acquire_ctx; + + /* Construct an atomic state to restore previous display setting */ + + /* + * Attach connectors to drm_atomic_commit + */ + conn_state = drm_atomic_get_connector_state(state, connector); + + /* Check for error in getting connector state */ + if (IS_ERR(conn_state)) { + ret = PTR_ERR(conn_state); + goto out; + } + + /* Attach crtc to drm_atomic_commit*/ + crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base); + + /* Check for error in getting crtc state */ + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto out; + } + + /* force a restore */ + crtc_state->mode_changed = true; + + /* Attach plane to drm_atomic_commit */ + plane_state = drm_atomic_get_plane_state(state, plane); + + /* Check for error in getting plane state */ + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto out; + } + + /* Call commit internally with the state we just constructed */ + ret = drm_atomic_commit(state); + +out: + drm_atomic_commit_put(state); + if (ret) + drm_err(ddev, "Restoring old state failed with %i\n", ret); + + return ret; +} + +/* + * This function handles all cases when set mode does not come upon hotplug. + * This includes when a display is unplugged then plugged back into the + * same port and when running without usermode desktop manager support + */ +void dm_restore_drm_connector_state(struct drm_device *dev, + struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_crtc *disconnected_acrtc; + struct dm_crtc_state *acrtc_state; + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + return; + + aconnector = to_amdgpu_dm_connector(connector); + + if (!aconnector->dc_sink || !connector->state || !connector->encoder) + return; + + disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); + if (!disconnected_acrtc) + return; + + acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state); + if (!acrtc_state->stream) + return; + + /* + * If the previous sink is not released and different from the current, + * we deduce we are in a state where we can not rely on usermode call + * to turn on the display, so we do it here + */ + if (acrtc_state->stream->sink != aconnector->dc_sink) + dm_force_atomic_commit(&aconnector->base); +} + +static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, + unsigned int offset, + unsigned int total_length, + u8 *data, + unsigned int length, + struct amdgpu_hdmi_vsdb_info *vsdb) +{ + bool res; + union dmub_rb_cmd cmd; + struct dmub_cmd_send_edid_cea *input; + struct dmub_cmd_edid_cea_output *output; + + if (length > DMUB_EDID_CEA_DATA_CHUNK_BYTES) + return false; + + memset(&cmd, 0, sizeof(cmd)); + + input = &cmd.edid_cea.data.input; + + cmd.edid_cea.header.type = DMUB_CMD__EDID_CEA; + cmd.edid_cea.header.sub_type = 0; + cmd.edid_cea.header.payload_bytes = + sizeof(cmd.edid_cea) - sizeof(cmd.edid_cea.header); + input->offset = offset; + input->length = length; + input->cea_total_length = total_length; + memcpy(input->payload, data, length); + + res = dc_wake_and_execute_dmub_cmd(dm->dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY); + if (!res) { + drm_err(adev_to_drm(dm->adev), "EDID CEA parser failed\n"); + return false; + } + + output = &cmd.edid_cea.data.output; + + if (output->type == DMUB_CMD__EDID_CEA_ACK) { + if (!output->ack.success) { + drm_err(adev_to_drm(dm->adev), "EDID CEA ack failed at offset %d\n", + output->ack.offset); + } + } else if (output->type == DMUB_CMD__EDID_CEA_AMD_VSDB) { + if (!output->amd_vsdb.vsdb_found) + return false; + + vsdb->freesync_supported = output->amd_vsdb.freesync_supported; + vsdb->amd_vsdb_version = output->amd_vsdb.amd_vsdb_version; + vsdb->min_refresh_rate_hz = output->amd_vsdb.min_frame_rate; + vsdb->max_refresh_rate_hz = output->amd_vsdb.max_frame_rate; + vsdb->freesync_mccs_vcp_code = output->amd_vsdb.freesync_mccs_vcp_code; + } else { + drm_warn(adev_to_drm(dm->adev), "Unknown EDID CEA parser results\n"); + return false; + } + + return true; +} + +static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, + u8 *edid_ext, int len, + struct amdgpu_hdmi_vsdb_info *vsdb_info) +{ + int i; + + /* send extension block to DMCU for parsing */ + for (i = 0; i < len; i += 8) { + bool res; + int offset; + + /* send 8 bytes a time */ + if (!dc_edid_parser_send_cea(dm->dc, i, len, &edid_ext[i], 8)) + return false; + + if (i+8 == len) { + /* EDID block sent completed, expect result */ + int version, min_rate, max_rate; + + res = dc_edid_parser_recv_amd_vsdb(dm->dc, &version, &min_rate, &max_rate); + if (res) { + /* amd vsdb found */ + vsdb_info->freesync_supported = 1; + vsdb_info->amd_vsdb_version = version; + vsdb_info->min_refresh_rate_hz = min_rate; + vsdb_info->max_refresh_rate_hz = max_rate; + /* Not enabled on DMCU*/ + vsdb_info->freesync_mccs_vcp_code = 0; + return true; + } + /* not amd vsdb */ + return false; + } + + /* check for ack*/ + res = dc_edid_parser_recv_cea_ack(dm->dc, &offset); + if (!res) + return false; + } + + return false; +} + +static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, + u8 *edid_ext, int len, + struct amdgpu_hdmi_vsdb_info *vsdb_info) +{ + int i; + + /* send extension block to DMCU for parsing */ + for (i = 0; i < len; i += 8) { + /* send 8 bytes a time */ + if (!dm_edid_parser_send_cea(dm, i, len, &edid_ext[i], 8, vsdb_info)) + return false; + } + + return vsdb_info->freesync_supported; +} + +static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, + u8 *edid_ext, int len, + struct amdgpu_hdmi_vsdb_info *vsdb_info) +{ + struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); + bool ret; + + mutex_lock(&adev->dm.dc_lock); + if (adev->dm.dmub_srv) + ret = parse_edid_cea_dmub(&adev->dm, edid_ext, len, vsdb_info); + else + ret = parse_edid_cea_dmcu(&adev->dm, edid_ext, len, vsdb_info); + mutex_unlock(&adev->dm.dc_lock); + return ret; +} + +static void parse_edid_displayid_vrr(struct drm_connector *connector, + const struct edid *edid) +{ + u8 *edid_ext = NULL; + int i; + int j = 0; + u16 min_vfreq; + u16 max_vfreq; + + if (!edid || !edid->extensions) + return; + + /* Find DisplayID extension */ + for (i = 0; i < edid->extensions; i++) { + edid_ext = (void *)(edid + (i + 1)); + if (edid_ext[0] == DISPLAYID_EXT) + break; + } + + if (i == edid->extensions) + return; + + while (j < EDID_LENGTH) { + /* Get dynamic video timing range from DisplayID if available */ + if (EDID_LENGTH - j > 13 && edid_ext[j] == 0x25 && + (edid_ext[j+1] & 0xFE) == 0 && (edid_ext[j+2] == 9)) { + min_vfreq = edid_ext[j+9]; + if (edid_ext[j+1] & 7) + max_vfreq = edid_ext[j+10] + ((edid_ext[j+11] & 3) << 8); + else + max_vfreq = edid_ext[j+10]; + + if (max_vfreq && min_vfreq) { + connector->display_info.monitor_range.max_vfreq = max_vfreq; + connector->display_info.monitor_range.min_vfreq = min_vfreq; + + return; + } + } + j++; + } +} + +static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, + struct amdgpu_hdmi_vsdb_info *vsdb_info) +{ + struct drm_connector *connector = &aconnector->base; + + vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode; + vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version; + + return connector->display_info.amd_vsdb.version != 0; +} + +static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, + const struct edid *edid, + struct amdgpu_hdmi_vsdb_info *vsdb_info) +{ + u8 *edid_ext = NULL; + int i; + bool valid_vsdb_found = false; + + /*----- drm_find_cea_extension() -----*/ + /* No EDID or EDID extensions */ + if (edid == NULL || edid->extensions == 0) + return -ENODEV; + + /* Find CEA extension */ + for (i = 0; i < edid->extensions; i++) { + edid_ext = (uint8_t *)edid + EDID_LENGTH * (i + 1); + if (edid_ext[0] == CEA_EXT) + break; + } + + if (i == edid->extensions) + return -ENODEV; + + /*----- cea_db_offsets() -----*/ + if (edid_ext[0] != CEA_EXT) + return -ENODEV; + + valid_vsdb_found = parse_edid_cea(aconnector, edid_ext, EDID_LENGTH, vsdb_info); + + return valid_vsdb_found ? i : -ENODEV; +} + +/** + * amdgpu_dm_update_freesync_caps - Update Freesync capabilities + * + * @connector: Connector to query. + * @drm_edid: DRM EDID from monitor + * @do_mccs: Controls whether MCCS (Monitor Control Command Set) over + * DDC (Display Data Channel) transactions are performed. When true, + * the driver queries the monitor to get or update additional FreeSync + * capability information. When false, these transactions are skipped. + * + * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep + * track of some of the display information in the internal data struct used by + * amdgpu_dm. This function checks which type of connector we need to set the + * FreeSync parameters. + */ +void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + const struct drm_edid *drm_edid, bool do_mccs) +{ + int i = 0; + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + struct dm_connector_state *dm_con_state = NULL; + struct dc_sink *sink; + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + const struct edid *edid; + bool freesync_capable = false; + enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; + + if (!connector->state) { + drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__); + goto update; + } + + sink = amdgpu_dm_connector->dc_sink ? + amdgpu_dm_connector->dc_sink : + amdgpu_dm_connector->dc_em_sink; + + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid || !sink) { + dm_con_state = to_dm_connector_state(connector->state); + + amdgpu_dm_connector->min_vfreq = 0; + amdgpu_dm_connector->max_vfreq = 0; + freesync_capable = false; + + goto update; + } + + dm_con_state = to_dm_connector_state(connector->state); + + if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) + goto update; + + /* FIXME: Get rid of drm_edid_raw() */ + edid = drm_edid_raw(drm_edid); + + /* Some eDP panels only have the refresh rate range info in DisplayID */ + if ((connector->display_info.monitor_range.min_vfreq == 0 || + connector->display_info.monitor_range.max_vfreq == 0)) + parse_edid_displayid_vrr(connector, edid); + + if (edid && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || + sink->sink_signal == SIGNAL_TYPE_EDP)) { + if (amdgpu_dm_connector->dc_link && + amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param) { + amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq; + amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq; + if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + freesync_capable = true; + } + + get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); + + if (vsdb_info.replay_mode) { + amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_info.replay_mode; + amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_info.amd_vsdb_version; + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; + } + + } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; + sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; + + if (vsdb_info.freesync_supported) { + amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; + amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; + if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + freesync_capable = true; + + connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; + connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; + } + } + } + + if (amdgpu_dm_connector->dc_link) + as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link); + + if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; + sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; + + if (vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) { + amdgpu_dm_connector->pack_sdp_v1_3 = true; + amdgpu_dm_connector->as_type = as_type; + + amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; + amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; + if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + freesync_capable = true; + + connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; + connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; + } + } + } + + /* Handle MCCS */ + if (do_mccs) + dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + + if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || + as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && + (!sink->edid_caps.freesync_vcp_code || + (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported))) + freesync_capable = false; + + if (do_mccs && sink->mccs_caps.freesync_supported && freesync_capable) + dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + +update: + if (dm_con_state) + dm_con_state->freesync_capable = freesync_capable; + + if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; + amdgpu_dm_connector->dc_link->replay_settings.replay_feature_enabled = false; + } + + if (connector->vrr_capable_property) + drm_connector_set_vrr_capable_property(connector, + freesync_capable); +} 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 new file mode 100644 index 000000000000..db8e5588dbfd --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -0,0 +1,147 @@ +/* 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_CONNECTOR_H__ +#define __AMDGPU_DM_CONNECTOR_H__ + +struct amdgpu_device; +struct amdgpu_dm_connector; +struct amdgpu_display_manager; +struct amdgpu_encoder; +struct amdgpu_i2c_adapter; +struct dc_crtc_timing; +struct dc_link; +struct dc_state; +struct dc_stream_state; +struct ddc_service; +struct dm_connector_state; +struct drm_atomic_commit; +struct drm_device; +struct drm_encoder_helper_funcs; +struct drm_connector; +struct drm_connector_state; +struct drm_crtc; +struct drm_device; +struct drm_display_mode; +struct drm_edid; +struct drm_property; + +void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector); + +struct drm_connector_state * +amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector); + +int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *connector_state, + struct drm_property *property, + uint64_t val); + +int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val); + +void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector, + int connector_type, + struct dc_link *link, + int link_index); + +enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode); + +void dm_restore_drm_connector_state(struct drm_device *dev, + struct drm_connector *connector); + +void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + const struct drm_edid *drm_edid, + bool do_mccs); + +void amdgpu_dm_update_connector_after_detect( + struct amdgpu_dm_connector *aconnector); + +void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector); +int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); + +struct drm_connector * +amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, + struct drm_crtc *crtc); + +int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth); + +struct dc_stream_state * +amdgpu_dm_create_validate_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 amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *amdgpu_dm_connector, + u32 link_index, + struct amdgpu_encoder *amdgpu_encoder); + +void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend); + +int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev); + +void amdgpu_set_panel_orientation(struct drm_connector *connector); + +enum dc_color_depth +amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector, + bool is_y420, int requested_bpc); + +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); + +bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, + struct amdgpu_dm_connector *aconnector); + +int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, + struct dc_info_packet *out); + +enum dc_color_space +amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state); + +struct drm_display_mode * +amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, + bool use_probed_modes); + +struct amdgpu_i2c_adapter * +amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem); + +#define DDC_MANUFACTURERNAME_SAMSUNG 0x2D4C + +/* Encoder functions */ +extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs; +int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev); +int amdgpu_dm_encoder_init(struct drm_device *dev, + struct amdgpu_encoder *aencoder, + uint32_t link_index); + +#endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 7db38ad3f848..3bcf3ff30aee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -2971,7 +2971,7 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf, ret = amdgpu_dm_initialize_hdmi_connector(aconnector); if (ret) return ret; - hdmi_cec_set_edid(aconnector); + amdgpu_dm_hdmi_cec_set_edid(aconnector); } else { if (!aconnector->notifier) return -EINVAL; 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 ff3afeb0ec07..9e1916f8f99b 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 @@ -1784,7 +1784,7 @@ int pre_validate_dsc(struct drm_atomic_commit *state, dm_old_crtc_state = to_dm_crtc_state(state->crtcs[ind].old_state); local_dc_state->streams[i] = - create_validate_stream_for_sink(connector, + amdgpu_dm_create_validate_stream_for_sink(connector, &state->crtcs[ind].new_state->mode, dm_new_conn_state, dm_old_crtc_state->stream); -- cgit v1.2.3 From 22087efb7846230b7c7456b3a7630deb3325aa8b Mon Sep 17 00:00:00 2001 From: Robin Chen Date: Sun, 31 May 2026 16:55:26 +0800 Subject: drm/amd/display: Add PSR Active VTotal Control capability [WHY] The PSRSU-RC capability should be populated in DC during edp detection. Reviewed-by: Aric Cyr Signed-off-by: Robin Chen Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc_dp_types.h | 1 + drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c | 6 +++++- drivers/gpu/drm/amd/display/include/ddc_service_types.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc_dp_types.h b/drivers/gpu/drm/amd/display/dc/dc_dp_types.h index fbef0dc743ff..d0ba9ad67a3e 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dp_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_dp_types.h @@ -1146,6 +1146,7 @@ struct edp_psr_info { union edp_psr_dpcd_caps psr_dpcd_caps; uint8_t psr2_su_y_granularity_cap; uint8_t force_psrsu_cap; + uint8_t psr_active_vtotal_control_cap; }; struct replay_info { diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index 47abb4066709..d47aefecfc2d 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -2242,10 +2242,14 @@ void detect_edp_sink_caps(struct dc_link *link) /* * ALPM is only valid for eDP v1.4 or higher. */ - if (link->dpcd_caps.dpcd_rev.raw >= DP_EDP_14) + if (link->dpcd_caps.dpcd_rev.raw >= DP_EDP_14) { core_link_read_dpcd(link, DP_RECEIVER_ALPM_CAP, &link->dpcd_caps.alpm_caps.raw, sizeof(link->dpcd_caps.alpm_caps.raw)); + core_link_read_dpcd(link, DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_CAP, + &link->dpcd_caps.psr_info.psr_active_vtotal_control_cap, + sizeof(link->dpcd_caps.psr_info.psr_active_vtotal_control_cap)); + } /* * Read REPLAY info diff --git a/drivers/gpu/drm/amd/display/include/ddc_service_types.h b/drivers/gpu/drm/amd/display/include/ddc_service_types.h index 53210e3aa0e0..827e9bd7c5cf 100644 --- a/drivers/gpu/drm/amd/display/include/ddc_service_types.h +++ b/drivers/gpu/drm/amd/display/include/ddc_service_types.h @@ -45,6 +45,7 @@ #define DP_DEVICE_ID_BA4159 0xBA4159 #define DP_FORCE_PSRSU_CAPABILITY 0x40F +#define DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_CAP 0x370 #define DP_SINK_PSR_ACTIVE_VTOTAL 0x373 #define DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_MODE 0x375 #define DP_SOURCE_PSR_ACTIVE_VTOTAL 0x376 -- cgit v1.2.3 From d2184b1ba1be247d1c4060a69f0c0c3628c87ff4 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Tue, 2 Jun 2026 11:38:35 -0400 Subject: drm/amd/display: Enable pstate for DCN4 non-emulation builds [Why] Pstate was disabled during bring-up to avoid interference. Now that bring-up is complete it can be enabled for non-emulation builds. [How] Set pstate_enabled to true in debug_defaults_drv for non-emulation DCN4 builds. Reviewed-by: Matthew Stewart Signed-off-by: Gabe Teeger Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 527d17f29f3b..669bd5eb4c8f 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 @@ -757,7 +757,7 @@ static const struct dc_debug_options debug_defaults_drv = { .underflow_assert_delay_us = 0xFFFFFFFF, .dwb_fi_phase = -1, // -1 = disable, .dmub_command_table = true, - .pstate_enabled = false, + .pstate_enabled = true, .enable_mem_low_power = { .bits = { .vga = false, -- cgit v1.2.3 From c1199393ec559071d2afb82399abf4d1a0698c53 Mon Sep 17 00:00:00 2001 From: Rafal Ostrowski Date: Wed, 20 May 2026 10:44:17 +0200 Subject: drm/amd/display: Refactor surface_update_flags to flat struct with helpers [Why] The union surface_update_flags type uses a union with a raw uint32_t member to allow bulk clear/set/test operations on the bitfield. This couples the struct layout to a specific integer width, breaks when the number of flag bits exceeds 32, and scatters raw-access patterns across many call sites. Replacing the union with a plain struct and adding explicit helper functions makes the intent clearer and prepares the code for future flag-set expansion. [How] Rename union surface_update_flags to struct pipe_update_bits and remove the union wrapper, the .bits sub-struct, and the .raw member. Add inline helpers in dc.h: surface_update_flags_clear(), surface_update_flags_set_full(), and surface_update_flags_is_any_set() that operate on the new struct via memset/memcmp. Add stream_update_flags_clear() and stream_update_flags_set_full() in dc_stream.h for the stream update flags union. Update all callers: change the type name, replace .bits.field with .field, replace .raw = 0 with the clear helper, replace .raw = 0xFFFFFFFF with the set_full helper, and replace .raw boolean tests with is_any_set. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Rafal Ostrowski Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +- drivers/gpu/drm/amd/display/dc/core/dc.c | 157 +++++++++++---------- .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 20 +-- drivers/gpu/drm/amd/display/dc/dc.h | 153 ++++++++++++++------ drivers/gpu/drm/amd/display/dc/dc_stream.h | 29 ++++ .../gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c | 2 +- .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 +- .../drm/amd/display/dc/hwss/dce60/dce60_hwseq.c | 8 +- .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 62 ++++---- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 56 ++++---- .../drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c | 2 +- .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 4 +- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 72 +++++----- .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 2 +- 14 files changed, 345 insertions(+), 235 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 f3833e038e99..68ec8f3264c8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -2015,8 +2015,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) for (i = 0; i < dc_state->stream_count; i++) { dc_state->streams[i]->mode_changed = true; for (j = 0; j < dc_state->stream_status[i].plane_count; j++) { - dc_state->stream_status[i].plane_states[j]->update_flags.raw - = 0xffffffff; + dc_pipe_update_bits_set_full(&dc_state->stream_status[i].plane_states[j]->update_bits); } } @@ -6321,7 +6320,7 @@ static int dm_update_plane_state(struct dc *dc, /* Tell DC to do a full surface update every time there * is a plane change. Inefficient, but works for now. */ - dm_new_plane_state->dc_state->update_flags.bits.full_update = 1; + dm_new_plane_state->dc_state->update_bits.full_update = 1; *lock_and_validation_needed = true; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 4220481d3960..0e3c27d526c3 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -2310,7 +2310,7 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c for (i = 0; i < context->stream_count; i++) { uint32_t prev_dsc_changed = context->streams[i]->update_flags.bits.dsc_changed; - context->streams[i]->update_flags.raw = 0xFFFFFFFF; + stream_update_flags_set_full(&context->streams[i]->update_flags); context->streams[i]->update_flags.bits.dsc_changed = prev_dsc_changed; } @@ -2416,7 +2416,7 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c /* Clear update flags that were set earlier to avoid redundant programming */ for (i = 0; i < context->stream_count; i++) { - context->streams[i]->update_flags.raw = 0x0; + stream_update_flags_clear(&context->streams[i]->update_flags); } old_state = dc->current_state; @@ -2764,7 +2764,7 @@ static bool is_surface_in_context( static struct surface_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u) { - union surface_update_flags *update_flags = &u->surface->update_flags; + struct pipe_update_bits *update_bits = &u->surface->update_bits; struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->plane_info) @@ -2774,37 +2774,37 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct elevate_update_type(&update_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); if (u->plane_info->color_space != u->surface->color_space) { - update_flags->bits.color_space_change = 1; + update_bits->color_space_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->plane_info->horizontal_mirror != u->surface->horizontal_mirror) { - update_flags->bits.horizontal_mirror_change = 1; + update_bits->horizontal_mirror_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->plane_info->rotation != u->surface->rotation) { - update_flags->bits.rotation_change = 1; + update_bits->rotation_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->format != u->surface->format) { - update_flags->bits.pixel_format_change = 1; + update_bits->pixel_format_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->stereo_format != u->surface->stereo_format) { - update_flags->bits.stereo_format_change = 1; + update_bits->stereo_format_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->per_pixel_alpha != u->surface->per_pixel_alpha) { - update_flags->bits.per_pixel_alpha_change = 1; + update_bits->per_pixel_alpha_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->plane_info->global_alpha_value != u->surface->global_alpha_value) { - update_flags->bits.global_alpha_change = 1; + update_bits->global_alpha_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } @@ -2816,7 +2816,7 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct * stutter period calculation. Triggering a full update will * recalculate stutter period. */ - update_flags->bits.dcc_change = 1; + update_bits->dcc_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } @@ -2825,25 +2825,25 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct /* different bytes per element will require full bandwidth * and DML calculation */ - update_flags->bits.bpp_change = 1; + update_bits->bpp_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->plane_size.surface_pitch != u->surface->plane_size.surface_pitch || u->plane_info->plane_size.chroma_pitch != u->surface->plane_size.chroma_pitch) { - update_flags->bits.plane_size_change = 1; + update_bits->plane_size_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } const struct dc_tiling_info *tiling = &u->plane_info->tiling_info; if (memcmp(tiling, &u->surface->tiling_info, sizeof(*tiling)) != 0) { - update_flags->bits.swizzle_change = 1; + update_bits->swizzle_change = 1; if (tiling->flags.avoid_full_update_on_tiling_change) { elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } else { - update_flags->bits.bandwidth_change = 1; + update_bits->bandwidth_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } } @@ -2853,10 +2853,10 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct } static struct surface_update_descriptor get_scaling_info_update_type( - const struct dc_check_config *check_config, - const struct dc_surface_update *u) + const struct dc_check_config *check_config, + const struct dc_surface_update *u) { - union surface_update_flags *update_flags = &u->surface->update_flags; + struct pipe_update_bits *update_bits = &u->surface->update_bits; struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->scaling_info) @@ -2873,26 +2873,26 @@ static struct surface_update_descriptor get_scaling_info_update_type( || u->scaling_info->clip_rect.height != u->surface->clip_rect.height || u->scaling_info->scaling_quality.integer_scaling != u->surface->scaling_quality.integer_scaling) { - update_flags->bits.scaling_change = 1; + update_bits->scaling_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); if (u->scaling_info->src_rect.width > u->surface->src_rect.width || u->scaling_info->src_rect.height > u->surface->src_rect.height) /* Making src rect bigger requires a bandwidth change */ - update_flags->bits.clock_change = 1; + update_bits->clock_change = 1; if ((u->scaling_info->dst_rect.width < u->surface->dst_rect.width || u->scaling_info->dst_rect.height < u->surface->dst_rect.height) && (u->scaling_info->dst_rect.width < u->surface->src_rect.width || u->scaling_info->dst_rect.height < u->surface->src_rect.height)) /* Making dst rect smaller requires a bandwidth change */ - update_flags->bits.bandwidth_change = 1; + update_bits->bandwidth_change = 1; if (u->scaling_info->src_rect.width > (int)check_config->max_optimizable_video_width && (u->scaling_info->clip_rect.width > u->surface->clip_rect.width || u->scaling_info->clip_rect.height > u->surface->clip_rect.height)) /* Changing clip size of a large surface may result in MPC slice count change */ - update_flags->bits.bandwidth_change = 1; + update_bits->bandwidth_change = 1; } if (u->scaling_info->src_rect.x != u->surface->src_rect.x @@ -2902,7 +2902,7 @@ static struct surface_update_descriptor get_scaling_info_update_type( || u->scaling_info->dst_rect.x != u->surface->dst_rect.x || u->scaling_info->dst_rect.y != u->surface->dst_rect.y) { elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); - update_flags->bits.position_change = 1; + update_bits->position_change = 1; } return update_type; @@ -2913,15 +2913,15 @@ static struct surface_update_descriptor det_surface_update( struct dc_surface_update *u) { struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; - union surface_update_flags *update_flags = &u->surface->update_flags; + struct pipe_update_bits *update_bits = &u->surface->update_bits; if (u->surface->force_full_update) { - update_flags->raw = 0xFFFFFFFF; + dc_pipe_update_bits_set_full(update_bits); elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); return overall_type; } - update_flags->raw = 0; // Reset all flags + dc_pipe_update_bits_clear(update_bits); struct surface_update_descriptor inner_type = get_plane_info_update_type(u); @@ -2931,47 +2931,47 @@ static struct surface_update_descriptor det_surface_update( elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor); if (u->flip_addr) { - update_flags->bits.addr_update = 1; + update_bits->addr_update = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); if (u->flip_addr->address.tmz_surface != u->surface->address.tmz_surface) { - update_flags->bits.tmz_changed = 1; + update_bits->tmz_changed = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } } if (u->in_transfer_func) { - update_flags->bits.in_transfer_func_change = 1; + update_bits->in_transfer_func_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->input_csc_color_matrix) { - update_flags->bits.input_csc_change = 1; + update_bits->input_csc_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->cursor_csc_color_matrix) { - update_flags->bits.cursor_csc_color_matrix_change = 1; + update_bits->cursor_csc_color_matrix_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->coeff_reduction_factor) { - update_flags->bits.coeff_reduction_change = 1; + update_bits->coeff_reduction_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->gamut_remap_matrix) { - update_flags->bits.gamut_remap_change = 1; + update_bits->gamut_remap_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if ((u->cm && u->cm->flags.bits.blend_enable) || (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) { - update_flags->bits.gamma_change = 1; + update_bits->gamma_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->cm && (u->cm->flags.bits.lut3d_enable || u->cm->flags.bits.shaper_enable)) { - update_flags->bits.lut_3d = 1; + update_bits->lut_3d = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } @@ -2989,19 +2989,19 @@ static struct surface_update_descriptor det_surface_update( if (u->hdr_mult.value) if (u->hdr_mult.value != u->surface->hdr_mult.value) { // TODO: Should be fast? - update_flags->bits.hdr_mult = 1; + update_bits->hdr_mult = 1; elevate_update_type(&overall_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->sdr_white_level_nits) if (u->sdr_white_level_nits != u->surface->sdr_white_level_nits) { // TODO: Should be fast? - update_flags->bits.sdr_white_level_nits = 1; + update_bits->sdr_white_level_nits = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->cm_hist_control) { - update_flags->bits.cm_hist_change = 1; + update_bits->cm_hist_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } @@ -3016,7 +3016,7 @@ static struct surface_update_descriptor det_surface_update( || u->cm->flags.bits.blend_enable != u->surface->cm.flags.bits.blend_enable || u->cm->flags.bits.lut3d_enable != u->surface->cm.flags.bits.lut3d_enable || u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable) { - update_flags->bits.mcm_transfer_function_enable_change = 1; + update_bits->mcm_transfer_function_enable_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } @@ -3026,17 +3026,17 @@ static struct surface_update_descriptor det_surface_update( } } - if (update_flags->bits.lut_3d && + if (update_bits->lut_3d && !u->surface->cm.flags.bits.lut3d_dma_enable) { elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (check_config->enable_legacy_fast_update && - (update_flags->bits.gamma_change || - update_flags->bits.gamut_remap_change || - update_flags->bits.input_csc_change || - update_flags->bits.cm_hist_change || - update_flags->bits.coeff_reduction_change)) { + (update_bits->gamma_change || + update_bits->gamut_remap_change || + update_bits->input_csc_change || + update_bits->cm_hist_change || + update_bits->coeff_reduction_change)) { elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } return overall_type; @@ -3061,7 +3061,7 @@ static void force_immediate_gsl_plane_flip(struct dc *dc, struct dc_surface_upda if (has_flip_immediate_plane && surface_count > 1) { for (i = 0; i < surface_count; i++) { if (updates[i].surface->flip_immediate) - updates[i].surface->update_flags.bits.addr_update = 1; + updates[i].surface->update_bits.addr_update = 1; } } } @@ -3216,9 +3216,9 @@ struct surface_update_descriptor dc_check_update_surfaces_for_stream( struct dc_stream_update *stream_update) { if (stream_update) - stream_update->stream->update_flags.raw = 0; + stream_update_flags_clear(&stream_update->stream->update_flags); for (int i = 0; i < surface_count; i++) - updates[i].surface->update_flags.raw = 0; + dc_pipe_update_bits_clear(&updates[i].surface->update_bits); return check_update_surfaces_for_stream(check_config, updates, surface_count, stream_update); } @@ -3765,11 +3765,11 @@ static bool update_planes_and_stream_state(struct dc *dc, if (update_type == UPDATE_TYPE_FULL) { if (stream_update) { uint32_t dsc_changed = stream_update->stream->update_flags.bits.dsc_changed; - stream_update->stream->update_flags.raw = 0xFFFFFFFF; + stream_update_flags_set_full(&stream_update->stream->update_flags); stream_update->stream->update_flags.bits.dsc_changed = dsc_changed; } for (i = 0; i < surface_count; i++) - srf_updates[i].surface->update_flags.raw = 0xFFFFFFFF; + dc_pipe_update_bits_set_full(&srf_updates[i].surface->update_bits); } if (update_type >= update_surface_trace_level) @@ -3818,7 +3818,7 @@ static bool update_planes_and_stream_state(struct dc *dc, if (update_type != UPDATE_TYPE_MED) continue; - if (surface->update_flags.bits.position_change) { + if (surface->update_bits.position_change) { for (j = 0; j < dc->res_pool->pipe_count; j++) { struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; @@ -4601,14 +4601,23 @@ static void build_dmub_update_dirty_rect( } } -static bool check_address_only_update(union surface_update_flags update_flags) +/** + * dc_check_address_only_update - Check if addr_update is the sole flag set + * + * @update_bits: The pipe update bits to check + * + * Determines whether an update contains only an address change with no other + * pending updates. + * + * Return: %true if addr_update is the sole bit set, %false otherwise. + */ +bool dc_check_address_only_update(struct pipe_update_bits update_bits) { - union surface_update_flags addr_only_update_flags; - addr_only_update_flags.raw = 0; - addr_only_update_flags.bits.addr_update = 1; + struct pipe_update_bits check = update_bits; /* 1. Copy all flags from input */ - return update_flags.bits.addr_update && - !(update_flags.raw & ~addr_only_update_flags.raw); + check.addr_update = 0; /* 2. Zero the addr_update bit in the copy */ + return update_bits.addr_update && /* 3. Check addr_update was set in original */ + !dc_pipe_update_bits_is_any_set(&check); /* 4. Check no other bits remain in the copy */ } /** @@ -4668,7 +4677,7 @@ static void commit_plane_for_stream_offload_fams2_flip(struct dc *dc, continue; /* update pipe context for plane */ - if (pipe_ctx->plane_state->update_flags.bits.addr_update) + if (pipe_ctx->plane_state->update_bits.addr_update) dc->hwss.update_plane_addr(dc, pipe_ctx); } } @@ -4706,8 +4715,8 @@ static void commit_planes_for_stream_fast(struct dc *dc, should_offload_fams2_flip = true; for (i = 0; i < surface_count; i++) { if (srf_updates[i].surface && - srf_updates[i].surface->update_flags.raw && - !check_address_only_update(srf_updates[i].surface->update_flags)) { + dc_pipe_update_bits_is_any_set(&srf_updates[i].surface->update_bits) && + !dc_check_address_only_update(srf_updates[i].surface->update_bits)) { /* more than address update, need to acquire FAMS2 lock */ should_offload_fams2_flip = false; break; @@ -4798,7 +4807,7 @@ static void commit_planes_for_stream_fast(struct dc *dc, * so no need to clear here. */ if (top_pipe_to_program->stream) - top_pipe_to_program->stream->update_flags.raw = 0; + stream_update_flags_clear(&top_pipe_to_program->stream->update_flags); } static void commit_planes_for_stream(struct dc *dc, @@ -5136,7 +5145,7 @@ static void commit_planes_for_stream(struct dc *dc, dc->hwss.program_triplebuffer( dc, pipe_ctx, pipe_ctx->plane_state->triplebuffer_flips); } - if (pipe_ctx->plane_state->update_flags.bits.addr_update) + if (pipe_ctx->plane_state->update_bits.addr_update) dc->hwss.update_plane_addr(dc, pipe_ctx); } } @@ -5227,7 +5236,7 @@ static void commit_planes_for_stream(struct dc *dc, if (pipe_ctx->bottom_pipe || pipe_ctx->next_odm_pipe || !pipe_ctx->stream || !should_update_pipe_for_stream(context, pipe_ctx, stream) || - !pipe_ctx->plane_state->update_flags.bits.addr_update || + !pipe_ctx->plane_state->update_bits.addr_update || pipe_ctx->plane_state->skip_manual_trigger) continue; @@ -5666,7 +5675,7 @@ static bool commit_minimal_transition_state(struct dc *dc, /* force full surface update */ for (i = 0; i < dc->current_state->stream_count; i++) { for (j = 0; j < (unsigned int)dc->current_state->stream_status[i].plane_count; j++) { - dc->current_state->stream_status[i].plane_states[j]->update_flags.raw = 0xFFFFFFFF; + dc_pipe_update_bits_set_full(&dc->current_state->stream_status[i].plane_states[j]->update_bits); } } @@ -6107,17 +6116,17 @@ static bool update_planes_and_stream_v3(struct dc *dc, return true; } -static void clear_update_flags(struct dc_surface_update *srf_updates, +static void clear_update_bits(struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream) { int i; if (stream) - stream->update_flags.raw = 0; + stream_update_flags_clear(&stream->update_flags); for (i = 0; i < surface_count; i++) if (srf_updates[i].surface) - srf_updates[i].surface->update_flags.raw = 0; + dc_pipe_update_bits_clear(&srf_updates[i].surface->update_bits); } bool dc_update_planes_and_stream(struct dc *dc, @@ -6169,7 +6178,7 @@ void dc_commit_updates_for_stream(struct dc *dc, } if (ret && dc->ctx->dce_version >= DCN_VERSION_3_2) - clear_update_flags(srf_updates, surface_count, stream); + clear_update_bits(srf_updates, surface_count, stream); } uint8_t dc_get_current_stream_count(struct dc *dc) @@ -7919,7 +7928,7 @@ struct dc_update_scratch_space { struct dc_stream_state *stream; struct dc_stream_update *stream_update; bool update_v3; - bool do_clear_update_flags; + bool do_clear_update_bits; enum surface_update_type update_type; struct dc_state *new_context; enum update_v3_flow flow; @@ -7962,8 +7971,8 @@ static bool update_planes_and_stream_cleanup_v2( const struct dc_update_scratch_space *scratch ) { - if (scratch->do_clear_update_flags) - clear_update_flags(scratch->surface_updates, scratch->surface_count, scratch->stream); + if (scratch->do_clear_update_bits) + clear_update_bits(scratch->surface_updates, scratch->surface_count, scratch->stream); return false; } @@ -8217,8 +8226,8 @@ static bool update_planes_and_stream_cleanup_v3( ASSERT(false); } - if (scratch->do_clear_update_flags) - clear_update_flags(scratch->surface_updates, scratch->surface_count, scratch->stream); + if (scratch->do_clear_update_bits) + clear_update_bits(scratch->surface_updates, scratch->surface_count, scratch->stream); return false; } @@ -8241,7 +8250,7 @@ struct dc_update_scratch_space *dc_update_planes_and_stream_init( .stream = stream, .stream_update = stream_update, .update_v3 = version >= DCN_VERSION_4_01 || version == DCN_VERSION_3_2 || version == DCN_VERSION_3_21, - .do_clear_update_flags = version >= DCN_VERSION_1_0, + .do_clear_update_bits = version >= DCN_VERSION_1_0, }; return scratch; 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 88446817a71f..c7c32c0a6b50 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 @@ -1028,20 +1028,20 @@ void hwss_build_fast_sequence(struct dc *dc, current_mpc_pipe = current_pipe; while (current_mpc_pipe) { if (current_mpc_pipe->plane_state) { - if (dc->hwss.set_flip_control_gsl && current_mpc_pipe->plane_state->update_flags.raw) { + if (dc->hwss.set_flip_control_gsl && dc_pipe_update_bits_is_any_set(¤t_mpc_pipe->plane_state->update_bits)) { block_sequence[*num_steps].params.set_flip_control_gsl_params.hubp = current_mpc_pipe->plane_res.hubp; block_sequence[*num_steps].params.set_flip_control_gsl_params.flip_immediate = current_mpc_pipe->plane_state->flip_immediate; block_sequence[*num_steps].func = HUBP_SET_FLIP_CONTROL_GSL; (*num_steps)++; } - if (dc->hwss.program_triplebuffer && dc->debug.enable_tri_buf && current_mpc_pipe->plane_state->update_flags.raw) { + if (dc->hwss.program_triplebuffer && dc->debug.enable_tri_buf && dc_pipe_update_bits_is_any_set(¤t_mpc_pipe->plane_state->update_bits)) { block_sequence[*num_steps].params.program_triplebuffer_params.dc = dc; block_sequence[*num_steps].params.program_triplebuffer_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].params.program_triplebuffer_params.enableTripleBuffer = current_mpc_pipe->plane_state->triplebuffer_flips; block_sequence[*num_steps].func = HUBP_PROGRAM_TRIPLEBUFFER; (*num_steps)++; } - if (dc->hwss.update_plane_addr && current_mpc_pipe->plane_state->update_flags.bits.addr_update) { + if (dc->hwss.update_plane_addr && current_mpc_pipe->plane_state->update_bits.addr_update) { if (resource_is_pipe_type(current_mpc_pipe, OTG_MASTER) && stream_status->mall_stream_config.type == SUBVP_MAIN) { block_sequence[*num_steps].params.subvp_save_surf_addr.dc_dmub_srv = dc->ctx->dmub_srv; @@ -1057,7 +1057,7 @@ void hwss_build_fast_sequence(struct dc *dc, (*num_steps)++; } - if (hws->funcs.set_input_transfer_func && current_mpc_pipe->plane_state->update_flags.bits.gamma_change) { + if (hws->funcs.set_input_transfer_func && current_mpc_pipe->plane_state->update_bits.gamma_change) { block_sequence[*num_steps].params.set_input_transfer_func_params.dc = dc; block_sequence[*num_steps].params.set_input_transfer_func_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].params.set_input_transfer_func_params.plane_state = current_mpc_pipe->plane_state; @@ -1066,23 +1066,23 @@ void hwss_build_fast_sequence(struct dc *dc, } if (dc->hwss.program_gamut_remap && - (current_mpc_pipe->plane_state->update_flags.bits.gamut_remap_change || + (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; block_sequence[*num_steps].func = DPP_PROGRAM_GAMUT_REMAP; (*num_steps)++; } - if (current_mpc_pipe->plane_state->update_flags.bits.input_csc_change) { + if (current_mpc_pipe->plane_state->update_bits.input_csc_change) { block_sequence[*num_steps].params.setup_dpp_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].func = DPP_SETUP_DPP; (*num_steps)++; } - if (current_mpc_pipe->plane_state->update_flags.bits.coeff_reduction_change) { + if (current_mpc_pipe->plane_state->update_bits.coeff_reduction_change) { block_sequence[*num_steps].params.program_bias_and_scale_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].func = DPP_PROGRAM_BIAS_AND_SCALE; (*num_steps)++; } - if (current_mpc_pipe->plane_state->update_flags.bits.cm_hist_change) { + if (current_mpc_pipe->plane_state->update_bits.cm_hist_change) { block_sequence[*num_steps].params.control_cm_hist_params.dpp = current_mpc_pipe->plane_res.dpp; block_sequence[*num_steps].params.control_cm_hist_params.cm_hist_control @@ -1095,7 +1095,7 @@ void hwss_build_fast_sequence(struct dc *dc, if (current_mpc_pipe->plane_res.dpp && current_mpc_pipe->plane_res.dpp->funcs->set_cursor_matrix && - current_mpc_pipe->plane_state->update_flags.bits.cursor_csc_color_matrix_change) { + current_mpc_pipe->plane_state->update_bits.cursor_csc_color_matrix_change) { block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.dpp = current_mpc_pipe->plane_res.dpp; block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.color_space = current_mpc_pipe->plane_state->color_space; block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.cursor_csc_color_matrix = ¤t_mpc_pipe->plane_state->cursor_csc_color_matrix; @@ -1176,7 +1176,7 @@ void hwss_build_fast_sequence(struct dc *dc, while (current_mpc_pipe) { if (!current_mpc_pipe->bottom_pipe && !current_mpc_pipe->next_odm_pipe && current_mpc_pipe->stream && current_mpc_pipe->plane_state && - current_mpc_pipe->plane_state->update_flags.bits.addr_update && + current_mpc_pipe->plane_state->update_bits.addr_update && !current_mpc_pipe->plane_state->skip_manual_trigger) { if (dc->hwss.program_cursor_offload_now) { block_sequence[*num_steps].params.program_cursor_update_now_params.dc = dc; diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 2a47d7ddf53b..2202c8669bf8 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1540,47 +1540,120 @@ struct dc_plane_status { struct cm_hist cm_hist; }; -union surface_update_flags { - - struct { - uint32_t addr_update:1; - /* Medium updates */ - uint32_t dcc_change:1; - uint32_t color_space_change:1; - uint32_t horizontal_mirror_change:1; - uint32_t per_pixel_alpha_change:1; - uint32_t global_alpha_change:1; - uint32_t hdr_mult:1; - uint32_t rotation_change:1; - uint32_t swizzle_change:1; - uint32_t scaling_change:1; - uint32_t position_change:1; - uint32_t in_transfer_func_change:1; - uint32_t input_csc_change:1; - uint32_t coeff_reduction_change:1; - uint32_t pixel_format_change:1; - uint32_t plane_size_change:1; - uint32_t gamut_remap_change:1; - uint32_t cursor_csc_color_matrix_change:1; - - /* Full updates */ - uint32_t new_plane:1; - uint32_t bpp_change:1; - uint32_t gamma_change:1; - uint32_t bandwidth_change:1; - uint32_t clock_change:1; - uint32_t stereo_format_change:1; - uint32_t lut_3d:1; - uint32_t tmz_changed:1; - uint32_t mcm_transfer_function_enable_change:1; /* disable or enable MCM transfer func */ - uint32_t full_update:1; - uint32_t sdr_white_level_nits:1; - uint32_t cm_hist_change:1; - } bits; - - uint32_t raw; +struct pipe_update_bits { + uint32_t addr_update:1; + uint32_t dcc_change:1; + uint32_t color_space_change:1; + uint32_t horizontal_mirror_change:1; + uint32_t per_pixel_alpha_change:1; + uint32_t global_alpha_change:1; + uint32_t hdr_mult:1; + uint32_t rotation_change:1; + uint32_t swizzle_change:1; + uint32_t scaling_change:1; + uint32_t position_change:1; + uint32_t in_transfer_func_change:1; + uint32_t input_csc_change:1; + uint32_t coeff_reduction_change:1; + uint32_t pixel_format_change:1; + uint32_t plane_size_change:1; + uint32_t gamut_remap_change:1; + uint32_t cursor_csc_color_matrix_change:1; + uint32_t new_plane:1; + uint32_t bpp_change:1; + uint32_t gamma_change:1; + uint32_t bandwidth_change:1; + uint32_t clock_change:1; + uint32_t stereo_format_change:1; + uint32_t lut_3d:1; + uint32_t tmz_changed:1; + uint32_t mcm_transfer_function_enable_change:1; /* disable or enable MCM transfer func */ + uint32_t full_update:1; + uint32_t sdr_white_level_nits:1; + uint32_t cm_hist_change:1; + /* NOTE: When adding a new field, also update: + * - dc_pipe_update_bits_set_full() + * - dc_pipe_update_bits_is_any_set() + */ }; +static inline void dc_pipe_update_bits_clear(struct pipe_update_bits *flags) +{ + /* memset ensures padding bits are zeroed */ + memset(flags, 0, sizeof(*flags)); +} + +static inline void dc_pipe_update_bits_set_full(struct pipe_update_bits *flags) +{ + dc_pipe_update_bits_clear(flags); + flags->addr_update = 1; + flags->dcc_change = 1; + flags->color_space_change = 1; + flags->horizontal_mirror_change = 1; + flags->per_pixel_alpha_change = 1; + flags->global_alpha_change = 1; + flags->hdr_mult = 1; + flags->rotation_change = 1; + flags->swizzle_change = 1; + flags->scaling_change = 1; + flags->position_change = 1; + flags->in_transfer_func_change = 1; + flags->input_csc_change = 1; + flags->coeff_reduction_change = 1; + flags->pixel_format_change = 1; + flags->plane_size_change = 1; + flags->gamut_remap_change = 1; + flags->cursor_csc_color_matrix_change = 1; + flags->new_plane = 1; + flags->bpp_change = 1; + flags->gamma_change = 1; + flags->bandwidth_change = 1; + flags->clock_change = 1; + flags->stereo_format_change = 1; + flags->lut_3d = 1; + flags->tmz_changed = 1; + flags->mcm_transfer_function_enable_change = 1; + flags->full_update = 1; + flags->sdr_white_level_nits = 1; + flags->cm_hist_change = 1; +} + +static inline bool dc_pipe_update_bits_is_any_set(const struct pipe_update_bits *flags) +{ + return flags->addr_update || + flags->dcc_change || + flags->color_space_change || + flags->horizontal_mirror_change || + flags->per_pixel_alpha_change || + flags->global_alpha_change || + flags->hdr_mult || + flags->rotation_change || + flags->swizzle_change || + flags->scaling_change || + flags->position_change || + flags->in_transfer_func_change || + flags->input_csc_change || + flags->coeff_reduction_change || + flags->pixel_format_change || + flags->plane_size_change || + flags->gamut_remap_change || + flags->cursor_csc_color_matrix_change || + flags->new_plane || + flags->bpp_change || + flags->gamma_change || + flags->bandwidth_change || + flags->clock_change || + flags->stereo_format_change || + flags->lut_3d || + flags->tmz_changed || + flags->mcm_transfer_function_enable_change || + flags->full_update || + flags->sdr_white_level_nits || + flags->cm_hist_change; +} + +bool dc_check_address_only_update(struct pipe_update_bits update_bits); + #define DC_REMOVE_PLANE_POINTERS 1 struct dc_plane_state { @@ -1637,7 +1710,7 @@ struct dc_plane_state { bool horizontal_mirror; unsigned int layer_index; - union surface_update_flags update_flags; + struct pipe_update_bits update_bits; bool flip_int_enabled; bool skip_manual_trigger; diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h index 4154cd059562..8b164edc9c51 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h @@ -128,6 +128,35 @@ union stream_update_flags { uint32_t raw; }; +static inline void stream_update_flags_clear(union stream_update_flags *flags) +{ + flags->raw = 0; +} + +static inline void stream_update_flags_set_full(union stream_update_flags *flags) +{ + stream_update_flags_clear(flags); + flags->bits.scaling = 1; + flags->bits.out_tf = 1; + flags->bits.out_csc = 1; + flags->bits.abm_level = 1; + flags->bits.dpms_off = 1; + flags->bits.gamut_remap = 1; + flags->bits.wb_update = 1; + flags->bits.dsc_changed = 1; + flags->bits.mst_bw = 1; + flags->bits.crtc_timing_adjust = 1; + flags->bits.fams_changed = 1; + flags->bits.scaler_sharpener = 1; + flags->bits.sharpening_required = 1; + flags->bits.cursor_attr = 1; + flags->bits.cursor_pos = 1; + flags->bits.periodic_interrupt = 1; + flags->bits.info_frame = 1; + flags->bits.dmdata = 1; + flags->bits.dither = 1; +} + struct test_pattern { enum dp_test_pattern type; enum dp_test_pattern_color_space color_space; diff --git a/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c b/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c index dcca23d53261..2ad4a2635683 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c @@ -1237,7 +1237,7 @@ bool dcn_validate_bandwidth( if (pipe->plane_state) { struct pipe_ctx *hsplit_pipe = pipe->bottom_pipe; - pipe->plane_state->update_flags.bits.full_update = 1; + pipe->plane_state->update_bits.full_update = 1; if (v->dpp_per_plane[input_idx] == 2 || ((pipe->stream->view_format == 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 042602c50e35..c9691974bf72 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 @@ -3166,12 +3166,12 @@ static void dce110_program_front_end_for_pipe( plane_state->rotation); /* Moved programming gamma from dc to hwss */ - if (pipe_ctx->plane_state->update_flags.bits.full_update || - pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change) + if (pipe_ctx->plane_state->update_bits.full_update || + pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); DC_LOG_SURFACE( 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 a08e9f9eec17..26aa303b8237 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 @@ -332,12 +332,12 @@ dce60_program_front_end_for_pipe( plane_state->rotation); /* Moved programming gamma from dc to hwss */ - if (pipe_ctx->plane_state->update_flags.bits.full_update || - pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change) + if (pipe_ctx->plane_state->update_bits.full_update || + pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); DC_LOG_SURFACE( 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 7112b71af977..541cd908b341 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 @@ -2981,7 +2981,7 @@ void dcn10_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = hubp->inst; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update) { + if (!pipe_ctx->plane_state->update_bits.full_update) { mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); return; @@ -3041,7 +3041,7 @@ static void dcn10_update_dchubp_dpp( /* If request max dpp clk is lower than current dispclk, no need to * divided by 2 */ - if (plane_state->update_flags.bits.full_update) { + if (plane_state->update_bits.full_update) { /* new calculated dispclk, dppclk are stored in * context->bw_ctx.bw.dcn.clk.dispclk_khz / dppclk_khz. current @@ -3096,7 +3096,7 @@ static void dcn10_update_dchubp_dpp( * VTG is within DCHUBBUB which is commond block share by each pipe HUBP. * VTG is 1:1 mapping with OTG. Each pipe HUBP will select which VTG */ - if (plane_state->update_flags.bits.full_update) { + if (plane_state->update_bits.full_update) { hubp->funcs->hubp_vtg_sel(hubp, pipe_ctx->stream_res.tg->inst); hubp->funcs->hubp_setup( @@ -3113,26 +3113,26 @@ static void dcn10_update_dchubp_dpp( size.surface_size = pipe_ctx->plane_res.scl_data.viewport; - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.bpp_change) + if (plane_state->update_bits.full_update || + plane_state->update_bits.bpp_change) dcn10_update_dpp(dpp, plane_state); - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.per_pixel_alpha_change || - plane_state->update_flags.bits.global_alpha_change) + if (plane_state->update_bits.full_update || + plane_state->update_bits.per_pixel_alpha_change || + plane_state->update_bits.global_alpha_change) hws->funcs.update_mpcc(dc, pipe_ctx); - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.per_pixel_alpha_change || - plane_state->update_flags.bits.global_alpha_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change) { + if (plane_state->update_bits.full_update || + plane_state->update_bits.per_pixel_alpha_change || + plane_state->update_bits.global_alpha_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change) { update_scaler(pipe_ctx); } - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change) { + if (plane_state->update_bits.full_update || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change) { hubp->funcs->mem_program_viewport( hubp, &pipe_ctx->plane_res.scl_data.viewport, @@ -3150,7 +3150,7 @@ static void dcn10_update_dchubp_dpp( dc->hwss.set_cursor_sdr_white_level(pipe_ctx); } - if (plane_state->update_flags.bits.full_update) { + if (plane_state->update_bits.full_update) { /*gamut remap*/ dc->hwss.program_gamut_remap(pipe_ctx); @@ -3161,15 +3161,15 @@ static void dcn10_update_dchubp_dpp( pipe_ctx->stream_res.opp->inst); } - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.pixel_format_change || - plane_state->update_flags.bits.horizontal_mirror_change || - plane_state->update_flags.bits.rotation_change || - plane_state->update_flags.bits.swizzle_change || - plane_state->update_flags.bits.dcc_change || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.plane_size_change) { + if (plane_state->update_bits.full_update || + plane_state->update_bits.pixel_format_change || + plane_state->update_bits.horizontal_mirror_change || + plane_state->update_bits.rotation_change || + plane_state->update_bits.swizzle_change || + plane_state->update_bits.dcc_change || + plane_state->update_bits.bpp_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.plane_size_change) { hubp->funcs->hubp_program_surface_config( hubp, plane_state->format, @@ -3278,16 +3278,16 @@ void dcn10_program_pipe( hws->funcs.blank_pixel_data(dc, pipe_ctx, blank); } - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) dcn10_enable_plane(dc, pipe_ctx, context); dcn10_update_dchubp_dpp(dc, pipe_ctx, context); hws->funcs.set_hdr_multiplier(pipe_ctx); - if (pipe_ctx->plane_state->update_flags.bits.full_update || - pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change) + if (pipe_ctx->plane_state->update_bits.full_update || + pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); /* dcn10_translate_regamma_to_hw_format takes 750us to finish @@ -3296,7 +3296,7 @@ void dcn10_program_pipe( * Always call this for now since it does memcmp inside before * doing heavy calculation and programming */ - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); } 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 50d039b3fb43..95e5b6a6ba0f 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 @@ -1733,10 +1733,10 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.input_csc_change || - plane_state->update_flags.bits.color_space_change || - plane_state->update_flags.bits.coeff_reduction_change) { + plane_state->update_bits.bpp_change || + plane_state->update_bits.input_csc_change || + plane_state->update_bits.color_space_change || + plane_state->update_bits.coeff_reduction_change) { struct dc_bias_and_scale bns_params = plane_state->bias_and_scale; // program the input csc @@ -1760,16 +1760,16 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.mpcc || pipe_ctx->update_flags.bits.plane_changed - || plane_state->update_flags.bits.global_alpha_change - || plane_state->update_flags.bits.per_pixel_alpha_change) { + || plane_state->update_bits.global_alpha_change + || plane_state->update_bits.per_pixel_alpha_change) { // MPCC inst is equal to pipe index in practice hws->funcs.update_mpcc(dc, pipe_ctx); } if (pipe_ctx->update_flags.bits.scaler || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change || - plane_state->update_flags.bits.per_pixel_alpha_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change || + plane_state->update_bits.per_pixel_alpha_change || pipe_ctx->stream->update_flags.bits.scaling) { pipe_ctx->plane_res.scl_data.lb_params.alpha_en = pipe_ctx->plane_state->per_pixel_alpha; ASSERT(pipe_ctx->plane_res.scl_data.lb_params.depth == LB_PIXEL_DEPTH_36BPP); @@ -1779,8 +1779,8 @@ void dcn20_update_dchubp_dpp( } if (pipe_ctx->update_flags.bits.viewport || - (context == dc->current_state && plane_state->update_flags.bits.position_change) || - (context == dc->current_state && plane_state->update_flags.bits.scaling_change) || + (context == dc->current_state && plane_state->update_bits.position_change) || + (context == dc->current_state && plane_state->update_bits.scaling_change) || (context == dc->current_state && pipe_ctx->stream->update_flags.bits.scaling)) { hubp->funcs->mem_program_viewport( @@ -1812,7 +1812,7 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.opp_changed || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.gamut_remap - || plane_state->update_flags.bits.gamut_remap_change + || 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); @@ -1828,14 +1828,14 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->update_flags.bits.opp_changed || - plane_state->update_flags.bits.pixel_format_change || - plane_state->update_flags.bits.horizontal_mirror_change || - plane_state->update_flags.bits.rotation_change || - plane_state->update_flags.bits.swizzle_change || - plane_state->update_flags.bits.dcc_change || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.plane_size_change) { + plane_state->update_bits.pixel_format_change || + plane_state->update_bits.horizontal_mirror_change || + plane_state->update_bits.rotation_change || + plane_state->update_bits.swizzle_change || + plane_state->update_bits.dcc_change || + plane_state->update_bits.bpp_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.plane_size_change) { struct plane_size size = plane_state->plane_size; size.surface_size = pipe_ctx->plane_res.scl_data.viewport; @@ -1853,7 +1853,7 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.addr_update) { + plane_state->update_bits.addr_update) { if (resource_is_pipe_type(pipe_ctx, OTG_MASTER) && pipe_mall_type == SUBVP_MAIN) { union block_sequence_params params; @@ -1969,18 +1969,18 @@ static void dcn20_program_pipe( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw || - pipe_ctx->plane_state->update_flags.raw || + dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) || pipe_ctx->stream->update_flags.raw)) dcn20_update_dchubp_dpp(dc, pipe_ctx, context); if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable || - pipe_ctx->plane_state->update_flags.bits.hdr_mult)) + pipe_ctx->plane_state->update_bits.hdr_mult)) hws->funcs.set_hdr_multiplier(pipe_ctx); if (pipe_ctx->plane_state && - (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change || - pipe_ctx->plane_state->update_flags.bits.lut_3d || + (pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change || + pipe_ctx->plane_state->update_bits.lut_3d || pipe_ctx->update_flags.bits.enable)) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); @@ -2186,7 +2186,7 @@ void dcn20_program_front_end_for_ctx( pipe = &context->res_ctx.pipe_ctx[i]; if (!pipe->top_pipe && !pipe->prev_odm_pipe && pipe->stream && pipe->stream->num_wb_info > 0 - && (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw) + && (pipe->update_flags.raw || (pipe->plane_state && dc_pipe_update_bits_is_any_set(&pipe->plane_state->update_bits)) || pipe->stream->update_flags.raw) && hws->funcs.program_all_writeback_pipes_in_tree) hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context); @@ -2998,7 +2998,7 @@ void dcn20_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = hubp->inst; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update && + if (!pipe_ctx->plane_state->update_bits.full_update && !pipe_ctx->update_flags.bits.mpcc) { mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c index ce18d75fd991..7b820bdae55b 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c @@ -485,7 +485,7 @@ void dcn201_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = dpp_id; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update) { + if (!pipe_ctx->plane_state->update_bits.full_update) { dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); return; 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 34cbd90b2283..1340f673ec3b 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 @@ -1466,7 +1466,7 @@ void dcn32_update_phantom_vp_position(struct dc *dc, if (pipe->stream && dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_MAIN && dc_state_get_paired_subvp_stream(context, pipe->stream) == phantom_pipe->stream) { - if (pipe->plane_state && pipe->plane_state->update_flags.bits.position_change) { + if (pipe->plane_state && pipe->plane_state->update_bits.position_change) { phantom_plane->src_rect.x = pipe->plane_state->src_rect.x; phantom_plane->src_rect.y = pipe->plane_state->src_rect.y; @@ -1474,7 +1474,7 @@ void dcn32_update_phantom_vp_position(struct dc *dc, phantom_plane->dst_rect.x = pipe->plane_state->dst_rect.x; phantom_plane->dst_rect.y = pipe->plane_state->dst_rect.y; - phantom_pipe->plane_state->update_flags.bits.position_change = 1; + phantom_pipe->plane_state->update_bits.position_change = 1; resource_build_scaling_params(phantom_pipe); return; } 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 49efd1f11c9a..9107493cdcda 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 @@ -1421,7 +1421,7 @@ void dcn401_wait_for_dcc_meta_propagation(const struct dc *dc, if (pipe_ctx->plane_state && pipe_ctx->plane_state->dcc.enable && pipe_ctx->plane_state->flip_immediate && - pipe_ctx->plane_state->update_flags.bits.addr_update) { + pipe_ctx->plane_state->update_bits.addr_update) { is_wait_needed = true; break; } @@ -2268,18 +2268,18 @@ void dcn401_program_pipe( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw || - pipe_ctx->plane_state->update_flags.raw || + dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) || pipe_ctx->stream->update_flags.raw)) dc->hwss.update_dchubp_dpp(dc, pipe_ctx, context); if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable || - pipe_ctx->plane_state->update_flags.bits.hdr_mult)) + pipe_ctx->plane_state->update_bits.hdr_mult)) hws->funcs.set_hdr_multiplier(pipe_ctx); if (pipe_ctx->plane_state && - (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change || - pipe_ctx->plane_state->update_flags.bits.lut_3d || + (pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change || + pipe_ctx->plane_state->update_bits.lut_3d || pipe_ctx->update_flags.bits.enable)) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); @@ -2338,7 +2338,7 @@ void dcn401_program_pipe( pipe_ctx->stream_res.test_pattern_params.offset); } if (pipe_ctx->plane_state - && pipe_ctx->plane_state->update_flags.bits.cm_hist_change + && pipe_ctx->plane_state->update_bits.cm_hist_change && hws->funcs.program_cm_hist) hws->funcs.program_cm_hist(dc, pipe_ctx, pipe_ctx->plane_state); } @@ -2419,7 +2419,7 @@ void dcn401_program_pipe_sequence( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw || - pipe_ctx->plane_state->update_flags.raw || + dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) || pipe_ctx->stream->update_flags.raw)) { if (dc->hwss.update_dchubp_dpp_sequence) @@ -2427,15 +2427,15 @@ void dcn401_program_pipe_sequence( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable || - pipe_ctx->plane_state->update_flags.bits.hdr_mult)) { + pipe_ctx->plane_state->update_bits.hdr_mult)) { hws->funcs.set_hdr_multiplier_sequence(pipe_ctx, seq_state); } if (pipe_ctx->plane_state && - (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change || - pipe_ctx->plane_state->update_flags.bits.lut_3d || + (pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change || + pipe_ctx->plane_state->update_bits.lut_3d || pipe_ctx->update_flags.bits.enable)) { hwss_add_dpp_set_input_transfer_func(seq_state, dc, pipe_ctx, pipe_ctx->plane_state); @@ -2493,7 +2493,7 @@ void dcn401_program_pipe_sequence( } if (pipe_ctx->plane_state - && pipe_ctx->plane_state->update_flags.bits.cm_hist_change + && pipe_ctx->plane_state->update_bits.cm_hist_change && hws->funcs.program_cm_hist) { hwss_add_dpp_program_cm_hist(seq_state, pipe_ctx->plane_res.dpp, @@ -2647,7 +2647,7 @@ void dcn401_program_front_end_for_ctx( pipe = &context->res_ctx.pipe_ctx[i]; if (!pipe->top_pipe && !pipe->prev_odm_pipe && pipe->stream && pipe->stream->num_wb_info > 0 - && (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw) + && (pipe->update_flags.raw || (pipe->plane_state && dc_pipe_update_bits_is_any_set(&pipe->plane_state->update_bits)) || pipe->stream->update_flags.raw) && hws->funcs.program_all_writeback_pipes_in_tree) hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context); @@ -3733,10 +3733,10 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 7: DPP setup - input CSC and format setup */ if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.input_csc_change || - plane_state->update_flags.bits.color_space_change || - plane_state->update_flags.bits.coeff_reduction_change) { + plane_state->update_bits.bpp_change || + plane_state->update_bits.input_csc_change || + plane_state->update_bits.color_space_change || + plane_state->update_bits.coeff_reduction_change) { hwss_add_dpp_setup_dpp(seq_state, pipe_ctx); /* Step 8: DPP cursor matrix setup */ @@ -3753,8 +3753,8 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 10: MPCC updates */ if (pipe_ctx->update_flags.bits.mpcc || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.global_alpha_change || - plane_state->update_flags.bits.per_pixel_alpha_change) { + plane_state->update_bits.global_alpha_change || + plane_state->update_bits.per_pixel_alpha_change) { /* Check if update_mpcc_sequence is implemented and prefer it over single MPC_UPDATE_MPCC step */ if (hws->funcs.update_mpcc_sequence) @@ -3763,9 +3763,9 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 11: DPP scaler setup */ if (pipe_ctx->update_flags.bits.scaler || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change || - plane_state->update_flags.bits.per_pixel_alpha_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change || + plane_state->update_bits.per_pixel_alpha_change || pipe_ctx->stream->update_flags.bits.scaling) { pipe_ctx->plane_res.scl_data.lb_params.alpha_en = pipe_ctx->plane_state->per_pixel_alpha; ASSERT(pipe_ctx->plane_res.scl_data.lb_params.depth == LB_PIXEL_DEPTH_36BPP); @@ -3774,8 +3774,8 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 12: HUBP viewport programming */ if (pipe_ctx->update_flags.bits.viewport || - (context == dc->current_state && plane_state->update_flags.bits.position_change) || - (context == dc->current_state && plane_state->update_flags.bits.scaling_change) || + (context == dc->current_state && plane_state->update_bits.position_change) || + (context == dc->current_state && plane_state->update_bits.scaling_change) || (context == dc->current_state && pipe_ctx->stream->update_flags.bits.scaling)) { hwss_add_hubp_mem_program_viewport(seq_state, hubp, &pipe_ctx->plane_res.scl_data.viewport, &pipe_ctx->plane_res.scl_data.viewport_c); @@ -3807,7 +3807,7 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.opp_changed || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.gamut_remap || - plane_state->update_flags.bits.gamut_remap_change || + plane_state->update_bits.gamut_remap_change || pipe_ctx->stream->update_flags.bits.out_csc) { /* Gamut remap */ @@ -3822,14 +3822,14 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->update_flags.bits.opp_changed || - plane_state->update_flags.bits.pixel_format_change || - plane_state->update_flags.bits.horizontal_mirror_change || - plane_state->update_flags.bits.rotation_change || - plane_state->update_flags.bits.swizzle_change || - plane_state->update_flags.bits.dcc_change || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.plane_size_change) { + plane_state->update_bits.pixel_format_change || + plane_state->update_bits.horizontal_mirror_change || + plane_state->update_bits.rotation_change || + plane_state->update_bits.swizzle_change || + plane_state->update_bits.dcc_change || + plane_state->update_bits.bpp_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.plane_size_change) { struct plane_size size = plane_state->plane_size; size.surface_size = pipe_ctx->plane_res.scl_data.viewport; @@ -3843,7 +3843,7 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 19: Update plane address (with SubVP support) */ if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.addr_update) { + plane_state->update_bits.addr_update) { /* SubVP save surface address if needed */ if (resource_is_pipe_type(pipe_ctx, OTG_MASTER) && pipe_mall_type == SUBVP_MAIN) { @@ -3916,7 +3916,7 @@ void dcn401_update_mpcc_sequence(struct dc *dc, mpcc_id = hubp->inst; /* Step 1: Update blending if no full update needed */ - if (!pipe_ctx->plane_state->update_flags.bits.full_update && + if (!pipe_ctx->plane_state->update_bits.full_update && !pipe_ctx->update_flags.bits.mpcc) { /* Update blending configuration */ 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 9cf8b379cb34..f415473517d4 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 @@ -355,7 +355,7 @@ void dcn42_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = hubp->inst; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update && + if (!pipe_ctx->plane_state->update_bits.full_update && !pipe_ctx->update_flags.bits.mpcc) { mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); -- cgit v1.2.3 From fcf4919cd87333f2e67c149cd3eacb1cd0835137 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 3 Jun 2026 13:39:13 -0400 Subject: drm/amd/display: Add Support for HDMI Compliance Automation Add support to get DUT trained at FRL link rate when working with Teledyne M41h compliance automation. Reviewed-by: Alex Hung Signed-off-by: Fangzhi Zuo Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 + .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67 +++++++++++++++++++++- .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 6 ++ 4 files changed, 80 insertions(+), 1 deletion(-) 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 c0144d14b793..eedca412eca0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -877,6 +877,9 @@ struct amdgpu_dm_connector { unsigned int hdmi_hpd_debounce_delay_ms; struct delayed_work hdmi_hpd_debounce_work; struct dc_sink *hdmi_prev_sink; + + /* HDMI compliance automation */ + bool hdmi_comp_auto; }; static inline void amdgpu_dm_set_mst_status(uint8_t *status, 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 f239ce767bff..df09627f4c04 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 @@ -573,6 +573,11 @@ void amdgpu_dm_update_connector_after_detect( amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); amdgpu_dm_update_connector_ext_caps(aconnector); dm_set_panel_type(aconnector); + + if (aconnector->hdmi_comp_auto) { + if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL) + sink->sink_signal = SIGNAL_TYPE_HDMI_FRL; + } } else { hdmi_cec_unset_edid(aconnector); drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 3bcf3ff30aee..2d455359fdb4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf, return size; } +/** + * hdmi_automation_enable - Enable/Disable HDMI automation feature + * @f: file structure. + * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature. + * @size: size of buffer from userpsace. + * @pos: unused. + * + * Return size on success, error code on failure + */ +static ssize_t hdmi_automation_enable(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private; + char *wr_buf = NULL; + const uint32_t wr_buf_size = 40; + int max_param_num = 1; + uint8_t param_nums = 0; + long param[2]; + bool hdmi_comp_auto; + + if (size == 0) + return -EINVAL; + + wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL); + if (!wr_buf) + return -ENOSPC; + + if (parse_write_buffer_into_params(wr_buf, wr_buf_size, + (long *)param, buf, + max_param_num, + ¶m_nums)) { + kfree(wr_buf); + return -EINVAL; + } + + if (param_nums <= 0) { + kfree(wr_buf); + DRM_DEBUG_DRIVER("user data not be read\n"); + return -EINVAL; + } + + switch (param[0]) { + case 0: + hdmi_comp_auto = false; + break; + case 1: + default: + hdmi_comp_auto = true; + break; + } + + /* Persist setting across sink re-detection/hotplug. */ + aconnector->hdmi_comp_auto = hdmi_comp_auto; + + kfree(wr_buf); + return size; +} + DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support); DEFINE_SHOW_ATTRIBUTE(dmub_fw_state); DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer); @@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = { .llseek = default_llseek }; +static const struct file_operations hdmi_automation_debugfs_fops = { + .owner = THIS_MODULE, + .write = hdmi_automation_enable, + .llseek = default_llseek +}; + static const struct { char *name; const struct file_operations *fops; @@ -3131,7 +3195,8 @@ static const struct { const struct file_operations *fops; } hdmi_debugfs_entries[] = { {"hdcp_sink_capability", &hdcp_sink_capability_fops}, - {"hdmi_cec_state", &hdmi_cec_state_fops} + {"hdmi_cec_state", &hdmi_cec_state_fops}, + {"hdmi_automation", &hdmi_automation_debugfs_fops} }; /* 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 c6f94eb71ffa..eef031022be2 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 @@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps( __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \ edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \ edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes); + if (aconnector->hdmi_comp_auto) { + edid_caps->panel_patch.hdmi_comp_auto = true; + link->ctx->dc->debug.force_frl_max = true; + link->ctx->dc->debug.force_frl_dsc = true; + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name); + } } apply_edid_quirks(link, edid_buf, edid_caps); -- cgit v1.2.3 From c26b643aa31b7f4b9ac2d9de856fc263904490d9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 15:46:34 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm Add KUnit tests for pure helper functions in amdgpu_dm.c. Tests cover: - dm_plane_layer_index_cmp(): equal, ascending, and descending layer_index ordering - fill_plane_color_attributes(): RGB plus BT601/BT709/BT2020 full- and limited-range YCbCr, and invalid encoding - modereset_required(): active vs inactive stream states with and without a mode change - dm_get_oriented_plane_size(): 0/90/180/270 degree rotations - dm_get_plane_scale(): identity, rotated identity, and division-by-zero guard - is_scaling_state_different(): identical state, scaling mode change, and underscan enable/border changes - is_timing_unchanged_for_freesync(): NULL args, identical modes, VRR vtotal/vsync shift, and pixel clock change - set_freesync_fixed_config(): fixed refresh-rate computation - is_dc_timing_adjust_needed(): pending hw adjust, VRR active-fixed, VRR active-state toggle, and steady state - set_multisync_trigger_params(): disabled trigger and rising/falling edge selection by vsync polarity - set_master_stream(): highest refresh-rate selection and the default-to-first-stream case Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 42 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 21 + .../display/amdgpu_dm/amdgpu_dm_kunit_helpers.h | 1 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 929 +++++++++++++++++++++ 5 files changed, 979 insertions(+), 15 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c 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 68ec8f3264c8..d23d9d85e567 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_kunit_helpers.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -146,7 +147,7 @@ static 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); -static bool +STATIC_IFN_KUNIT bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); @@ -247,8 +248,8 @@ static int dm_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state) +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; @@ -259,13 +260,14 @@ static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, 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 * by descending zpos. */ -static int dm_plane_layer_index_cmp(const void *a, const void *b) +STATIC_IFN_KUNIT int dm_plane_layer_index_cmp(const void *a, const void *b) { const struct dc_surface_update *sa = (struct dc_surface_update *)a; const struct dc_surface_update *sb = (struct dc_surface_update *)b; @@ -273,6 +275,7 @@ static int dm_plane_layer_index_cmp(const void *a, const void *b) /* Sort by descending dc_plane layer_index (i.e. normalized_zpos) */ return sb->surface->layer_index - sa->surface->layer_index; } +EXPORT_IF_KUNIT(dm_plane_layer_index_cmp); /** * update_planes_and_stream_adapter() - Send planes to be updated in DC @@ -2980,12 +2983,13 @@ static int dm_early_init(struct amdgpu_ip_block *ip_block) return dm_init_microcode(adev); } -static bool modereset_required(struct drm_crtc_state *crtc_state) +STATIC_IFN_KUNIT bool modereset_required(struct drm_crtc_state *crtc_state) { return !crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state); } +EXPORT_IF_KUNIT(modereset_required); -static int +STATIC_IFN_KUNIT int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, enum dc_color_space *color_space) @@ -3032,6 +3036,7 @@ fill_plane_color_attributes(const struct drm_plane_state *plane_state, return 0; } +EXPORT_IF_KUNIT(fill_plane_color_attributes); static int fill_dc_plane_info_and_addr(struct amdgpu_device *adev, @@ -3600,7 +3605,7 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev, amdgpu_irq_update(adev, &adev->pageflip_irq, irq_type); } -static bool +STATIC_IFN_KUNIT bool is_scaling_state_different(const struct dm_connector_state *dm_state, const struct dm_connector_state *old_dm_state) { @@ -3617,6 +3622,7 @@ is_scaling_state_different(const struct dm_connector_state *dm_state, return true; return false; } +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, @@ -5079,7 +5085,7 @@ static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state) return 0; } -static void set_multisync_trigger_params( +STATIC_IFN_KUNIT void set_multisync_trigger_params( struct dc_stream_state *stream) { struct dc_stream_state *master = NULL; @@ -5092,9 +5098,10 @@ static void set_multisync_trigger_params( stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL; } } +EXPORT_IF_KUNIT(set_multisync_trigger_params); -static void set_master_stream(struct dc_stream_state *stream_set[], - int stream_count) +STATIC_IFN_KUNIT void set_master_stream(struct dc_stream_state *stream_set[], + int stream_count) { int j, highest_rfr = 0, master_stream = 0; @@ -5115,6 +5122,7 @@ static void set_master_stream(struct dc_stream_state *stream_set[], stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream]; } } +EXPORT_IF_KUNIT(set_master_stream); static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) { @@ -5560,7 +5568,7 @@ static void reset_freesync_config_for_crtc( sizeof(new_crtc_state->vrr_infopacket)); } -static bool +STATIC_IFN_KUNIT bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state) { @@ -5589,8 +5597,9 @@ is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, return false; } +EXPORT_IF_KUNIT(is_timing_unchanged_for_freesync); -static void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) +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; @@ -5604,6 +5613,7 @@ static void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) 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, @@ -6339,8 +6349,8 @@ out: return ret; } -static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, - int *src_w, int *src_h) +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: @@ -6356,8 +6366,9 @@ static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, break; } } +EXPORT_IF_KUNIT(dm_get_oriented_plane_size); -static void +STATIC_IFN_KUNIT void dm_get_plane_scale(struct drm_plane_state *plane_state, int *out_plane_scale_w, int *out_plane_scale_h) { @@ -6367,6 +6378,7 @@ dm_get_plane_scale(struct drm_plane_state *plane_state, *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 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 eedca412eca0..2ace3abe15e5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1126,4 +1126,25 @@ 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); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +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); +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); +#endif + #endif /* __AMDGPU_DM_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 index 4b2864375105..1f910a6a00c0 100644 --- 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 @@ -10,6 +10,7 @@ #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 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4d2eb301c2af..4365d4024f70 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_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 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 new file mode 100644 index 000000000000..31194ab42f04 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" + +/* Tests for dm_plane_layer_index_cmp() */ + +/** + * dm_test_plane_layer_index_cmp_equal - Test Plane layer index cmp equal + * @test: The KUnit test context + */ +static void dm_test_plane_layer_index_cmp_equal(struct kunit *test) +{ + struct dc_plane_state *plane_a; + struct dc_plane_state *plane_b; + struct dc_surface_update sa, sb; + + plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); + plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); + + plane_a->layer_index = 5; + plane_b->layer_index = 5; + sa.surface = plane_a; + sb.surface = plane_b; + + KUNIT_EXPECT_EQ(test, dm_plane_layer_index_cmp(&sa, &sb), 0); +} + +/** + * dm_test_plane_layer_index_cmp_descending - Test Plane layer index cmp descending + * @test: The KUnit test context + */ +static void dm_test_plane_layer_index_cmp_descending(struct kunit *test) +{ + struct dc_plane_state *plane_a; + struct dc_plane_state *plane_b; + struct dc_surface_update sa, sb; + + plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); + plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); + + plane_a->layer_index = 3; + plane_b->layer_index = 7; + sa.surface = plane_a; + sb.surface = plane_b; + + /* b has higher index, so cmp(a,b) = b - a > 0 (b sorts first) */ + KUNIT_EXPECT_GT(test, dm_plane_layer_index_cmp(&sa, &sb), 0); +} + +/** + * dm_test_plane_layer_index_cmp_ascending - Test Plane layer index cmp ascending + * @test: The KUnit test context + */ +static void dm_test_plane_layer_index_cmp_ascending(struct kunit *test) +{ + struct dc_plane_state *plane_a; + struct dc_plane_state *plane_b; + struct dc_surface_update sa, sb; + + plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); + plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); + + plane_a->layer_index = 9; + plane_b->layer_index = 2; + sa.surface = plane_a; + sb.surface = plane_b; + + /* a has higher index, so cmp(a,b) = b - a < 0 (a sorts first) */ + KUNIT_EXPECT_LT(test, dm_plane_layer_index_cmp(&sa, &sb), 0); +} + +/* Tests for fill_plane_color_attributes() */ + +/** + * dm_test_fill_color_attr_rgb_format - Test Fill color attr rgb format + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_rgb_format(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + /* RGB format: should return 0 and set SRGB regardless of encoding */ + plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_fill_color_attr_bt601_full - Test Fill color attr bt601 full + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt601_full(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT601; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR601); +} + +/** + * dm_test_fill_color_attr_bt601_limited - Test Fill color attr bt601 limited + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt601_limited(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT601; + plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_YCBCR601_LIMITED); +} + +/** + * dm_test_fill_color_attr_bt709_full - Test Fill color attr bt709 full + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt709_full(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR709); +} + +/** + * dm_test_fill_color_attr_bt709_limited - Test Fill color attr bt709 limited + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt709_limited(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; + plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_YCBCR709_LIMITED); +} + +/** + * dm_test_fill_color_attr_bt2020_full - Test Fill color attr bt2020 full + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt2020_full(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_2020_YCBCR_FULL); +} + +/** + * dm_test_fill_color_attr_bt2020_limited - Test Fill color attr bt2020 limited + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt2020_limited(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020; + plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_2020_YCBCR_LIMITED); +} + +/** + * dm_test_fill_color_attr_invalid_encoding - Test Fill color attr invalid encoding + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_invalid_encoding(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = 99; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* Tests for modereset_required() */ + +/** + * dm_test_modereset_required_when_inactive_and_modeset - Test Modereset required when inactive and modeset + * @test: The KUnit test context + */ +static void dm_test_modereset_required_when_inactive_and_modeset(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + crtc_state.active = false; + crtc_state.mode_changed = true; + + KUNIT_EXPECT_TRUE(test, modereset_required(&crtc_state)); +} + +/** + * dm_test_modereset_not_required_when_active_and_modeset - Test Modereset not required when active and modeset + * @test: The KUnit test context + */ +static void dm_test_modereset_not_required_when_active_and_modeset(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + crtc_state.active = true; + crtc_state.mode_changed = true; + + KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); +} + +/** + * dm_test_modereset_not_required_when_inactive_without_modeset - Test Modereset not required when inactive without modeset + * @test: The KUnit test context + */ +static void dm_test_modereset_not_required_when_inactive_without_modeset(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + crtc_state.active = false; + crtc_state.mode_changed = false; + + 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() */ + +/** + * dm_test_scaling_state_same - Test identical scaling states compare equal + * @test: The KUnit test context + */ +static void dm_test_scaling_state_same(struct kunit *test) +{ + struct dm_connector_state a = { 0 }; + struct dm_connector_state b = { 0 }; + + a.scaling = RMX_FULL; + a.underscan_enable = false; + b = a; + + KUNIT_EXPECT_FALSE(test, is_scaling_state_different(&a, &b)); +} + +/** + * dm_test_scaling_state_scaling_changed - Test differing scaling mode is detected + * @test: The KUnit test context + */ +static void dm_test_scaling_state_scaling_changed(struct kunit *test) +{ + struct dm_connector_state a = { 0 }; + struct dm_connector_state b = { 0 }; + + a.scaling = RMX_FULL; + b.scaling = RMX_CENTER; + + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); +} + +/** + * dm_test_scaling_state_underscan_enabled - Test enabling underscan with borders differs + * @test: The KUnit test context + */ +static void dm_test_scaling_state_underscan_enabled(struct kunit *test) +{ + struct dm_connector_state old_state = { 0 }; + struct dm_connector_state new_state = { 0 }; + + /* new enables underscan with non-zero borders, old has it disabled */ + new_state.underscan_enable = true; + new_state.underscan_hborder = 16; + new_state.underscan_vborder = 16; + old_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 + */ +static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) +{ + struct dm_connector_state a = { 0 }; + struct dm_connector_state b = { 0 }; + + a.underscan_enable = true; + a.underscan_hborder = 16; + a.underscan_vborder = 16; + b = a; + b.underscan_hborder = 32; + + 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() */ + +/** + * dm_test_multisync_trigger_disabled - Test disabled reset leaves params untouched + * @test: The KUnit test context + */ +static void dm_test_multisync_trigger_disabled(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + stream->triggered_crtc_reset.enabled = false; + stream->triggered_crtc_reset.event = CRTC_EVENT_VSYNC_FALLING; + stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_LINE; + + set_multisync_trigger_params(stream); + + /* Nothing should change when the reset trigger is disabled */ + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_FALLING); + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_LINE); +} + +/** + * dm_test_multisync_trigger_rising - Test positive vsync polarity selects rising edge + * @test: The KUnit test context + */ +static void dm_test_multisync_trigger_rising(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_stream_state *master; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master); + + master->timing.flags.VSYNC_POSITIVE_POLARITY = 1; + stream->triggered_crtc_reset.enabled = true; + stream->triggered_crtc_reset.event_source = master; + + set_multisync_trigger_params(stream); + + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_RISING); + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_PIXEL); +} + +/** + * dm_test_multisync_trigger_falling - Test negative vsync polarity selects falling edge + * @test: The KUnit test context + */ +static void dm_test_multisync_trigger_falling(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_stream_state *master; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master); + + master->timing.flags.VSYNC_POSITIVE_POLARITY = 0; + stream->triggered_crtc_reset.enabled = true; + stream->triggered_crtc_reset.event_source = master; + + set_multisync_trigger_params(stream); + + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_FALLING); + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_PIXEL); +} + +/* Tests for set_master_stream() */ + +/** + * dm_test_master_stream_highest_refresh - Test highest refresh-rate stream becomes master + * @test: The KUnit test context + */ +static void dm_test_master_stream_highest_refresh(struct kunit *test) +{ + struct dc_stream_state *stream0, *stream1; + struct dc_stream_state *stream_set[2]; + + stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0); + stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1); + stream_set[0] = stream0; + stream_set[1] = stream1; + + /* stream0: 60Hz, stream1: 120Hz -> stream1 is master */ + 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; + + set_master_stream(stream_set, 2); + + KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, + stream1); + KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, + stream1); +} + +/** + * dm_test_master_stream_defaults_to_first - Test default master when none triggered + * @test: The KUnit test context + * + * When no stream has the reset trigger enabled, master_stream stays 0 and all + * streams point at the first stream as their event source. + */ +static void dm_test_master_stream_defaults_to_first(struct kunit *test) +{ + struct dc_stream_state *stream0, *stream1; + struct dc_stream_state *stream_set[2]; + + stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0); + stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1); + stream_set[0] = stream0; + stream_set[1] = stream1; + + set_master_stream(stream_set, 2); + + KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, + stream0); + KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, + stream0); +} + +static struct kunit_case amdgpu_dm_tests[] = { + /* dm_plane_layer_index_cmp */ + KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), + KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), + KUNIT_CASE(dm_test_plane_layer_index_cmp_ascending), + /* fill_plane_color_attributes */ + KUNIT_CASE(dm_test_fill_color_attr_rgb_format), + KUNIT_CASE(dm_test_fill_color_attr_bt601_full), + KUNIT_CASE(dm_test_fill_color_attr_bt601_limited), + KUNIT_CASE(dm_test_fill_color_attr_bt709_full), + KUNIT_CASE(dm_test_fill_color_attr_bt709_limited), + KUNIT_CASE(dm_test_fill_color_attr_bt2020_full), + KUNIT_CASE(dm_test_fill_color_attr_bt2020_limited), + KUNIT_CASE(dm_test_fill_color_attr_invalid_encoding), + /* modereset_required */ + 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), + KUNIT_CASE(dm_test_scaling_state_underscan_enabled), + 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), + KUNIT_CASE(dm_test_multisync_trigger_falling), + /* set_master_stream */ + KUNIT_CASE(dm_test_master_stream_highest_refresh), + KUNIT_CASE(dm_test_master_stream_defaults_to_first), + {} +}; + +static struct kunit_suite amdgpu_dm_test_suite = { + .name = "amdgpu_dm", + .test_cases = amdgpu_dm_tests, +}; + +kunit_test_suite(amdgpu_dm_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 179b22085ef0177fa3c78afa2858100d59f5a991 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 20:57:54 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_backlight Add KUnit tests for the backlight helpers in amdgpu_dm_backlight.c. Tests cover: - amdgpu_dm_update_backlight_caps(): short-circuit on populated caps and default value assignment - get_brightness_range(): NULL, PWM-only, and AUX backlight paths - convert_brightness_to_user(): minimum clamp, maximum passthrough, and mid-range rescaling - convert_brightness_from_user(): linear rescaling, AUX path, and custom-curve mapping - convert_custom_brightness(): exact match, below-first, interpolation, above-last, single data point, zero lower luminance, and the debug-mask and no-data-point guards - amdgpu_dm_update_connector_ext_caps(): negative bl_idx and non-eDP early returns, OLED defaults, luminance range copy, and the amdgpu_backlight force-AUX/force-PWM overrides - amdgpu_dm_should_create_sysfs(): forced ABM, non-eDP, missing backlight index, and AUX vs PWM backlight - amdgpu_dm_setup_backlight_device(): non-eDP/LVDS skip, disconnected link skip, eDP-count limit, and the successful eDP setup path Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 71 +- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 18 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 1128 ++++++++++++++++++++ 4 files changed, 1210 insertions(+), 9 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c 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 3770e8dafdbf..f101aed75bb3 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,6 +47,7 @@ #include "amdgpu_dm_trace.h" #include "amd_shared.h" +#include "amdgpu_dm_kunit_helpers.h" #define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 #define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 @@ -92,9 +93,11 @@ void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, caps->caps_valid = true; #endif } +EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps); -static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, - unsigned int *min, unsigned int *max) +STATIC_IFN_KUNIT +int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, + unsigned int *min, unsigned int *max) { if (!caps) return 0; @@ -110,6 +113,7 @@ static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, } return 1; } +EXPORT_IF_KUNIT(get_brightness_range); /* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ static inline u32 scale_input_to_fw(int min, int max, u64 input) @@ -123,9 +127,10 @@ static inline u32 scale_fw_to_input(int min, int max, u64 input) return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); } -static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, - unsigned int min, unsigned int max, - uint32_t *user_brightness) +STATIC_IFN_KUNIT +void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, + unsigned int min, unsigned int max, + uint32_t *user_brightness) { u32 brightness = scale_input_to_fw(min, max, *user_brightness); u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; @@ -187,8 +192,11 @@ scale: DIV_ROUND_CLOSEST(lum * brightness, 101)); } -static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) +EXPORT_IF_KUNIT(convert_custom_brightness); + +STATIC_IFN_KUNIT +u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) { unsigned int min, max; @@ -201,8 +209,11 @@ static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *c return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max); } -static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) +EXPORT_IF_KUNIT(convert_brightness_from_user); + +STATIC_IFN_KUNIT +u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) { unsigned int min, max; @@ -215,6 +226,7 @@ static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *cap return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min), max - min); } +EXPORT_IF_KUNIT(convert_brightness_to_user); static struct dc_stream_state *dm_find_stream_with_link( struct amdgpu_display_manager *dm, @@ -529,6 +541,7 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) } } } +EXPORT_IF_KUNIT(amdgpu_dm_update_connector_ext_caps); void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector) @@ -561,6 +574,7 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, dm->adev->mode_info.abm_level_property, ABM_SYSFS_CONTROL); } +EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device); /** * DOC: panel power savings @@ -658,3 +672,42 @@ amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) return true; } +EXPORT_IF_KUNIT(amdgpu_dm_should_create_sysfs); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +uint amdgpu_dm_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_dc_debug_mask); + +void amdgpu_dm_set_dc_debug_mask(uint val) +{ + amdgpu_dc_debug_mask = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_dc_debug_mask); + +int amdgpu_dm_get_abm_level_param(void) +{ + return amdgpu_dm_abm_level; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_abm_level_param); + +void amdgpu_dm_set_abm_level_param(int val) +{ + amdgpu_dm_abm_level = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_abm_level_param); + +int amdgpu_dm_get_backlight_param(void) +{ + return amdgpu_backlight; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_backlight_param); + +void amdgpu_dm_set_backlight_param(int val) +{ + amdgpu_backlight = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_backlight_param); +#endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index acff23f9feef..5234da6ae484 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -41,4 +41,22 @@ bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector); extern const struct attribute_group amdgpu_group; +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, + unsigned int *min, unsigned int *max); +void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, + unsigned int min, unsigned int max, + uint32_t *user_brightness); +u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness); +u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness); +uint amdgpu_dm_get_dc_debug_mask(void); +void amdgpu_dm_set_dc_debug_mask(uint val); +int amdgpu_dm_get_abm_level_param(void); +void amdgpu_dm_set_abm_level_param(int val); +int amdgpu_dm_get_backlight_param(void); +void amdgpu_dm_set_backlight_param(int val); +#endif + #endif /* __AMDGPU_DM_BACKLIGHT_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4365d4024f70..ddd9fce66232 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -8,12 +8,14 @@ ccflags-y += -I$(src)/../../include ccflags-y += -I$(src)/../../modules/inc ccflags-y += -I$(src)/../../dc ccflags-y += -I$(src)/../../../amdgpu +ccflags-y += -I$(src)/../../../amdkfd ccflags-y += -I$(src)/../../../include obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o 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 new file mode 100644 index 000000000000..2f4293cfd478 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -0,0 +1,1128 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_backlight.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_backlight.h" +#include "amd_shared.h" + +struct dm_backlight_connector_fixture { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + return dm; +} + +static void setup_test_connector(struct kunit *test, + struct dm_backlight_connector_fixture *fixture, + int bl_idx, enum signal_type signal) +{ + fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL); + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), GFP_KERNEL); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + + fixture->aconnector->bl_idx = bl_idx; + fixture->aconnector->dc_link = fixture->link; + fixture->aconnector->base.dev = &fixture->adev->ddev; + fixture->link->connector_signal = signal; +} + +/* Tests for amdgpu_dm_update_backlight_caps() */ + +/** + * dm_test_backlight_caps_valid_short_circuit - Test Backlight caps valid short circuit + * @test: The KUnit test context + */ +static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + caps->caps_valid = true; + caps->aux_support = false; + caps->min_input_signal = 42; + caps->max_input_signal = 199; + + amdgpu_dm_update_backlight_caps(dm, 0); + + KUNIT_EXPECT_TRUE(test, caps->caps_valid); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, 42); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, 199); +} + +#if !defined(CONFIG_ACPI) + +/** + * dm_test_backlight_caps_aux_support_noop - Test Backlight caps aux support noop + * @test: The KUnit test context + */ +static void dm_test_backlight_caps_aux_support_noop(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + caps->caps_valid = false; + caps->aux_support = true; + caps->min_input_signal = 11; + caps->max_input_signal = 222; + + amdgpu_dm_update_backlight_caps(dm, 0); + + KUNIT_EXPECT_FALSE(test, caps->caps_valid); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, 11); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, 222); +} + +/** + * dm_test_backlight_caps_non_aux_sets_defaults - Test Backlight caps non aux sets defaults + * @test: The KUnit test context + */ +static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + caps->caps_valid = false; + caps->aux_support = false; + caps->min_input_signal = 0; + caps->max_input_signal = 0; + + amdgpu_dm_update_backlight_caps(dm, 0); + + KUNIT_EXPECT_TRUE(test, caps->caps_valid); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, 12); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, 255); +} +#endif + +/* Tests for get_brightness_range() */ + +/** + * dm_test_brightness_range_null_caps - Test Brightness range null caps + * @test: The KUnit test context + */ +static void dm_test_brightness_range_null_caps(struct kunit *test) +{ + unsigned int min = 99, max = 99; + + KUNIT_EXPECT_EQ(test, get_brightness_range(NULL, &min, &max), 0); + /* min/max should remain untouched */ + KUNIT_EXPECT_EQ(test, min, 99U); + KUNIT_EXPECT_EQ(test, max, 99U); +} + +/** + * dm_test_brightness_range_pwm - Test Brightness range pwm + * @test: The KUnit test context + */ +static void dm_test_brightness_range_pwm(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); + /* 0x101 * 12 = 3084, 0x101 * 255 = 65535 */ + KUNIT_EXPECT_EQ(test, min, 0x101U * 12); + KUNIT_EXPECT_EQ(test, max, 0x101U * 255); +} + +/** + * dm_test_brightness_range_aux - Test Brightness range aux + * @test: The KUnit test context + */ +static void dm_test_brightness_range_aux(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = true; + caps.aux_min_input_signal = 1; + caps.aux_max_input_signal = 512; + + KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); + /* millinits: 1000 * value */ + KUNIT_EXPECT_EQ(test, min, 1000U); + KUNIT_EXPECT_EQ(test, max, 512000U); +} + +/* Tests for convert_brightness_to_user() */ + +/** + * dm_test_brightness_to_user_null_caps - Test Brightness to user null caps + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_null_caps(struct kunit *test) +{ + /* + * With NULL caps, get_brightness_range fails → passthrough. + * We simulate this by passing a zeroed caps struct where + * max_input_signal=0 makes max=0 and the function hits + * get_brightness_range returning 0 since caps is NULL. + */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(NULL, 42), 42U); +} + +/** + * dm_test_brightness_to_user_below_min - Test Brightness to user below min + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_below_min(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + /* brightness < min (0x101*12 = 3084), should return 0 */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U); +} + +/** + * dm_test_brightness_to_user_at_max - Test Brightness to user at max + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_at_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* At max → should return max */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, max), max); +} + +/** + * dm_test_brightness_to_user_at_min - Test Brightness to user at min + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_at_min(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* At min → should return 0 */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, min), 0U); +} + +/** + * dm_test_brightness_to_user_midpoint_pwm - Test Brightness to user midpoint pwm + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max, mid_hw, result; + u64 expected; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* midpoint of hw range */ + mid_hw = min + (max - min) / 2; + /* expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min) */ + expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min); + result = convert_brightness_to_user(&caps, mid_hw); + + KUNIT_EXPECT_EQ(test, result, (u32)expected); +} + +/* Tests for convert_brightness_from_user() — no custom curve */ + +/** + * dm_test_brightness_from_user_null_caps - Test Brightness from user null caps + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_null_caps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(NULL, 100), 100U); +} + +/** + * dm_test_brightness_from_user_zero - Test Brightness from user zero + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_zero(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + /* no custom curve */ + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* brightness=0 → min + 0 = min */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min); +} + +/** + * dm_test_brightness_from_user_max - Test Brightness from user max + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* + * brightness=max → min + DIV_ROUND_CLOSEST((max-min)*max, max) + * = min + (max - min) = max + */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max); +} + +/** + * dm_test_brightness_from_user_aux - Test Brightness from user aux + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_aux(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = true; + caps.aux_min_input_signal = 1; + caps.aux_max_input_signal = 512; + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* brightness=0 → min */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min); + /* brightness=max → max */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max); +} + +/* Tests for convert_custom_brightness() */ + +/** + * dm_test_custom_brightness_no_data_points - Test Custom brightness no data points + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_no_data_points(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness = 128; + uint32_t saved = brightness; + + caps.data_points = 0; + + convert_custom_brightness(&caps, 3084, 65535, &brightness); + + /* No data points → no-op */ + KUNIT_EXPECT_EQ(test, brightness, saved); +} + +/** + * dm_test_custom_brightness_debug_mask_disables - Test Custom brightness debug mask disables + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_debug_mask_disables(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness = 128; + uint32_t saved = brightness; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + caps.data_points = 3; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 10; + + /* Set the disable flag */ + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + convert_custom_brightness(&caps, 3084, 65535, &brightness); + + /* Should be no-op due to debug mask */ + KUNIT_EXPECT_EQ(test, brightness, saved); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_exact_match - Test Custom brightness exact match + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_exact_match(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 3; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 128; + caps.luminance_data[1].luminance = 50; + caps.luminance_data[2].input_signal = 200; + caps.luminance_data[2].luminance = 90; + + get_brightness_range(&caps, &min, &max); + + /* + * Set brightness so that scale_input_to_fw yields exactly 128. + * scale_input_to_fw(min, max, x) = DIV_ROUND_CLOSEST(x * 255, max - min) + * With min=0, max=0x101*255=65535: + * We need x such that DIV_ROUND_CLOSEST(x * 255, 65535) = 128 + * → x = 128 * 65535 / 255 = 32896 + */ + brightness = 32896; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* + * Exact match: lum=50, brightness_scaled=128 + * result = scale_fw_to_input(min, max, DIV_ROUND_CLOSEST(50*128, 101)) + * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(6400, 101)) + * = scale_fw_to_input(0, 65535, 63) + * = 0 + DIV_ROUND_CLOSEST(63 * 65535, 255) = 16191 (approx) + */ + KUNIT_EXPECT_TRUE(test, brightness != 32896); + KUNIT_EXPECT_TRUE(test, brightness < 32896); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_below_first - Test Custom brightness below first + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_below_first(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 100; + caps.luminance_data[0].luminance = 40; + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + /* + * Set brightness low enough that scaled value < 100. + * scale_input_to_fw(0, 65535, x) = DIV_ROUND_CLOSEST(x*255, 65535) + * For result=50: x = 50*65535/255 = 12850 + */ + brightness = 12850; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* + * Below first data point: lum = DIV_ROUND_CLOSEST(40 * 50, 100) = 20 + * Then: scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(20 * 50, 101)) + * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(1000, 101)) + * = scale_fw_to_input(0, 65535, 10) + * The output should be significantly less than input. + */ + KUNIT_EXPECT_TRUE(test, brightness < 12850); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_interpolation - Test Custom brightness interpolation + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_interpolation(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + /* + * Choose a value between data points 50 and 200. + * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125 + */ + brightness = 32125; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* + * The function should interpolate between data points and produce + * a remapped value different from the input. + */ + KUNIT_EXPECT_TRUE(test, brightness != 32125); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_above_last - Test Custom brightness above last data point + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_above_last(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 150; + caps.luminance_data[1].luminance = 60; + + get_brightness_range(&caps, &min, &max); + + /* + * Choose brightness above the last data point (150). + * scale_input_to_fw(0, 65535, x) = 220 when x = 220*65535/255 = 56533 + * After binary search, left >= data_points, clamped → right==left, + * so lum = upper_lum = 60. + */ + brightness = 56533; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* Output should differ from input (remapped via curve) */ + KUNIT_EXPECT_TRUE(test, brightness != 56533); + KUNIT_EXPECT_TRUE(test, brightness < 56533); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_single_data_point - Test Custom brightness with single data point + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_single_data_point(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 1; + caps.luminance_data[0].input_signal = 128; + caps.luminance_data[0].luminance = 50; + + get_brightness_range(&caps, &min, &max); + + /* + * Brightness below the single data point triggers the + * "below first" path: lum = DIV_ROUND_CLOSEST(50 * scaled, 128). + * scale_input_to_fw(0, 65535, x) = 64 when x = 64*65535/255 = 16448 + */ + brightness = 16448; + + convert_custom_brightness(&caps, min, max, &brightness); + + KUNIT_EXPECT_TRUE(test, brightness < 16448); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_lower_lum_zero - Test Custom brightness with zero lower luminance + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 0; /* zero lower luminance */ + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + /* + * Choose brightness between data points to trigger interpolation. + * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125 + * With lower_lum == 0, code takes shortcut: lum = upper_lum = 80. + */ + brightness = 32125; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* Should remap; result should differ from input */ + KUNIT_EXPECT_TRUE(test, brightness != 32125); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_brightness_to_user_above_max - Test Brightness to user above max + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_above_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max, result; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* brightness above max → result > max (linear extrapolation) */ + result = convert_brightness_to_user(&caps, max + 1000); + + KUNIT_EXPECT_GT(test, result, max); +} + +/** + * dm_test_brightness_from_user_midrange - Test Brightness from user mid-range value + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_midrange(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + u32 result; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* Mid-range brightness should map to between min and max */ + result = convert_brightness_from_user(&caps, max / 2); + + KUNIT_EXPECT_GE(test, result, min); + KUNIT_EXPECT_LE(test, result, max); +} + +/** + * dm_test_brightness_from_user_with_curve - Test Brightness from user with custom curve active + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_with_curve(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + u32 with_curve, without_curve; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + with_curve = convert_brightness_from_user(&caps, max / 2); + + /* Now disable the curve and compare */ + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + without_curve = convert_brightness_from_user(&caps, max / 2); + + /* Custom curve should produce a different mapping */ + KUNIT_EXPECT_NE(test, with_curve, without_curve); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_brightness_range_zero_signals - Test Brightness range with zero min and max signals + * @test: The KUnit test context + */ +static void dm_test_brightness_range_zero_signals(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min = 99, max = 99; + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 0; + + /* Both signals zero → min=max=0 */ + KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); + KUNIT_EXPECT_EQ(test, min, 0U); + KUNIT_EXPECT_EQ(test, max, 0U); +} + +/* Tests for amdgpu_dm_update_connector_ext_caps() */ + +/** + * dm_test_update_connector_ext_caps_negative_bl_idx - Test negative backlight index early return + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_negative_bl_idx(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + aconnector->bl_idx = -1; + + amdgpu_dm_update_connector_ext_caps(aconnector); + + KUNIT_SUCCEED(test); +} + +/** + * dm_test_update_connector_ext_caps_non_edp - Test non-eDP connector early return + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_non_edp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A); + fixture.adev->dm.backlight_caps[0].aux_support = true; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_PTR_EQ(test, fixture.adev->dm.backlight_caps[0].ext_caps, NULL); +} + +/** + * dm_test_update_connector_ext_caps_oled_defaults - Test OLED eDP defaults to AUX backlight + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_oled_defaults(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_PTR_EQ(test, fixture.adev->dm.backlight_caps[0].ext_caps, + &fixture.link->dpcd_sink_ext_caps); + KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_EQ(test, fixture.link->backlight_control_type, + BACKLIGHT_CONTROL_AMD_AUX); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_max_input_signal, 512); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_min_input_signal, 1); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/** + * dm_test_update_connector_ext_caps_luminance_values - Test luminance range copy + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_luminance_values(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.display_info.luminance_range.min_luminance = 2; + fixture.aconnector->base.display_info.luminance_range.max_luminance = 400; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_max_input_signal, 400); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_min_input_signal, 2); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/** + * dm_test_update_connector_ext_caps_force_aux - Test module parameter forces AUX backlight + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_force_aux(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_EQ(test, fixture.link->backlight_control_type, + BACKLIGHT_CONTROL_AMD_AUX); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/** + * dm_test_update_connector_ext_caps_force_pwm - Test module parameter forces PWM backlight + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_force_pwm(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(0); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_NE(test, fixture.link->backlight_control_type, + BACKLIGHT_CONTROL_AMD_AUX); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/* Tests for amdgpu_dm_should_create_sysfs() */ + +/** + * dm_test_should_create_sysfs_abm_forced - Test forced ABM disables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_abm_forced(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_non_edp - Test non-eDP connector disables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_non_edp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_no_backlight_index - Test eDP without backlight index enables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_aux_backlight - Test AUX backlight disables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.adev->dm.backlight_caps[0].aux_support = true; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_pwm_backlight - Test PWM backlight enables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.adev->dm.backlight_caps[0].aux_support = false; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/* Tests for amdgpu_dm_setup_backlight_device() */ + +/** + * dm_test_setup_backlight_device_non_edp - Test non-eDP/LVDS link is skipped + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_non_edp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_HDMI_TYPE_A); + fixture.link->type = dc_connection_single; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = 0; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + /* Non-eDP/LVDS signal → no backlight setup */ + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 0); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1); +} + +/** + * dm_test_setup_backlight_device_connection_none - Test disconnected link is skipped + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_connection_none(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.link->type = dc_connection_none; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = 0; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + /* Disconnected link → no backlight setup */ + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 0); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1); +} + +/** + * dm_test_setup_backlight_device_max_edps - Test setup is skipped when at eDP limit + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_max_edps(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.link->type = dc_connection_single; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = AMDGPU_DM_MAX_NUM_EDP; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + /* Already at the eDP limit → no additional setup */ + KUNIT_EXPECT_EQ(test, dm->num_of_edps, AMDGPU_DM_MAX_NUM_EDP); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1); +} + +/** + * dm_test_setup_backlight_device_oled_success - Test successful eDP backlight setup + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_oled_success(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(-1); + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.link->type = dc_connection_single; + /* OLED panel avoids the ABM property attach path */ + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = 0; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 1); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, 0); + KUNIT_EXPECT_PTR_EQ(test, (void *)dm->backlight_link[0], + (void *)fixture.link); + KUNIT_EXPECT_TRUE(test, dm->backlight_caps[0].aux_support); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +static struct kunit_case dm_backlight_test_cases[] = { + KUNIT_CASE(dm_test_backlight_caps_valid_short_circuit), +#if !defined(CONFIG_ACPI) + KUNIT_CASE(dm_test_backlight_caps_aux_support_noop), + KUNIT_CASE(dm_test_backlight_caps_non_aux_sets_defaults), +#endif + /* get_brightness_range */ + KUNIT_CASE(dm_test_brightness_range_null_caps), + KUNIT_CASE(dm_test_brightness_range_pwm), + KUNIT_CASE(dm_test_brightness_range_aux), + /* convert_brightness_to_user */ + KUNIT_CASE(dm_test_brightness_to_user_null_caps), + KUNIT_CASE(dm_test_brightness_to_user_below_min), + KUNIT_CASE(dm_test_brightness_to_user_at_max), + KUNIT_CASE(dm_test_brightness_to_user_at_min), + KUNIT_CASE(dm_test_brightness_to_user_midpoint_pwm), + /* convert_brightness_from_user */ + KUNIT_CASE(dm_test_brightness_from_user_null_caps), + KUNIT_CASE(dm_test_brightness_from_user_zero), + KUNIT_CASE(dm_test_brightness_from_user_max), + KUNIT_CASE(dm_test_brightness_from_user_aux), + /* convert_custom_brightness */ + KUNIT_CASE(dm_test_custom_brightness_no_data_points), + KUNIT_CASE(dm_test_custom_brightness_debug_mask_disables), + KUNIT_CASE(dm_test_custom_brightness_exact_match), + KUNIT_CASE(dm_test_custom_brightness_below_first), + KUNIT_CASE(dm_test_custom_brightness_interpolation), + KUNIT_CASE(dm_test_custom_brightness_above_last), + KUNIT_CASE(dm_test_custom_brightness_single_data_point), + KUNIT_CASE(dm_test_custom_brightness_lower_lum_zero), + KUNIT_CASE(dm_test_brightness_to_user_above_max), + KUNIT_CASE(dm_test_brightness_from_user_midrange), + KUNIT_CASE(dm_test_brightness_from_user_with_curve), + KUNIT_CASE(dm_test_brightness_range_zero_signals), + /* amdgpu_dm_update_connector_ext_caps */ + KUNIT_CASE(dm_test_update_connector_ext_caps_negative_bl_idx), + KUNIT_CASE(dm_test_update_connector_ext_caps_non_edp), + KUNIT_CASE(dm_test_update_connector_ext_caps_oled_defaults), + KUNIT_CASE(dm_test_update_connector_ext_caps_luminance_values), + KUNIT_CASE(dm_test_update_connector_ext_caps_force_aux), + KUNIT_CASE(dm_test_update_connector_ext_caps_force_pwm), + /* amdgpu_dm_should_create_sysfs */ + KUNIT_CASE(dm_test_should_create_sysfs_abm_forced), + KUNIT_CASE(dm_test_should_create_sysfs_non_edp), + KUNIT_CASE(dm_test_should_create_sysfs_no_backlight_index), + KUNIT_CASE(dm_test_should_create_sysfs_aux_backlight), + KUNIT_CASE(dm_test_should_create_sysfs_pwm_backlight), + /* amdgpu_dm_setup_backlight_device */ + KUNIT_CASE(dm_test_setup_backlight_device_non_edp), + KUNIT_CASE(dm_test_setup_backlight_device_connection_none), + KUNIT_CASE(dm_test_setup_backlight_device_max_edps), + KUNIT_CASE(dm_test_setup_backlight_device_oled_success), + {} +}; + +static struct kunit_suite dm_backlight_test_suite = { + .name = "amdgpu_dm_backlight", + .test_cases = dm_backlight_test_cases, +}; + +kunit_test_suite(dm_backlight_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_backlight"); +MODULE_AUTHOR("AMD"); -- cgit v1.2.3 From c71a6dc1cf01cb2c03c9cbb00bfb761cd65a4543 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 21:17:43 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_audio Add KUnit tests for amdgpu_dm_audio.c. Tests cover: - amdgpu_dm_audio_init(): early exit when audio is disabled - amdgpu_dm_audio_fini(): early exit when audio is not enabled - fill_audio_info(): manufacturer and product ID propagation, display name copy, speaker allocation flags, CEA revision gating of audio mode copying (including the zero-mode case), and latency field propagation - amdgpu_dm_audio_component_bind()/unbind(): component ops, device, and audio_component pointer are wired up on bind and cleared on unbind - amdgpu_dm_audio_eld_notify(): callback is forwarded with the correct port and audio pointer, and the no-op guard paths for a missing component, audio_ops, or pin_eld_notify callback Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 27 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h | 12 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_audio_test.c | 490 +++++++++++++++++++++ 4 files changed, 527 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c 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 a15b7c0c9075..13c9a9d145ba 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,6 +26,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_audio.h" +#include "amdgpu_dm_kunit_helpers.h" #include "dc.h" #include @@ -83,7 +84,7 @@ static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { .get_eld = amdgpu_dm_audio_component_get_eld, }; -static int amdgpu_dm_audio_component_bind(struct device *kdev, +STATIC_IFN_KUNIT int amdgpu_dm_audio_component_bind(struct device *kdev, struct device *hda_kdev, void *data) { struct drm_device *dev = dev_get_drvdata(kdev); @@ -96,8 +97,9 @@ static int amdgpu_dm_audio_component_bind(struct device *kdev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_component_bind); -static void amdgpu_dm_audio_component_unbind(struct device *kdev, +STATIC_IFN_KUNIT void amdgpu_dm_audio_component_unbind(struct device *kdev, struct device *hda_kdev, void *data) { struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); @@ -107,6 +109,7 @@ static void amdgpu_dm_audio_component_unbind(struct device *kdev, acomp->dev = NULL; adev->dm.audio_component = NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_component_unbind); static const struct component_ops amdgpu_dm_audio_component_bind_ops = { .bind = amdgpu_dm_audio_component_bind, @@ -144,6 +147,7 @@ int amdgpu_dm_audio_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_init); void amdgpu_dm_audio_fini(struct amdgpu_device *adev) { @@ -162,8 +166,9 @@ void amdgpu_dm_audio_fini(struct amdgpu_device *adev) adev->mode_info.audio.enabled = false; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_fini); -static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) +STATIC_IFN_KUNIT void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) { struct drm_audio_component *acomp = adev->dm.audio_component; @@ -174,6 +179,7 @@ static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) pin, -1); } } +EXPORT_IF_KUNIT(amdgpu_dm_audio_eld_notify); void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, @@ -219,6 +225,7 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ } +EXPORT_IF_KUNIT(amdgpu_dm_fill_audio_info); void amdgpu_dm_commit_audio(struct drm_device *dev, struct drm_atomic_commit *state) @@ -300,3 +307,17 @@ notify: amdgpu_dm_audio_eld_notify(adev, inst); } } + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int amdgpu_dm_audio_get_param(void) +{ + return amdgpu_audio; +} +EXPORT_IF_KUNIT(amdgpu_dm_audio_get_param); + +void amdgpu_dm_audio_set_param(int val) +{ + amdgpu_audio = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_audio_set_param); +#endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h index 58cce1f79ffd..7acfc5ef69b3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -41,4 +41,16 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, const struct dc_sink *dc_sink); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct device; + +int amdgpu_dm_audio_component_bind(struct device *kdev, + struct device *hda_kdev, void *data); +void amdgpu_dm_audio_component_unbind(struct device *kdev, + struct device *hda_kdev, void *data); +void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); +int amdgpu_dm_audio_get_param(void); +void amdgpu_dm_audio_set_param(int val); +#endif + #endif /* __AMDGPU_DM_AUDIO_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index ddd9fce66232..5bb43b3bc439 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -13,6 +13,7 @@ ccflags-y += -I$(src)/../../../include obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o 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 new file mode 100644 index 000000000000..79ff5d9b3fa5 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_audio.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_audio.h" + +/* Tests for amdgpu_dm_audio_init() */ + +/** + * dm_test_audio_init_disabled - Test audio init exits when audio is disabled + * @test: The KUnit test context + */ +static void dm_test_audio_init_disabled(struct kunit *test) +{ + struct amdgpu_device *adev; + int saved_audio = amdgpu_dm_audio_get_param(); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + amdgpu_dm_audio_set_param(0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_audio_init(adev), 0); + KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled); + KUNIT_EXPECT_FALSE(test, adev->dm.audio_registered); + + amdgpu_dm_audio_set_param(saved_audio); +} + +/* Tests for amdgpu_dm_audio_fini() */ + +/** + * dm_test_audio_fini_without_enabled_audio - Test fini exits when audio is not enabled + * @test: The KUnit test context + */ +static void dm_test_audio_fini_without_enabled_audio(struct kunit *test) +{ + struct amdgpu_device *adev; + int saved_audio = amdgpu_dm_audio_get_param(); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + amdgpu_dm_audio_set_param(1); + adev->mode_info.audio.enabled = false; + adev->dm.audio_registered = true; + + amdgpu_dm_audio_fini(adev); + + KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled); + KUNIT_EXPECT_TRUE(test, adev->dm.audio_registered); + + amdgpu_dm_audio_set_param(saved_audio); +} + +/* Tests for amdgpu_dm_fill_audio_info() */ + +/** + * dm_test_fill_audio_info_ids_name_flags - Test Fill audio info ids name flags + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_ids_name_flags(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + const char *name = "DM-AUDIO-PANEL"; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + dc_sink->edid_caps.manufacturer_id = 0x1234; + dc_sink->edid_caps.product_id = 0xABCD; + dc_sink->edid_caps.speaker_flags = 0x5; + strscpy(dc_sink->edid_caps.display_name, name, + AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); + + connector->display_info.cea_rev = 1; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->manufacture_id, 0x1234U); + KUNIT_EXPECT_EQ(test, audio_info->product_id, 0xABCDU); + KUNIT_EXPECT_EQ(test, audio_info->flags.all, 0x5U); + KUNIT_EXPECT_STREQ(test, audio_info->display_name, name); +} + +/** + * dm_test_fill_audio_info_cea_lt_3_skips_modes - Test Fill audio info cea lt 3 skips modes + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_cea_lt_3_skips_modes(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 2; + dc_sink->edid_caps.audio_mode_count = 2; + dc_sink->edid_caps.audio_modes[0].format_code = 1; + dc_sink->edid_caps.audio_modes[0].channel_count = 2; + dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07; + dc_sink->edid_caps.audio_modes[0].sample_size = 16; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U); +} + +/** + * dm_test_fill_audio_info_cea_ge_3_copies_modes - Test Fill audio info cea ge 3 copies modes + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_cea_ge_3_copies_modes(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + dc_sink->edid_caps.audio_mode_count = 2; + + dc_sink->edid_caps.audio_modes[0].format_code = 1; + dc_sink->edid_caps.audio_modes[0].channel_count = 2; + dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07; + dc_sink->edid_caps.audio_modes[0].sample_size = 16; + + dc_sink->edid_caps.audio_modes[1].format_code = 11; + dc_sink->edid_caps.audio_modes[1].channel_count = 6; + dc_sink->edid_caps.audio_modes[1].sample_rate = 0x1F; + dc_sink->edid_caps.audio_modes[1].sample_size = 24; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->mode_count, 2U); + + KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 1); + KUNIT_EXPECT_EQ(test, audio_info->modes[0].channel_count, 2); + KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_rates.all, 0x07U); + KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_size, 16); + + KUNIT_EXPECT_EQ(test, (int)audio_info->modes[1].format_code, 11); + KUNIT_EXPECT_EQ(test, audio_info->modes[1].channel_count, 6); + KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_rates.all, 0x1FU); + KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_size, 24); +} + +/** + * dm_test_fill_audio_info_latency_present - Test Fill audio info latency present + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_latency_present(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + connector->latency_present[0] = true; + connector->video_latency[0] = 11; + connector->audio_latency[0] = 22; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->video_latency, 11U); + KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 22U); +} + +/** + * dm_test_fill_audio_info_latency_absent_keeps_zero - Test Fill audio info latency absent keeps zero + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_latency_absent_keeps_zero(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + connector->latency_present[0] = false; + connector->video_latency[0] = 99; + connector->audio_latency[0] = 88; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->video_latency, 0U); + KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 0U); +} + +/** + * dm_test_fill_audio_info_cea_ge_3_zero_modes - Test cea >= 3 with zero modes + * @test: The KUnit test context + * + * When cea_rev >= 3 but the sink reports no audio modes, mode_count must be + * copied as 0 and no mode entries should be populated. + */ +static void dm_test_fill_audio_info_cea_ge_3_zero_modes(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + dc_sink->edid_caps.audio_mode_count = 0; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U); + KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 0); +} + +/* Tests for amdgpu_dm_audio_component_bind()/unbind() */ + +/** + * dm_test_audio_component_bind_sets_fields - Test bind wires up audio component + * @test: The KUnit test context + * + * Binding must publish the DRM audio component ops, record the kernel device, + * and store the component pointer in the display manager. + */ +static void dm_test_audio_component_bind_sets_fields(struct kunit *test) +{ + struct amdgpu_device *adev; + struct device *kdev; + struct drm_audio_component *acomp; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + + dev_set_drvdata(kdev, &adev->ddev); + + ret = amdgpu_dm_audio_component_bind(kdev, NULL, acomp); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_NOT_NULL(test, acomp->ops); + KUNIT_EXPECT_PTR_EQ(test, acomp->dev, kdev); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.audio_component, acomp); +} + +/** + * dm_test_audio_component_unbind_clears_fields - Test unbind tears down component + * @test: The KUnit test context + * + * Unbinding must clear the component ops, the kernel device, and the display + * manager's stored component pointer. + */ +static void dm_test_audio_component_unbind_clears_fields(struct kunit *test) +{ + struct amdgpu_device *adev; + struct device *kdev; + struct drm_audio_component *acomp; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + + dev_set_drvdata(kdev, &adev->ddev); + + /* Pretend a prior bind already happened. */ + acomp->dev = kdev; + adev->dm.audio_component = acomp; + + amdgpu_dm_audio_component_unbind(kdev, NULL, acomp); + + KUNIT_EXPECT_NULL(test, acomp->ops); + KUNIT_EXPECT_NULL(test, acomp->dev); + KUNIT_EXPECT_NULL(test, adev->dm.audio_component); +} + +/* Tests for amdgpu_dm_audio_eld_notify() */ + +static int dm_test_eld_notify_count; +static int dm_test_eld_notify_port; +static void *dm_test_eld_notify_ptr; + +static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe) +{ + dm_test_eld_notify_count++; + dm_test_eld_notify_port = port; + dm_test_eld_notify_ptr = audio_ptr; +} + +/** + * dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver + * @test: The KUnit test context + * + * When a component with a pin_eld_notify callback is registered, the notify + * helper must invoke it with the audio pointer and the requested pin. + */ +static void dm_test_eld_notify_invokes_callback(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_audio_component *acomp; + struct drm_audio_component_audio_ops *audio_ops; + int marker = 0; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops); + + audio_ops->audio_ptr = ▮ + audio_ops->pin_eld_notify = dm_test_pin_eld_notify; + acomp->audio_ops = audio_ops; + adev->dm.audio_component = acomp; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -100; + dm_test_eld_notify_ptr = NULL; + + amdgpu_dm_audio_eld_notify(adev, 7); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 7); + KUNIT_EXPECT_PTR_EQ(test, dm_test_eld_notify_ptr, (void *)&marker); +} + +/** + * dm_test_eld_notify_no_component - Test ELD notify is a no-op without component + * @test: The KUnit test context + * + * With no registered audio component, the notify helper must return without + * invoking any callback. + */ +static void dm_test_eld_notify_no_component(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->dm.audio_component = NULL; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_audio_eld_notify(adev, 3); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_eld_notify_null_audio_ops - Test ELD notify is a no-op without audio_ops + * @test: The KUnit test context + * + * A component without audio_ops must not trigger any callback. + */ +static void dm_test_eld_notify_null_audio_ops(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_audio_component *acomp; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + + acomp->audio_ops = NULL; + adev->dm.audio_component = acomp; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_audio_eld_notify(adev, 3); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_eld_notify_null_callback - Test ELD notify is a no-op without callback + * @test: The KUnit test context + * + * audio_ops present but with a NULL pin_eld_notify must not crash or call + * anything. + */ +static void dm_test_eld_notify_null_callback(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_audio_component *acomp; + struct drm_audio_component_audio_ops *audio_ops; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops); + + audio_ops->pin_eld_notify = NULL; + acomp->audio_ops = audio_ops; + adev->dm.audio_component = acomp; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_audio_eld_notify(adev, 3); + + 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), + /* amdgpu_dm_audio_fini */ + KUNIT_CASE(dm_test_audio_fini_without_enabled_audio), + /* amdgpu_dm_fill_audio_info */ + KUNIT_CASE(dm_test_fill_audio_info_ids_name_flags), + KUNIT_CASE(dm_test_fill_audio_info_cea_lt_3_skips_modes), + KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_copies_modes), + KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_zero_modes), + KUNIT_CASE(dm_test_fill_audio_info_latency_present), + KUNIT_CASE(dm_test_fill_audio_info_latency_absent_keeps_zero), + /* amdgpu_dm_audio_component_bind/unbind */ + KUNIT_CASE(dm_test_audio_component_bind_sets_fields), + KUNIT_CASE(dm_test_audio_component_unbind_clears_fields), + /* amdgpu_dm_audio_eld_notify */ + KUNIT_CASE(dm_test_eld_notify_invokes_callback), + 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), + {} +}; + +static struct kunit_suite dm_audio_test_suite = { + .name = "amdgpu_dm_audio", + .test_cases = dm_audio_test_cases, +}; + +kunit_test_suite(dm_audio_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_audio"); +MODULE_AUTHOR("AMD"); -- cgit v1.2.3 From 829e9b68eaf2f5b4fd3cd9c24e7e154fcd637c53 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 21:28:36 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_dmub Add KUnit tests for amdgpu_dm_dmub.c covering the following functions: - dm_register_dmub_notify_callback(): NULL callback rejection, out-of-range type, valid registration with offload flag - dm_dmub_aux_setconfig_callback(): copy and complete on AUX reply, non-AUX skip, NULL dm_notify, SET_CONFIG reply - dm_dmub_aux_fused_io_callback(): copy reply and complete, max ddc_line boundary - dm_get_default_ips_mode(): IPS mode per DCN version (3.5, 3.5.1, 3.6, 4.2), disabled for older ASICs, default enabled for unhandled newer ASICs - dm_dmub_hw_init(): early returns for no dmub_srv, no fb_info, no firmware - dm_dmub_hw_resume(): no-op when dmub_srv is NULL - dm_dmub_sw_init(): returns 0 for unsupported ASIC - dm_init_microcode(): returns 0 for unsupported ASIC Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 9 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 600 +++++++++++++++++++++ 3 files changed, 610 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c 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 739e685f1c3c..b4c3371f5757 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,6 +38,7 @@ #include "amdgpu_ucode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_dmub.h" +#include "amdgpu_dm_kunit_helpers.h" #include #include @@ -80,6 +81,7 @@ void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev, if (notify->type == DMUB_NOTIFICATION_AUX_REPLY) complete(&adev->dm.dmub_aux_transfer_done); } +EXPORT_IF_KUNIT(dm_dmub_aux_setconfig_callback); void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, struct dmub_notification *notify) @@ -103,6 +105,7 @@ void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, memcpy(sync->reply_data, req, sizeof(*req)); complete(&sync->replied); } +EXPORT_IF_KUNIT(dm_dmub_aux_fused_io_callback); /** * dm_register_dmub_notify_callback - Sets callback for DMUB notify @@ -129,6 +132,7 @@ bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, return true; } +EXPORT_IF_KUNIT(dm_register_dmub_notify_callback); int dm_dmub_hw_init(struct amdgpu_device *adev) { @@ -318,6 +322,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(dm_dmub_hw_init); void dm_dmub_hw_resume(struct amdgpu_device *adev) { @@ -347,6 +352,7 @@ void dm_dmub_hw_resume(struct amdgpu_device *adev) drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); } } +EXPORT_IF_KUNIT(dm_dmub_hw_resume); static enum dmub_status dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, @@ -460,6 +466,7 @@ enum dmub_ips_disable_type dm_get_default_ips_mode( return ret; } +EXPORT_IF_KUNIT(dm_get_default_ips_mode); static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address) { @@ -677,6 +684,7 @@ int dm_dmub_sw_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(dm_dmub_sw_init); int dm_init_microcode(struct amdgpu_device *adev) { @@ -749,6 +757,7 @@ int dm_init_microcode(struct amdgpu_device *adev) "%s", fw_name_dmub); return r; } +EXPORT_IF_KUNIT(dm_init_microcode); int amdgpu_dm_process_dmub_aux_transfer_sync( struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 5bb43b3bc439..4bd8d1fa0fee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o 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 new file mode 100644 index 000000000000..b82dd301a896 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_dmub.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "dc/inc/core_types.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "dmub/dmub_srv.h" +#include "amdgpu_dm_dmub.h" + +/* Tests for dm_register_dmub_notify_callback() */ + +static void dummy_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ +} + +/** + * dm_test_register_dmub_notify_callback_null_callback - Test null callback is rejected + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_null_callback(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_FALSE(test, dm_register_dmub_notify_callback(adev, + DMUB_NOTIFICATION_AUX_REPLY, NULL, false)); +} + +/** + * dm_test_register_dmub_notify_callback_type_out_of_range - Test out-of-range type is rejected + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_type_out_of_range(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_FALSE(test, dm_register_dmub_notify_callback(adev, + AMDGPU_DMUB_NOTIFICATION_MAX, dummy_callback, false)); +} + +/** + * dm_test_register_dmub_notify_callback_valid - Test Register dmub notify callback valid + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_valid(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_TRUE(test, dm_register_dmub_notify_callback(adev, + DMUB_NOTIFICATION_AUX_REPLY, dummy_callback, true)); + + KUNIT_EXPECT_TRUE(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_AUX_REPLY] == dummy_callback); + KUNIT_EXPECT_TRUE(test, + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_AUX_REPLY]); +} + +/** + * dm_test_register_dmub_notify_callback_offload_false - Test registration with offload disabled + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_offload_false(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_TRUE(test, dm_register_dmub_notify_callback(adev, + DMUB_NOTIFICATION_HPD, dummy_callback, false)); + + KUNIT_EXPECT_TRUE(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD] == dummy_callback); + KUNIT_EXPECT_FALSE(test, + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_HPD]); +} + +/* Tests for dm_dmub_aux_setconfig_callback() */ + +/** + * dm_test_dmub_aux_setconfig_callback_copies_and_completes - Test copy and complete on AUX reply + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_copies_and_completes(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification *dm_notify; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = dm_notify; + + notify.type = DMUB_NOTIFICATION_AUX_REPLY; + notify.result = AUX_RET_SUCCESS; + notify.aux_reply.command = 0xA5; + notify.aux_reply.length = 3; + notify.aux_reply.data[0] = 0x11; + notify.aux_reply.data[1] = 0x22; + notify.aux_reply.data[2] = 0x33; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type); + KUNIT_EXPECT_EQ(test, dm_notify->result, notify.result); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.command, notify.aux_reply.command); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.length, notify.aux_reply.length); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[0], notify.aux_reply.data[0]); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[1], notify.aux_reply.data[1]); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[2], notify.aux_reply.data[2]); + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/** + * dm_test_dmub_aux_setconfig_callback_non_aux_no_complete - Test non-AUX type skips completion + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_non_aux_no_complete(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification *dm_notify; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = dm_notify; + + notify.type = DMUB_NOTIFICATION_HPD; + notify.result = AUX_RET_ERROR_TIMEOUT; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type); + KUNIT_EXPECT_FALSE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/** + * dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify - Test AUX with NULL dm_notify + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = NULL; + + notify.type = DMUB_NOTIFICATION_AUX_REPLY; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/** + * dm_test_dmub_aux_setconfig_callback_set_config_reply - Test SET_CONFIG reply copies status + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_set_config_reply(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification *dm_notify; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = dm_notify; + + notify.type = DMUB_NOTIFICATION_SET_CONFIG_REPLY; + notify.sc_status = SET_CONFIG_RX_TIMEOUT; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type); + KUNIT_EXPECT_EQ(test, dm_notify->sc_status, notify.sc_status); + KUNIT_EXPECT_FALSE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/* Tests for dm_dmub_aux_fused_io_callback() */ + +/** + * dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes - Test copy and complete + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification notify = {}; + struct dmub_cmd_fused_request *reply; + u32 reply_ddc_line; + u32 notify_ddc_line; + u32 reply_address; + u32 notify_address; + u32 reply_length; + u32 notify_length; + uint8_t ddc_line = 2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + init_completion(&adev->dm.fused_io[ddc_line].replied); + + notify.fused_request.identifier = 0x34; + notify.fused_request.status = FUSED_REQUEST_STATUS_SUCCESS; + notify.fused_request.u.aux.ddc_line = ddc_line; + notify.fused_request.u.aux.address = 0x50; + notify.fused_request.u.aux.length = 4; + + dm_dmub_aux_fused_io_callback(adev, ¬ify); + + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.fused_io[ddc_line].replied)); + + reply = (struct dmub_cmd_fused_request *)adev->dm.fused_io[ddc_line].reply_data; + reply_ddc_line = reply->u.aux.ddc_line; + notify_ddc_line = notify.fused_request.u.aux.ddc_line; + reply_address = reply->u.aux.address; + notify_address = notify.fused_request.u.aux.address; + reply_length = reply->u.aux.length; + notify_length = notify.fused_request.u.aux.length; + + KUNIT_EXPECT_EQ(test, reply->identifier, notify.fused_request.identifier); + KUNIT_EXPECT_EQ(test, reply->status, notify.fused_request.status); + KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line); + KUNIT_EXPECT_EQ(test, reply_address, notify_address); + KUNIT_EXPECT_EQ(test, reply_length, notify_length); +} + +/** + * dm_test_dmub_aux_fused_io_callback_max_ddc_line - Test Dmub aux fused io callback max ddc line + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_fused_io_callback_max_ddc_line(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification notify = {}; + struct dmub_cmd_fused_request *reply; + u32 reply_ddc_line; + u32 notify_ddc_line; + uint8_t ddc_line; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ddc_line = ARRAY_SIZE(adev->dm.fused_io) - 1; + init_completion(&adev->dm.fused_io[ddc_line].replied); + + notify.fused_request.identifier = 0x56; + notify.fused_request.status = FUSED_REQUEST_STATUS_SUCCESS; + notify.fused_request.u.aux.ddc_line = ddc_line; + notify.fused_request.u.aux.address = 0x50; + notify.fused_request.u.aux.length = 1; + + dm_dmub_aux_fused_io_callback(adev, ¬ify); + + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.fused_io[ddc_line].replied)); + + reply = (struct dmub_cmd_fused_request *)adev->dm.fused_io[ddc_line].reply_data; + reply_ddc_line = reply->u.aux.ddc_line; + notify_ddc_line = notify.fused_request.u.aux.ddc_line; + + KUNIT_EXPECT_EQ(test, reply->identifier, notify.fused_request.identifier); + KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line); +} + +/* Tests for dm_get_default_ips_mode() */ + +/** + * dm_test_get_default_ips_mode_dcn35 - Test Get default ips mode dcn35 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn35(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_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); +} + +/** + * dm_test_get_default_ips_mode_dcn351 - Test Get default ips mode dcn351 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn351(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, 1); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); +} + +/** + * dm_test_get_default_ips_mode_dcn36 - Test Get default ips mode dcn36 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn36(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, 6, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); +} + +/** + * dm_test_get_default_ips_mode_dcn42 - Test Get default ips mode dcn42 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn42(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(4, 2, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_DISABLE_ALL); +} + +/** + * dm_test_get_default_ips_mode_older_than_dcn35 - Test Get default ips mode older than dcn35 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_older_than_dcn35(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, 2, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_DISABLE_ALL); +} + +/** + * dm_test_get_default_ips_mode_newer_default - Test Get default ips mode newer default + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_newer_default(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* DCN 4.0.1 is >= 3.5 but has no explicit case, returns ENABLE */ + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 1); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_ENABLE); +} + +/* Tests for dm_dmub_hw_init() */ + +/* + * Build an amdgpu_device with the minimal dc/res_pool pointers that + * dm_dmub_hw_init() and dm_dmub_hw_resume() dereference before their + * early-return checks. + */ +static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct resource_pool *res_pool; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + + dc->res_pool = res_pool; + adev->dm.dc = dc; + + return adev; +} + +/** + * dm_test_dmub_hw_init_no_dmub_srv - Test hw init returns 0 when DMUB unsupported + * @test: The KUnit test context + * + * When adev->dm.dmub_srv is NULL the ASIC does not support DMUB and + * dm_dmub_hw_init() should return 0 without touching the hardware. + */ +static void dm_test_dmub_hw_init_no_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = NULL; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); +} + +/** + * dm_test_dmub_hw_init_no_fb_info - Test hw init fails without framebuffer info + * @test: The KUnit test context + * + * With a DMUB service present but no framebuffer info, dm_dmub_hw_init() + * should return -EINVAL. + */ +static void dm_test_dmub_hw_init_no_fb_info(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(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); + + adev->dm.dmub_srv = dmub_srv; + adev->dm.dmub_fb_info = NULL; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); +} + +/** + * dm_test_dmub_hw_init_no_firmware - Test hw init fails without firmware + * @test: The KUnit test context + * + * With a DMUB service and framebuffer info present but no firmware, + * dm_dmub_hw_init() should return -EINVAL. + */ +static void dm_test_dmub_hw_init_no_firmware(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + struct dmub_srv *dmub_srv; + struct dmub_srv_fb_info *fb_info; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + fb_info = kunit_kzalloc(test, sizeof(*fb_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info); + + adev->dm.dmub_srv = dmub_srv; + adev->dm.dmub_fb_info = fb_info; + adev->dm.dmub_fw = NULL; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); +} + +/* Tests for dm_dmub_hw_resume() */ + +/** + * dm_test_dmub_hw_resume_no_dmub_srv - Test hw resume is a no-op when DMUB unsupported + * @test: The KUnit test context + * + * When adev->dm.dmub_srv is NULL, dm_dmub_hw_resume() should return early + * without dereferencing the (absent) DMUB service. + */ +static void dm_test_dmub_hw_resume_no_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = NULL; + + /* Must not crash. */ + dm_dmub_hw_resume(adev); +} + +/* Tests for dm_dmub_sw_init() */ + +/** + * dm_test_dmub_sw_init_unsupported_asic - Test sw init returns 0 for unsupported ASIC + * @test: The KUnit test context + * + * For an IP version with no DMUB support, dm_dmub_sw_init() should return 0 + * before attempting to access the firmware. + */ +static void dm_test_dmub_sw_init_unsupported_asic(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(1, 0, 0); + + KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), 0); +} + +/* Tests for dm_init_microcode() */ + +/** + * dm_test_init_microcode_unsupported_asic - Test microcode init returns 0 for unsupported ASIC + * @test: The KUnit test context + * + * For an IP version with no DMUB support, dm_init_microcode() should return 0 + * without requesting any firmware. + */ +static void dm_test_init_microcode_unsupported_asic(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(1, 0, 0); + + KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0); +} + +static struct kunit_case amdgpu_dm_dmub_tests[] = { + /* dm_register_dmub_notify_callback() */ + KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback), + KUNIT_CASE(dm_test_register_dmub_notify_callback_type_out_of_range), + KUNIT_CASE(dm_test_register_dmub_notify_callback_valid), + KUNIT_CASE(dm_test_register_dmub_notify_callback_offload_false), + /* dm_dmub_aux_setconfig_callback() */ + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_copies_and_completes), + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_non_aux_no_complete), + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify), + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_set_config_reply), + /* 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), + /* dm_get_default_ips_mode() */ + KUNIT_CASE(dm_test_get_default_ips_mode_dcn35), + KUNIT_CASE(dm_test_get_default_ips_mode_dcn351), + KUNIT_CASE(dm_test_get_default_ips_mode_dcn36), + KUNIT_CASE(dm_test_get_default_ips_mode_dcn42), + KUNIT_CASE(dm_test_get_default_ips_mode_older_than_dcn35), + KUNIT_CASE(dm_test_get_default_ips_mode_newer_default), + /* dm_dmub_hw_init() */ + 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), + /* dm_dmub_hw_resume() */ + KUNIT_CASE(dm_test_dmub_hw_resume_no_dmub_srv), + /* dm_dmub_sw_init() */ + KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), + /* dm_init_microcode() */ + KUNIT_CASE(dm_test_init_microcode_unsupported_asic), + {} +}; + +static struct kunit_suite amdgpu_dm_dmub_test_suite = { + .name = "amdgpu_dm_dmub", + .test_cases = amdgpu_dm_dmub_tests, +}; + +kunit_test_suite(amdgpu_dm_dmub_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_dmub"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From a895eb57a55f7a8ad42d5bf14165549fb844394d Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 22:16:11 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_connector Add KUnit tests for helper functions in amdgpu_dm_connector.c, including both pure helper tests and DRM mock-based tests. Tests cover: - get_subconnector_type(): all dongle types and unknown default - get_output_content_type(): all content type mappings and unknown default - adjust_colour_depth_from_display_info(): depth reduction from 12bpc to 10bpc, 16bpc no-fallback, YCbCr420 clock halving, and no-fit rejection - get_output_color_space(): RGB full/limited, YCbCr default 709/601, BT601/709 with Y_ONLY, OPRGB, BT2020 RGB/YCC paths - convert_dc_color_depth_into_bpc(): all depths and undefined default - convert_color_depth_from_display_info(): non-Y420 bpc values, Y420 default/10/12/16bpc, requested odd bpc rounding, unsupported bpc, and requested_bpc capping - to_drm_connector_type(): HDMI, eDP, LVDS, RGB, DP/MST, DVI single and dual link DVII/DVID, virtual, and unknown - is_duplicate_mode(): empty list, match, no-match, and same-size different-clock cases - amdgpu_dm_get_encoder_crtc_mask(): 1-6 CRTCs and default - get_aspect_ratio(): all HDMI picture aspect ratios - decide_crtc_timing_for_drm_display_mode(): scale enabled, matching mode, no copy, and no crtc_clock cases - amdgpu_dm_connector_funcs_reset(): default fields, eDP ABM level set, and eDP ABM disabled - amdgpu_dm_connector_atomic_duplicate_state(): field copy verification - amdgpu_dm_fill_hdr_info_packet(): null metadata early return and output zeroing - amdgpu_dm_connector_atomic_set_property(): scaling center/aspect/ fullscreen/none/unchanged, underscan hborder/vborder/enable, abm sysfs control/level off/level value, and unknown property -EINVAL - amdgpu_dm_connector_atomic_get_property(): scaling center/aspect/ full/off, underscan borders, abm sysfs allowed/level/disabled, and unknown property -EINVAL - amdgpu_dm_get_highest_refresh_rate_mode(): null writeback, cached base mode, and preferred mode selection - amdgpu_dm_is_freesync_video_mode(): null mode, match, and no-match cases Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 33 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 15 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 2142 ++++++++++++++++++++ 4 files changed, 2184 insertions(+), 7 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c 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 df09627f4c04..27f8fb2e8c12 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,6 +42,7 @@ #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" @@ -188,6 +189,7 @@ int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev) return 0x3f; } } +EXPORT_IF_KUNIT(amdgpu_dm_get_encoder_crtc_mask); int amdgpu_dm_encoder_init(struct drm_device *dev, struct amdgpu_encoder *aencoder, @@ -213,7 +215,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, return res; } -static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) +STATIC_IFN_KUNIT enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) { switch (link->dpcd_caps.dongle_type) { case DISPLAY_DONGLE_NONE: @@ -231,6 +233,7 @@ static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) return DRM_MODE_SUBCONNECTOR_Unknown; } } +EXPORT_IF_KUNIT(get_subconnector_type); static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) { @@ -662,13 +665,15 @@ amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *conn return COLOR_DEPTH_UNDEFINED; } } +EXPORT_IF_KUNIT(amdgpu_dm_convert_color_depth_from_display_info); -static enum dc_aspect_ratio +STATIC_IFN_KUNIT enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in) { /* 1-1 mapping, since both enums follow the HDMI spec. */ return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio; } +EXPORT_IF_KUNIT(get_aspect_ratio); enum dc_color_space amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, @@ -728,8 +733,9 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, return color_space; } +EXPORT_IF_KUNIT(amdgpu_dm_get_output_color_space); -static enum display_content_type +STATIC_IFN_KUNIT enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state) { switch (connector_state->content_type) { @@ -746,8 +752,9 @@ get_output_content_type(const struct drm_connector_state *connector_state) return DISPLAY_CONTENT_TYPE_GAME; } } +EXPORT_IF_KUNIT(get_output_content_type); -static bool adjust_colour_depth_from_display_info( +STATIC_IFN_KUNIT bool adjust_colour_depth_from_display_info( struct dc_crtc_timing *timing_out, const struct drm_display_info *info) { @@ -783,6 +790,7 @@ static bool adjust_colour_depth_from_display_info( } while (--depth > COLOR_DEPTH_666); return false; } +EXPORT_IF_KUNIT(adjust_colour_depth_from_display_info); static void fill_stream_properties_from_drm_display_mode( struct dc_stream_state *stream, @@ -932,7 +940,7 @@ copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, dst_mode->crtc_vtotal = src_mode->crtc_vtotal; } -static void +STATIC_IFN_KUNIT void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled) @@ -947,6 +955,7 @@ decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, /* no scaling nor amdgpu inserted, no need to patch */ } } +EXPORT_IF_KUNIT(decide_crtc_timing_for_drm_display_mode); static struct dc_sink * create_fake_sink(struct drm_device *dev, struct dc_link *link) @@ -1052,6 +1061,7 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, drm_mode_copy(&aconnector->freesync_vid_base, m_pref); return m_pref; } +EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode); bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, struct amdgpu_dm_connector *aconnector) @@ -1079,6 +1089,7 @@ bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, else return true; } +EXPORT_IF_KUNIT(amdgpu_dm_is_freesync_video_mode); #if defined(CONFIG_DRM_AMD_DC_FP) static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, @@ -1667,6 +1678,7 @@ int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_set_property); int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, const struct drm_connector_state *state, @@ -1716,6 +1728,7 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_get_property); static void amdgpu_dm_connector_unregister(struct drm_connector *connector) { @@ -1801,6 +1814,7 @@ void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) __drm_atomic_helper_connector_reset(connector, &state->base); } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_reset); struct drm_connector_state * amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) @@ -1826,6 +1840,7 @@ amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) new_state->pbn = state->pbn; return &new_state->base; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_duplicate_state); static int amdgpu_dm_connector_late_register(struct drm_connector *connector) @@ -2253,6 +2268,7 @@ int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_fill_hdr_info_packet); static int amdgpu_dm_connector_atomic_check(struct drm_connector *conn, @@ -2368,8 +2384,9 @@ int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_ } return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_convert_dc_color_depth_into_bpc); -static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) +STATIC_IFN_KUNIT int to_drm_connector_type(enum signal_type st, uint32_t connector_id) { switch (st) { case SIGNAL_TYPE_HDMI_TYPE_A: @@ -2403,6 +2420,7 @@ static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) return DRM_MODE_CONNECTOR_Unknown; } } +EXPORT_IF_KUNIT(to_drm_connector_type); static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) { @@ -2598,7 +2616,7 @@ static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, } } -static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode) { struct drm_display_mode *m; @@ -2610,6 +2628,7 @@ static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, return false; } +EXPORT_IF_KUNIT(is_duplicate_mode); static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) { 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 db8e5588dbfd..c5b8b13f8f06 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 @@ -34,6 +34,7 @@ struct amdgpu_encoder; struct amdgpu_i2c_adapter; struct dc_crtc_timing; struct dc_link; +enum signal_type; struct dc_state; struct dc_stream_state; struct ddc_service; @@ -144,4 +145,18 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, struct amdgpu_encoder *aencoder, uint32_t link_index); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); +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, + const struct drm_display_info *info); + +int to_drm_connector_type(enum signal_type st, uint32_t connector_id); +bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode); +enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in); +void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, + const struct drm_display_mode *native_mode, + bool scale_enabled); +#endif #endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4bd8d1fa0fee..422eef0bfe49 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_connector_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o 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 new file mode 100644 index 000000000000..34e40d2a9d2c --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -0,0 +1,2142 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_connector.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_connector.h" +#include "amdgpu_dm_backlight.h" +#include "include/grph_object_id.h" + +/* Tests for get_subconnector_type() */ + +/** + * dm_test_subconnector_type_none - Test Subconnector type none + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_none(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Native); +} + +/** + * dm_test_subconnector_type_vga - Test Subconnector type vga + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_vga(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_VGA); +} + +/** + * dm_test_subconnector_type_dvi_converter - Test Subconnector type dvi converter + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_dvi_converter(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); +} + +/** + * dm_test_subconnector_type_dvi_dongle - Test Subconnector type dvi dongle + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_dvi_dongle(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); +} + +/** + * dm_test_subconnector_type_hdmi_converter - Test Subconnector type hdmi converter + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_hdmi_converter(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); +} + +/** + * dm_test_subconnector_type_hdmi_dongle - Test Subconnector type hdmi dongle + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); +} + +/** + * dm_test_subconnector_type_mismatched - Test Subconnector type mismatched + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_mismatched(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); +} + +/** + * dm_test_subconnector_type_default_unknown - Test Subconnector type default unknown + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_default_unknown(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); +} + +/* Tests for get_output_content_type() */ + +/** + * dm_test_content_type_no_data - Test Content type no data + * @test: The KUnit test context + */ +static void dm_test_content_type_no_data(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_NO_DATA; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_NO_DATA); +} + +/** + * dm_test_content_type_graphics - Test Content type graphics + * @test: The KUnit test context + */ +static void dm_test_content_type_graphics(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_GRAPHICS); +} + +/** + * dm_test_content_type_photo - Test Content type photo + * @test: The KUnit test context + */ +static void dm_test_content_type_photo(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_PHOTO; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_PHOTO); +} + +/** + * dm_test_content_type_cinema - Test Content type cinema + * @test: The KUnit test context + */ +static void dm_test_content_type_cinema(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_CINEMA; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_CINEMA); +} + +/** + * dm_test_content_type_game - Test Content type game + * @test: The KUnit test context + */ +static void dm_test_content_type_game(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_GAME; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_GAME); +} + +/** + * dm_test_content_type_unknown_defaults_no_data - Test unknown content type defaults to no data + * @test: The KUnit test context + */ +static void dm_test_content_type_unknown_defaults_no_data(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = 0x7f; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), + (int)DISPLAY_CONTENT_TYPE_NO_DATA); +} + +/* Tests for adjust_colour_depth_from_display_info() */ + +/** + * dm_test_adjust_colour_depth_fits_at_888 - Test Adjust colour depth fits at 888 + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_fits_at_888(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 1080p @ 148500 KHz = 1485000 in 100Hz units */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_888; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 150000; /* 150 MHz */ + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_888); +} + +/** + * dm_test_adjust_colour_depth_reduces_to_888 - Test Adjust colour depth reduces to 888 + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_reduces_to_888(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* Request 10bpc but TMDS limit only allows 8bpc */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_101010; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + /* 10bpc would need 148500*30/24 = 185625 KHz, exceeds limit */ + info.max_tmds_clock = 160000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_888); +} + +/** + * dm_test_adjust_colour_depth_10bpc_passes - Test Adjust colour depth 10bpc passes + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_10bpc_passes(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_101010; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + /* 10bpc needs 185625 KHz, allow it */ + info.max_tmds_clock = 200000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_adjust_colour_depth_420_halves_clk - Test Adjust colour depth 420 halves clk + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_420_halves_clk(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 4K @ 594000 KHz = 5940000 in 100Hz units */ + timing.pix_clk_100hz = 5940000; + timing.display_color_depth = COLOR_DEPTH_101010; + timing.pixel_encoding = PIXEL_ENCODING_YCBCR420; + /* With 420: effective = 594000/2 = 297000, 10bpc = 297000*30/24 = 371250 */ + info.max_tmds_clock = 400000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc - Test Adjust colour + * depth reduces 12bpc to 10bpc + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_121212; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 190000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_adjust_colour_depth_16bpc_no_fallback - Test Adjust colour depth + * 16bpc cannot fall back + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_16bpc_no_fallback(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 16bpc that exceeds limit cannot reduce because the next enum + * value (COLOR_DEPTH_141414) is not a valid HDMI depth. + */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_161616; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 230000; + + KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info)); +} + +/** + * dm_test_adjust_colour_depth_none_fits - Test Adjust colour depth none fits + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_none_fits(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* Even 8bpc doesn't fit */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_888; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 100000; /* Too low */ + + KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info)); +} + +/** + * dm_test_adjust_colour_depth_invalid_depth - Test Adjust colour depth invalid depth + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_invalid_depth(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_141414; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 400000; + + KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_141414); +} + +/* Tests for amdgpu_dm_get_output_color_space() */ + +/** + * dm_test_output_color_space_default_rgb_full - Test Output color space default rgb full + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_full(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_limited - Test Output color space default rgb limited + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB_LIMITED); +} + +/** + * dm_test_output_color_space_default_ycbcr709 - Test Output color space default ycbcr709 + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr709(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 300000; + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709); +} + +/** + * dm_test_output_color_space_default_ycbcr601_limited - Test Output color space + * default ycbcr601 limited + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr601_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 270300; + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601_LIMITED); +} + +/** + * dm_test_output_color_space_bt601_y_only - Test Output color space bt601 y only + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt601_y_only(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_BT601_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601_LIMITED); +} + +/** + * dm_test_output_color_space_bt601 - Test Output color space bt601 + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt601(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_BT601_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601); +} + +/** + * dm_test_output_color_space_bt709 - Test Output color space bt709 + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt709(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_BT709_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709); +} + +/** + * dm_test_output_color_space_bt709_y_only - Test Output color space bt709 y only + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt709_y_only(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_BT709_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709_LIMITED); +} + +/** + * dm_test_output_color_space_oprgb - Test Output color space oprgb + * @test: The KUnit test context + */ +static void dm_test_output_color_space_oprgb(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + state.colorspace = DRM_MODE_COLORIMETRY_OPRGB; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_ADOBERGB); +} + +/** + * dm_test_output_color_space_bt2020_rgb - Test Output color space bt2020 rgb + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_FULLRANGE); +} + +/** + * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_ycc(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR422; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_YCBCR_LIMITED); +} + +/* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc() */ + +/** + * dm_test_convert_color_depth_bpc_mappings - Test Convert color depth bpc mappings + * @test: The KUnit test context + */ +static void dm_test_convert_color_depth_bpc_mappings(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_666), 6); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_888), 8); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_101010), 10); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_121212), 12); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_141414), 14); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_161616), 16); +} + +/** + * dm_test_convert_color_depth_bpc_unknown - Test Convert color depth bpc unknown + * @test: The KUnit test context + */ +static void dm_test_convert_color_depth_bpc_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_UNDEFINED), 0); +} + +/* Tests for amdgpu_dm_convert_color_depth_from_display_info() */ + +/** + * dm_test_color_depth_from_info_bpc8 - Test Color depth from info bpc8 + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_bpc8(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 8; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_color_depth_from_info_bpc10 - Test Color depth from info bpc10 + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_bpc10(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 10; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_zero_bpc_defaults_888 - Test Color depth from + * info zero bpc defaults 888 + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_zero_bpc_defaults_888(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 0; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_color_depth_from_info_requested_bpc_caps - Test Color depth from info requested bpc caps + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_requested_bpc_caps(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + /* Display supports 12bpc but user requests max 10 */ + connector->display_info.bpc = 12; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 10), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_y420_default - Test Color depth from info y420 default + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_default(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + /* No Y420 DC modes set → 8bpc */ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_color_depth_from_info_y420_10bpc - Test Color depth from info y420 10bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_10bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_30; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_y420_12bpc - Test Color depth from info y420 12bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_12bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_36; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_121212); +} + +/** + * dm_test_color_depth_from_info_y420_16bpc - Test Color depth from info y420 16bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_16bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_48; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_161616); +} + +/** + * dm_test_color_depth_from_info_requested_odd_bpc - Test Color depth from info requested odd bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_requested_odd_bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 12; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 11), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_unsupported_bpc - Test Color depth from info unsupported bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_unsupported_bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 9; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_UNDEFINED); +} + +/* Tests for to_drm_connector_type() */ + +/** + * dm_test_to_connector_type_hdmi - Test To connector type hdmi + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_hdmi(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_HDMI_TYPE_A, 0), + DRM_MODE_CONNECTOR_HDMIA); +} + +/** + * dm_test_to_connector_type_edp - Test To connector type edp + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_edp(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_EDP, 0), + DRM_MODE_CONNECTOR_eDP); +} + +/** + * dm_test_to_connector_type_lvds - Test To connector type lvds + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_lvds(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_LVDS, 0), + DRM_MODE_CONNECTOR_LVDS); +} + +/** + * dm_test_to_connector_type_rgb - Test To connector type rgb + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_rgb(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_RGB, 0), + DRM_MODE_CONNECTOR_VGA); +} + +/** + * dm_test_to_connector_type_dp - Test To connector type dp + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dp(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_DISPLAY_PORT, 0), + DRM_MODE_CONNECTOR_DisplayPort); +} + +/** + * dm_test_to_connector_type_dp_mst - Test To connector type dp mst + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dp_mst(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_DISPLAY_PORT_MST, 0), + DRM_MODE_CONNECTOR_DisplayPort); +} + +/** + * dm_test_to_connector_type_dvi_dvii - Test To connector type dvi dvii + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dvi_dvii(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_SINGLE_LINK, CONNECTOR_ID_SINGLE_LINK_DVII); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVII); +} + +/** + * dm_test_to_connector_type_dual_link_dvii - Test To connector type dual link dvii + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dual_link_dvii(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_DUAL_LINK, CONNECTOR_ID_DUAL_LINK_DVII); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVII); +} + +/** + * dm_test_to_connector_type_dvi_dvid - Test To connector type dvi dvid + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dvi_dvid(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_SINGLE_LINK, CONNECTOR_ID_SINGLE_LINK_DVID); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID); +} + +/** + * dm_test_to_connector_type_virtual - Test To connector type virtual + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_virtual(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_VIRTUAL, 0), + DRM_MODE_CONNECTOR_VIRTUAL); +} + +/** + * dm_test_to_connector_type_unknown - Test To connector type unknown + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_NONE, 0), + DRM_MODE_CONNECTOR_Unknown); +} + +/* Tests for is_duplicate_mode() */ + +/** + * dm_test_is_duplicate_mode_empty_list - Test Is duplicate mode empty list + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_empty_list(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode mode = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + mode.hdisplay = 1920; + mode.vdisplay = 1080; + + KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &mode)); +} + +/** + * dm_test_is_duplicate_mode_match - Test Is duplicate mode match + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode existing = {}; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + existing.hdisplay = 1920; + existing.vdisplay = 1080; + existing.clock = 148500; + list_add_tail(&existing.head, &aconnector->base.probed_modes); + + candidate.hdisplay = 1920; + candidate.vdisplay = 1080; + candidate.clock = 148500; + + KUNIT_EXPECT_TRUE(test, is_duplicate_mode(aconnector, &candidate)); +} + +/** + * dm_test_is_duplicate_mode_no_match - Test Is duplicate mode no match + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_no_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode existing = {}; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + existing.hdisplay = 1920; + existing.vdisplay = 1080; + existing.clock = 148500; + list_add_tail(&existing.head, &aconnector->base.probed_modes); + + candidate.hdisplay = 2560; + candidate.vdisplay = 1440; + candidate.clock = 241500; + + KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate)); +} + +/** + * dm_test_is_duplicate_mode_same_size_different_clock - Test Is duplicate mode + * same size different clock + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_same_size_different_clock(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode existing = {}; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + existing.hdisplay = 1920; + existing.vdisplay = 1080; + existing.clock = 148500; + list_add_tail(&existing.head, &aconnector->base.probed_modes); + + candidate.hdisplay = 1920; + candidate.vdisplay = 1080; + candidate.clock = 74250; + + KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate)); +} + +/* Tests for amdgpu_dm_get_encoder_crtc_mask() */ + +/** + * dm_test_encoder_crtc_mask_1 - Test Encoder crtc mask 1 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_1(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 1; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x1); +} + +/** + * dm_test_encoder_crtc_mask_2 - Test Encoder crtc mask 2 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_2(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 2; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3); +} + +/** + * dm_test_encoder_crtc_mask_3 - Test Encoder crtc mask 3 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_3(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 3; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x7); +} + +/** + * dm_test_encoder_crtc_mask_4 - Test Encoder crtc mask 4 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_4(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 4; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0xf); +} + +/** + * dm_test_encoder_crtc_mask_5 - Test Encoder crtc mask 5 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_5(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 5; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x1f); +} + +/** + * dm_test_encoder_crtc_mask_6 - Test Encoder crtc mask 6 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_6(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 6; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3f); +} + +/** + * dm_test_encoder_crtc_mask_default - Test Encoder crtc mask default + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_default(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Values > 6 use the default case */ + adev->mode_info.num_crtc = 8; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3f); +} + +/* Tests for get_aspect_ratio() */ + +/** + * dm_test_aspect_ratio_no_data - Test Aspect ratio no data + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_no_data(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_NO_DATA); +} + +/** + * dm_test_aspect_ratio_4_3 - Test Aspect ratio 4 3 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_4_3(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_4_3); +} + +/** + * dm_test_aspect_ratio_16_9 - Test Aspect ratio 16 9 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_16_9(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_16_9); +} + +/** + * dm_test_aspect_ratio_64_27 - Test Aspect ratio 64 27 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_64_27(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_64_27); +} + +/** + * dm_test_aspect_ratio_256_135 - Test Aspect ratio 256 135 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_256_135(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_256_135); +} + +/* Tests for decide_crtc_timing_for_drm_display_mode() */ + +/** + * dm_test_decide_crtc_timing_scale_enabled - Test Decide crtc timing scale enabled + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_scale_enabled(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + native_mode.crtc_clock = 148500; + native_mode.crtc_hdisplay = 1920; + native_mode.crtc_vdisplay = 1080; + native_mode.crtc_htotal = 2200; + native_mode.crtc_vtotal = 1125; + native_mode.crtc_hsync_start = 2008; + native_mode.crtc_hsync_end = 2052; + native_mode.crtc_vsync_start = 1084; + native_mode.crtc_vsync_end = 1089; + + /* Different clock/htotal/vtotal, but scale_enabled forces copy */ + drm_mode.clock = 74250; + drm_mode.htotal = 1650; + drm_mode.vtotal = 750; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, true); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 148500); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 1920); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vdisplay, 1080); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_htotal, 2200); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vtotal, 1125); +} + +/** + * dm_test_decide_crtc_timing_matching_mode - Test Decide crtc timing matching mode + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_matching_mode(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + native_mode.clock = 148500; + native_mode.htotal = 2200; + native_mode.vtotal = 1125; + native_mode.crtc_clock = 148500; + native_mode.crtc_hdisplay = 1920; + native_mode.crtc_vdisplay = 1080; + native_mode.crtc_htotal = 2200; + native_mode.crtc_vtotal = 1125; + + /* Matching clock/htotal/vtotal triggers copy */ + drm_mode.clock = 148500; + drm_mode.htotal = 2200; + drm_mode.vtotal = 1125; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 148500); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 1920); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vtotal, 1125); +} + +/** + * dm_test_decide_crtc_timing_no_copy - Test Decide crtc timing no copy + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_no_copy(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + native_mode.clock = 148500; + native_mode.htotal = 2200; + native_mode.vtotal = 1125; + native_mode.crtc_clock = 148500; + native_mode.crtc_hdisplay = 1920; + + /* Different timings, no scaling → no copy */ + drm_mode.clock = 74250; + drm_mode.htotal = 1650; + drm_mode.vtotal = 750; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 0); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); +} + +/** + * dm_test_decide_crtc_timing_no_crtc_clock - Test Decide crtc timing no crtc clock + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_no_crtc_clock(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + /* Matching timings but native crtc_clock is 0 → no copy */ + native_mode.clock = 148500; + native_mode.htotal = 2200; + native_mode.vtotal = 1125; + native_mode.crtc_clock = 0; + native_mode.crtc_hdisplay = 1920; + + drm_mode.clock = 148500; + drm_mode.htotal = 2200; + drm_mode.vtotal = 1125; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 0); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); +} + +/* Tests for amdgpu_dm_connector_funcs_reset() */ + +static const struct drm_connector_funcs dm_test_connector_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, +}; + +/** + * dm_test_funcs_reset_sets_defaults - Test funcs_reset sets defaults + * @test: The KUnit test context + */ +static void dm_test_funcs_reset_sets_defaults(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + + 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(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + + amdgpu_dm_connector_funcs_reset(connector); + + KUNIT_ASSERT_NOT_NULL(test, connector->state); + dm_state = to_dm_connector_state(connector->state); + KUNIT_EXPECT_EQ(test, (int)dm_state->scaling, (int)RMX_OFF); + KUNIT_EXPECT_FALSE(test, dm_state->underscan_enable); + KUNIT_EXPECT_EQ(test, (int)dm_state->underscan_hborder, 0); + KUNIT_EXPECT_EQ(test, (int)dm_state->underscan_vborder, 0); + KUNIT_EXPECT_EQ(test, (int)dm_state->base.max_requested_bpc, 8); + KUNIT_EXPECT_EQ(test, dm_state->vcpi_slots, 0); + KUNIT_EXPECT_EQ(test, (int)dm_state->pbn, 0); +} + +/** + * dm_test_funcs_reset_edp_abm_level - Test funcs_reset eDP sets ABM + * @test: The KUnit test context + */ +static void dm_test_funcs_reset_edp_abm_level(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + 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(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + + /* Test with abm_level > 0 */ + amdgpu_dm_set_abm_level_param(3); + amdgpu_dm_connector_funcs_reset(connector); + + KUNIT_ASSERT_NOT_NULL(test, connector->state); + dm_state = to_dm_connector_state(connector->state); + KUNIT_EXPECT_EQ(test, (int)dm_state->abm_level, 3); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_funcs_reset_edp_abm_disabled - Test funcs_reset eDP ABM + * disabled + * @test: The KUnit test context + */ +static void dm_test_funcs_reset_edp_abm_disabled(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + 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(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + + /* Test with abm_level <= 0 → immediate disable */ + amdgpu_dm_set_abm_level_param(-1); + amdgpu_dm_connector_funcs_reset(connector); + + KUNIT_ASSERT_NOT_NULL(test, connector->state); + dm_state = to_dm_connector_state(connector->state); + KUNIT_EXPECT_EQ(test, (int)dm_state->abm_level, + (int)ABM_LEVEL_IMMEDIATE_DISABLE); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/* Tests for amdgpu_dm_connector_atomic_duplicate_state() */ + +/** + * dm_test_atomic_dup_state_copies_fields - Test atomic_duplicate copies + * fields + * @test: The KUnit test context + */ +static void dm_test_atomic_dup_state_copies_fields(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + struct dm_connector_state *new_dm_state; + struct drm_connector_state *new_state; + + 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(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, NULL); + + amdgpu_dm_connector_funcs_reset(connector); + KUNIT_ASSERT_NOT_NULL(test, connector->state); + + /* Modify original state fields */ + dm_state = to_dm_connector_state(connector->state); + dm_state->scaling = RMX_CENTER; + dm_state->underscan_enable = true; + dm_state->underscan_hborder = 10; + dm_state->underscan_vborder = 20; + dm_state->freesync_capable = true; + dm_state->abm_level = 2; + dm_state->vcpi_slots = 4; + dm_state->pbn = 1234; + + /* Duplicate */ + new_state = amdgpu_dm_connector_atomic_duplicate_state(connector); + KUNIT_ASSERT_NOT_NULL(test, new_state); + new_dm_state = to_dm_connector_state(new_state); + + /* Verify all fields copied */ + KUNIT_EXPECT_EQ(test, (int)new_dm_state->scaling, (int)RMX_CENTER); + KUNIT_EXPECT_TRUE(test, new_dm_state->underscan_enable); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->underscan_hborder, 10); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->underscan_vborder, 20); + KUNIT_EXPECT_TRUE(test, new_dm_state->freesync_capable); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->abm_level, 2); + KUNIT_EXPECT_EQ(test, new_dm_state->vcpi_slots, 4); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->pbn, 1234); + + kfree(new_dm_state); +} + +/* Tests for amdgpu_dm_fill_hdr_info_packet() */ + +/** + * dm_test_fill_hdr_null_metadata - Test fill_hdr returns 0 with no + * metadata + * @test: The KUnit test context + */ +static void dm_test_fill_hdr_null_metadata(struct kunit *test) +{ + struct drm_connector_state state = {}; + struct dc_info_packet out = {}; + + /* No hdr_output_metadata → early return 0, out stays zeroed */ + state.hdr_output_metadata = NULL; + KUNIT_EXPECT_EQ(test, amdgpu_dm_fill_hdr_info_packet(&state, &out), 0); + KUNIT_EXPECT_FALSE(test, out.valid); +} + +/** + * dm_test_fill_hdr_zeroes_output - Test fill_hdr zeroes output with no + * metadata + * @test: The KUnit test context + */ +static void dm_test_fill_hdr_zeroes_output(struct kunit *test) +{ + struct drm_connector_state state = {}; + struct dc_info_packet out; + + /* Pre-fill out with nonzero to verify memset(0) */ + memset(&out, 0xAA, sizeof(out)); + + state.hdr_output_metadata = NULL; + KUNIT_EXPECT_EQ(test, amdgpu_dm_fill_hdr_info_packet(&state, &out), 0); + KUNIT_EXPECT_FALSE(test, out.valid); + KUNIT_EXPECT_EQ(test, (int)out.hb0, 0); + KUNIT_EXPECT_EQ(test, (int)out.hb1, 0); + KUNIT_EXPECT_EQ(test, (int)out.hb2, 0); + KUNIT_EXPECT_EQ(test, (int)out.hb3, 0); +} + +/* Tests for amdgpu_dm_connector_atomic_set_property() */ + +/* + * Build a connector wired to a kunit-allocated amdgpu_device so that + * drm_to_adev() resolves correctly, together with old/new dm states and + * the set of properties used by the get/set property handlers. + */ +struct dm_test_prop_ctx { + struct amdgpu_device *adev; + struct drm_connector *connector; + struct dm_connector_state *old_state; + struct dm_connector_state *new_state; + struct drm_property *scaling_prop; + struct drm_property *hborder_prop; + struct drm_property *vborder_prop; + struct drm_property *underscan_prop; + struct drm_property *abm_prop; +}; + +static struct dm_test_prop_ctx *dm_test_prop_ctx_alloc(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->adev = kunit_kzalloc(test, sizeof(*ctx->adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->adev); + ctx->connector = kunit_kzalloc(test, sizeof(*ctx->connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->connector); + ctx->old_state = kunit_kzalloc(test, sizeof(*ctx->old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->old_state); + ctx->new_state = kunit_kzalloc(test, sizeof(*ctx->new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->new_state); + ctx->scaling_prop = kunit_kzalloc(test, sizeof(*ctx->scaling_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->scaling_prop); + ctx->hborder_prop = kunit_kzalloc(test, sizeof(*ctx->hborder_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->hborder_prop); + ctx->vborder_prop = kunit_kzalloc(test, sizeof(*ctx->vborder_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->vborder_prop); + ctx->underscan_prop = kunit_kzalloc(test, sizeof(*ctx->underscan_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->underscan_prop); + ctx->abm_prop = kunit_kzalloc(test, sizeof(*ctx->abm_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->abm_prop); + + ctx->connector->dev = &ctx->adev->ddev; + ctx->connector->state = &ctx->old_state->base; + + ctx->adev->ddev.mode_config.scaling_mode_property = ctx->scaling_prop; + ctx->adev->mode_info.underscan_hborder_property = ctx->hborder_prop; + ctx->adev->mode_info.underscan_vborder_property = ctx->vborder_prop; + ctx->adev->mode_info.underscan_property = ctx->underscan_prop; + ctx->adev->mode_info.abm_level_property = ctx->abm_prop; + + return ctx; +} + +/** + * dm_test_set_property_scaling_center - Test set scaling property to center + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_center(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_CENTER), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_CENTER); +} + +/** + * dm_test_set_property_scaling_aspect - Test set scaling property to aspect + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_aspect(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_ASPECT), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_ASPECT); +} + +/** + * dm_test_set_property_scaling_fullscreen - Test set scaling property to full + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_fullscreen(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_FULLSCREEN), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_FULL); +} + +/** + * dm_test_set_property_scaling_none - Test set scaling property to none + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_none(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + /* old scaling is RMX_CENTER so RMX_OFF is a real change */ + ctx->old_state->scaling = RMX_CENTER; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_NONE), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_OFF); +} + +/** + * dm_test_set_property_scaling_unchanged - Test set scaling property unchanged + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_unchanged(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + /* old already RMX_OFF, requesting NONE/OFF returns 0 without write */ + ctx->old_state->scaling = RMX_OFF; + ctx->new_state->scaling = RMX_CENTER; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_NONE), 0); + /* new_state untouched because of early return */ + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_CENTER); +} + +/** + * dm_test_set_property_underscan_hborder - Test set underscan hborder + * @test: The KUnit test context + */ +static void dm_test_set_property_underscan_hborder(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->hborder_prop, 42), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->underscan_hborder, 42); +} + +/** + * dm_test_set_property_underscan_vborder - Test set underscan vborder + * @test: The KUnit test context + */ +static void dm_test_set_property_underscan_vborder(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->vborder_prop, 24), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->underscan_vborder, 24); +} + +/** + * dm_test_set_property_underscan_enable - Test set underscan enable + * @test: The KUnit test context + */ +static void dm_test_set_property_underscan_enable(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->underscan_prop, 1), 0); + KUNIT_EXPECT_TRUE(test, ctx->new_state->underscan_enable); +} + +/** + * dm_test_set_property_abm_sysfs_control - Test set abm sysfs control + * @test: The KUnit test context + */ +static void dm_test_set_property_abm_sysfs_control(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + ctx->new_state->abm_sysfs_forbidden = true; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, ABM_SYSFS_CONTROL), 0); + KUNIT_EXPECT_FALSE(test, ctx->new_state->abm_sysfs_forbidden); +} + +/** + * dm_test_set_property_abm_level_off - Test set abm level off + * @test: The KUnit test context + */ +static void dm_test_set_property_abm_level_off(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, ABM_LEVEL_OFF), 0); + KUNIT_EXPECT_TRUE(test, ctx->new_state->abm_sysfs_forbidden); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->abm_level, + (int)ABM_LEVEL_IMMEDIATE_DISABLE); +} + +/** + * dm_test_set_property_abm_level_value - Test set abm level to a value + * @test: The KUnit test context + */ +static void dm_test_set_property_abm_level_value(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, 3), 0); + KUNIT_EXPECT_TRUE(test, ctx->new_state->abm_sysfs_forbidden); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->abm_level, 3); +} + +/** + * dm_test_set_property_unknown - Test set unknown property returns -EINVAL + * @test: The KUnit test context + */ +static void dm_test_set_property_unknown(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + struct drm_property *other; + + other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + other, 0), -EINVAL); +} + +/* Tests for amdgpu_dm_connector_atomic_get_property() */ + +/** + * dm_test_get_property_scaling_center - Test get scaling property center + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_center(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_CENTER; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_CENTER); +} + +/** + * dm_test_get_property_scaling_aspect - Test get scaling property aspect + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_aspect(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_ASPECT; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_ASPECT); +} + +/** + * dm_test_get_property_scaling_full - Test get scaling property fullscreen + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_full(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_FULL; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_FULLSCREEN); +} + +/** + * dm_test_get_property_scaling_off - Test get scaling property off/none + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_off(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_OFF; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_NONE); +} + +/** + * dm_test_get_property_underscan_borders - Test get underscan borders/enable + * @test: The KUnit test context + */ +static void dm_test_get_property_underscan_borders(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->underscan_hborder = 12; + ctx->new_state->underscan_vborder = 34; + ctx->new_state->underscan_enable = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->hborder_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 12); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->vborder_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 34); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->underscan_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 1); +} + +/** + * dm_test_get_property_abm_sysfs_allowed - Test get abm returns sysfs control + * @test: The KUnit test context + */ +static void dm_test_get_property_abm_sysfs_allowed(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->abm_sysfs_forbidden = false; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)ABM_SYSFS_CONTROL); +} + +/** + * dm_test_get_property_abm_level - Test get abm returns level when forbidden + * @test: The KUnit test context + */ +static void dm_test_get_property_abm_level(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->abm_sysfs_forbidden = true; + ctx->new_state->abm_level = 2; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 2); +} + +/** + * dm_test_get_property_abm_disabled_zero - Test get abm returns 0 when disabled + * @test: The KUnit test context + */ +static void dm_test_get_property_abm_disabled_zero(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0xdead; + + ctx->new_state->abm_sysfs_forbidden = true; + ctx->new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 0); +} + +/** + * dm_test_get_property_unknown - Test get unknown property returns -EINVAL + * @test: The KUnit test context + */ +static void dm_test_get_property_unknown(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + struct drm_property *other; + uint64_t val = 0; + + other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + other, &val), -EINVAL); +} + +/* Tests for amdgpu_dm_get_highest_refresh_rate_mode() */ + +/** + * dm_test_highest_refresh_writeback_null - Test writeback connector returns NULL + * @test: The KUnit test context + */ +static void dm_test_highest_refresh_writeback_null(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_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false)); +} + +/** + * dm_test_highest_refresh_cached_base - Test cached freesync_vid_base is returned + * @test: The KUnit test context + */ +static void dm_test_highest_refresh_cached_base(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_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + + KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false), + &aconnector->freesync_vid_base); +} + +/** + * dm_test_highest_refresh_preferred_mode - Test preferred mode is selected + * @test: The KUnit test context + */ +static void dm_test_highest_refresh_preferred_mode(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); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + INIT_LIST_HEAD(&aconnector->base.modes); + + mode->type = DRM_MODE_TYPE_PREFERRED; + mode->clock = 148500; + mode->hdisplay = 1920; + mode->vdisplay = 1080; + mode->htotal = 2200; + mode->vtotal = 1125; + list_add_tail(&mode->head, &aconnector->base.modes); + + KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false), + mode); +} + +/* Tests for amdgpu_dm_is_freesync_video_mode() */ + +/** + * dm_test_is_freesync_video_mode_null_mode - Test NULL mode returns false + * @test: The KUnit test context + */ +static void dm_test_is_freesync_video_mode_null_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_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector)); +} + +/** + * dm_test_is_freesync_video_mode_match - Test matching mode returns true + * @test: The KUnit test context + */ +static void dm_test_is_freesync_video_mode_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + /* Cached high mode acts as reference */ + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + aconnector->freesync_vid_base.hdisplay = 1920; + aconnector->freesync_vid_base.vdisplay = 1080; + aconnector->freesync_vid_base.hsync_start = 2008; + aconnector->freesync_vid_base.hsync_end = 2052; + aconnector->freesync_vid_base.htotal = 2200; + aconnector->freesync_vid_base.vsync_start = 1084; + aconnector->freesync_vid_base.vsync_end = 1089; + aconnector->freesync_vid_base.vtotal = 1125; + + candidate.clock = 148500; + candidate.hdisplay = 1920; + candidate.vdisplay = 1080; + candidate.hsync_start = 2008; + candidate.hsync_end = 2052; + candidate.htotal = 2200; + candidate.vsync_start = 1084; + candidate.vsync_end = 1089; + candidate.vtotal = 1125; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); +} + +/** + * dm_test_is_freesync_video_mode_no_match - Test mismatched mode returns false + * @test: The KUnit test context + */ +static void dm_test_is_freesync_video_mode_no_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + aconnector->freesync_vid_base.hdisplay = 1920; + aconnector->freesync_vid_base.vdisplay = 1080; + aconnector->freesync_vid_base.htotal = 2200; + aconnector->freesync_vid_base.vtotal = 1125; + + /* Different resolution → not a freesync video mode */ + candidate.clock = 148500; + candidate.hdisplay = 1280; + candidate.vdisplay = 720; + candidate.htotal = 1650; + candidate.vtotal = 750; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); +} + +static struct kunit_case amdgpu_dm_connector_tests[] = { + /* get_subconnector_type */ + KUNIT_CASE(dm_test_subconnector_type_none), + KUNIT_CASE(dm_test_subconnector_type_vga), + KUNIT_CASE(dm_test_subconnector_type_dvi_converter), + KUNIT_CASE(dm_test_subconnector_type_dvi_dongle), + KUNIT_CASE(dm_test_subconnector_type_hdmi_converter), + KUNIT_CASE(dm_test_subconnector_type_hdmi_dongle), + KUNIT_CASE(dm_test_subconnector_type_mismatched), + KUNIT_CASE(dm_test_subconnector_type_default_unknown), + /* get_output_content_type */ + KUNIT_CASE(dm_test_content_type_no_data), + KUNIT_CASE(dm_test_content_type_graphics), + KUNIT_CASE(dm_test_content_type_photo), + KUNIT_CASE(dm_test_content_type_cinema), + KUNIT_CASE(dm_test_content_type_game), + KUNIT_CASE(dm_test_content_type_unknown_defaults_no_data), + /* adjust_colour_depth_from_display_info */ + KUNIT_CASE(dm_test_adjust_colour_depth_fits_at_888), + KUNIT_CASE(dm_test_adjust_colour_depth_reduces_to_888), + KUNIT_CASE(dm_test_adjust_colour_depth_10bpc_passes), + KUNIT_CASE(dm_test_adjust_colour_depth_420_halves_clk), + KUNIT_CASE(dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc), + KUNIT_CASE(dm_test_adjust_colour_depth_16bpc_no_fallback), + KUNIT_CASE(dm_test_adjust_colour_depth_none_fits), + KUNIT_CASE(dm_test_adjust_colour_depth_invalid_depth), + /* amdgpu_dm_get_output_color_space */ + KUNIT_CASE(dm_test_output_color_space_default_rgb_full), + KUNIT_CASE(dm_test_output_color_space_default_rgb_limited), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr709), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr601_limited), + KUNIT_CASE(dm_test_output_color_space_bt601_y_only), + KUNIT_CASE(dm_test_output_color_space_bt601), + KUNIT_CASE(dm_test_output_color_space_bt709), + KUNIT_CASE(dm_test_output_color_space_bt709_y_only), + KUNIT_CASE(dm_test_output_color_space_oprgb), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), + KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), + /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */ + KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings), + KUNIT_CASE(dm_test_convert_color_depth_bpc_unknown), + /* amdgpu_dm_convert_color_depth_from_display_info */ + KUNIT_CASE(dm_test_color_depth_from_info_bpc8), + KUNIT_CASE(dm_test_color_depth_from_info_bpc10), + KUNIT_CASE(dm_test_color_depth_from_info_zero_bpc_defaults_888), + KUNIT_CASE(dm_test_color_depth_from_info_requested_bpc_caps), + KUNIT_CASE(dm_test_color_depth_from_info_y420_default), + KUNIT_CASE(dm_test_color_depth_from_info_y420_10bpc), + KUNIT_CASE(dm_test_color_depth_from_info_y420_12bpc), + KUNIT_CASE(dm_test_color_depth_from_info_y420_16bpc), + KUNIT_CASE(dm_test_color_depth_from_info_requested_odd_bpc), + KUNIT_CASE(dm_test_color_depth_from_info_unsupported_bpc), + /* to_drm_connector_type */ + KUNIT_CASE(dm_test_to_connector_type_hdmi), + KUNIT_CASE(dm_test_to_connector_type_edp), + KUNIT_CASE(dm_test_to_connector_type_lvds), + KUNIT_CASE(dm_test_to_connector_type_rgb), + KUNIT_CASE(dm_test_to_connector_type_dp), + KUNIT_CASE(dm_test_to_connector_type_dp_mst), + KUNIT_CASE(dm_test_to_connector_type_dvi_dvii), + KUNIT_CASE(dm_test_to_connector_type_dual_link_dvii), + KUNIT_CASE(dm_test_to_connector_type_dvi_dvid), + KUNIT_CASE(dm_test_to_connector_type_virtual), + KUNIT_CASE(dm_test_to_connector_type_unknown), + /* is_duplicate_mode */ + KUNIT_CASE(dm_test_is_duplicate_mode_empty_list), + KUNIT_CASE(dm_test_is_duplicate_mode_match), + KUNIT_CASE(dm_test_is_duplicate_mode_no_match), + KUNIT_CASE(dm_test_is_duplicate_mode_same_size_different_clock), + /* amdgpu_dm_get_encoder_crtc_mask */ + KUNIT_CASE(dm_test_encoder_crtc_mask_1), + KUNIT_CASE(dm_test_encoder_crtc_mask_2), + KUNIT_CASE(dm_test_encoder_crtc_mask_3), + KUNIT_CASE(dm_test_encoder_crtc_mask_4), + KUNIT_CASE(dm_test_encoder_crtc_mask_5), + KUNIT_CASE(dm_test_encoder_crtc_mask_6), + KUNIT_CASE(dm_test_encoder_crtc_mask_default), + /* get_aspect_ratio */ + KUNIT_CASE(dm_test_aspect_ratio_no_data), + KUNIT_CASE(dm_test_aspect_ratio_4_3), + KUNIT_CASE(dm_test_aspect_ratio_16_9), + KUNIT_CASE(dm_test_aspect_ratio_64_27), + KUNIT_CASE(dm_test_aspect_ratio_256_135), + /* decide_crtc_timing_for_drm_display_mode */ + KUNIT_CASE(dm_test_decide_crtc_timing_scale_enabled), + KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), + KUNIT_CASE(dm_test_decide_crtc_timing_no_copy), + KUNIT_CASE(dm_test_decide_crtc_timing_no_crtc_clock), + /* amdgpu_dm_connector_funcs_reset */ + KUNIT_CASE(dm_test_funcs_reset_sets_defaults), + KUNIT_CASE(dm_test_funcs_reset_edp_abm_level), + KUNIT_CASE(dm_test_funcs_reset_edp_abm_disabled), + /* amdgpu_dm_connector_atomic_duplicate_state */ + KUNIT_CASE(dm_test_atomic_dup_state_copies_fields), + /* amdgpu_dm_fill_hdr_info_packet */ + KUNIT_CASE(dm_test_fill_hdr_null_metadata), + KUNIT_CASE(dm_test_fill_hdr_zeroes_output), + /* amdgpu_dm_connector_atomic_set_property */ + KUNIT_CASE(dm_test_set_property_scaling_center), + KUNIT_CASE(dm_test_set_property_scaling_aspect), + KUNIT_CASE(dm_test_set_property_scaling_fullscreen), + KUNIT_CASE(dm_test_set_property_scaling_none), + KUNIT_CASE(dm_test_set_property_scaling_unchanged), + KUNIT_CASE(dm_test_set_property_underscan_hborder), + KUNIT_CASE(dm_test_set_property_underscan_vborder), + KUNIT_CASE(dm_test_set_property_underscan_enable), + KUNIT_CASE(dm_test_set_property_abm_sysfs_control), + KUNIT_CASE(dm_test_set_property_abm_level_off), + KUNIT_CASE(dm_test_set_property_abm_level_value), + KUNIT_CASE(dm_test_set_property_unknown), + /* amdgpu_dm_connector_atomic_get_property */ + KUNIT_CASE(dm_test_get_property_scaling_center), + KUNIT_CASE(dm_test_get_property_scaling_aspect), + KUNIT_CASE(dm_test_get_property_scaling_full), + KUNIT_CASE(dm_test_get_property_scaling_off), + KUNIT_CASE(dm_test_get_property_underscan_borders), + KUNIT_CASE(dm_test_get_property_abm_sysfs_allowed), + KUNIT_CASE(dm_test_get_property_abm_level), + KUNIT_CASE(dm_test_get_property_abm_disabled_zero), + KUNIT_CASE(dm_test_get_property_unknown), + /* amdgpu_dm_get_highest_refresh_rate_mode */ + KUNIT_CASE(dm_test_highest_refresh_writeback_null), + KUNIT_CASE(dm_test_highest_refresh_cached_base), + KUNIT_CASE(dm_test_highest_refresh_preferred_mode), + /* amdgpu_dm_is_freesync_video_mode */ + KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode), + KUNIT_CASE(dm_test_is_freesync_video_mode_match), + KUNIT_CASE(dm_test_is_freesync_video_mode_no_match), + {} +}; + +static struct kunit_suite amdgpu_dm_connector_test_suite = { + .name = "amdgpu_dm_connector", + .test_cases = amdgpu_dm_connector_tests, +}; + +kunit_test_suite(amdgpu_dm_connector_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_connector"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 55d6cc7dae145e52b7cb804344d12b1e113d4876 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 15:00:51 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_irq Add KUnit tests for helper functions, IRQ table management paths, and DRM mock-backed CRTC lookup in amdgpu_dm_irq.c. Tests cover: - amdgpu_dm_hpd_to_dal_irq_source(): all HPD types 1-6, AMDGPU_HPD_NONE, and out-of-range values - are_sinks_equal(): NULL inputs, signal mismatch, EDID length mismatch, EDID data mismatch, identical sinks, zero-length EDID, full-length identical EDID, and a single trailing-byte difference - dmub_notification_type_str(): notification type mappings that are always built, plus the unknown/default case - amdgpu_dm_irq_init(): low/high handler list initialization - amdgpu_dm_irq_register_interrupt(): NULL input rejection, invalid context/source rejection, low/high handler insertion, multiple handlers on one source, and the same handler registered in both low and high contexts - amdgpu_dm_irq_unregister_interrupt(): invalid source and NULL handler rejection, removal of registered low/high handlers, and the handler-not-found path - amdgpu_dm_irq_fini(): cleanup of registered low/high handlers and the empty-table case - amdgpu_dm_get_crtc_by_otg_inst(): DRM mock CRTC list match, no-match, and empty-list paths Assisted-by: Copilot:Claude-Opus-4 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 15 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 8 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 934 +++++++++++++++++++++ 4 files changed, 955 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c 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 36c0177f5eb0..0759c1d92b61 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,6 +33,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_hdcp.h" #include "amdgpu_dm_mst_types.h" @@ -372,6 +373,7 @@ void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev, return handler_data; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_register_interrupt); /** * amdgpu_dm_irq_unregister_interrupt() - Remove a handler from the DM IRQ table @@ -416,6 +418,7 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, ih, irq_source); } } +EXPORT_IF_KUNIT(amdgpu_dm_irq_unregister_interrupt); /** * amdgpu_dm_irq_init() - Initialize DM IRQ management @@ -450,6 +453,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_init); /** * amdgpu_dm_irq_fini() - Tear down DM IRQ management @@ -488,6 +492,7 @@ void amdgpu_dm_irq_fini(struct amdgpu_device *adev) /* Deallocate handlers from the table. */ unregister_all_irq_handlers(adev); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_fini); void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) { @@ -690,7 +695,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, return 0; } -static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) +STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) { switch (type) { case AMDGPU_HPD_1: @@ -709,6 +714,7 @@ static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) return DC_IRQ_SOURCE_INVALID; } } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source); static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, @@ -1192,7 +1198,7 @@ void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) } } -static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) +STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) { if (!sink1 || !sink2) return false; @@ -1207,6 +1213,7 @@ static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *s return false; return true; } +EXPORT_IF_KUNIT(are_sinks_equal); /** @@ -1692,6 +1699,7 @@ amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_get_crtc_by_otg_inst); /** * dm_pflip_high_irq() - Handle pageflip interrupt @@ -2067,7 +2075,7 @@ static void dm_handle_hpd_work(struct work_struct *work) } -static const char *dmub_notification_type_str(enum dmub_notification_type e) +STATIC_IFN_KUNIT const char *dmub_notification_type_str(enum dmub_notification_type e) { switch (e) { case DMUB_NOTIFICATION_NO_DATA: @@ -2090,6 +2098,7 @@ static const char *dmub_notification_type_str(enum dmub_notification_type e) return ""; } } +EXPORT_IF_KUNIT(dmub_notification_type_str); #define DMUB_TRACE_MAX_READ 64 /** diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index ba6968f5626f..bccb5d354a9f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -30,8 +30,10 @@ struct amdgpu_device; struct amdgpu_crtc; struct amdgpu_display_manager; +struct dc_sink; struct hpd_rx_irq_offload_work_queue; struct work_struct; +enum dmub_notification_type; /* * Display Manager IRQ-related interfaces (for use by DAL). @@ -120,4 +122,10 @@ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); +bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2); +const char *dmub_notification_type_str(enum dmub_notification_type e); +#endif + #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 422eef0bfe49..583604914753 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_irq_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_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 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 new file mode 100644 index 000000000000..525caa0b1f6a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_irq.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_irq.h" +#include "dmub/dmub_srv.h" + +static void dm_test_irq_handler(void *arg) +{ +} + +static void dm_test_irq_handler_alt(void *arg) +{ +} + +static void dm_test_crtc_list_del(void *data) +{ + struct amdgpu_crtc *acrtc = data; + + list_del_init(&acrtc->base.head); +} + +/* Tests for amdgpu_dm_hpd_to_dal_irq_source() */ + +/** + * dm_test_hpd_to_dal_irq_source_hpd1 - Test Hpd to dal irq source hpd1 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd1(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_1), + (int)DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd2 - Test Hpd to dal irq source hpd2 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd2(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_2), + (int)DC_IRQ_SOURCE_HPD2); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd3 - Test Hpd to dal irq source hpd3 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd3(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_3), + (int)DC_IRQ_SOURCE_HPD3); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd4 - Test Hpd to dal irq source hpd4 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd4(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_4), + (int)DC_IRQ_SOURCE_HPD4); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd5 - Test Hpd to dal irq source hpd5 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd5(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_5), + (int)DC_IRQ_SOURCE_HPD5); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd6 - Test Hpd to dal irq source hpd6 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd6(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_6), + (int)DC_IRQ_SOURCE_HPD6); +} + +/** + * dm_test_hpd_to_dal_irq_source_invalid - Test Hpd to dal irq source invalid + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_invalid(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_NONE), + (int)DC_IRQ_SOURCE_INVALID); +} + +/** + * dm_test_hpd_to_dal_irq_source_out_of_range - Test Hpd to dal irq source out of range + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_out_of_range(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(99), + (int)DC_IRQ_SOURCE_INVALID); +} + +/* Tests for are_sinks_equal() */ + +/** + * dm_test_are_sinks_equal_both_null - Test Are sinks equal both null + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_both_null(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, are_sinks_equal(NULL, NULL)); +} + +/** + * dm_test_are_sinks_equal_first_null - Test Are sinks equal first null + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_first_null(struct kunit *test) +{ + struct dc_sink *sink2; + + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(NULL, sink2)); +} + +/** + * dm_test_are_sinks_equal_second_null - Test Are sinks equal second null + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_second_null(struct kunit *test) +{ + struct dc_sink *sink1; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, NULL)); +} + +/** + * dm_test_are_sinks_equal_different_signal - Test Are sinks equal different signal + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_different_signal(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_different_edid_length - Test Are sinks equal different edid length + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_different_edid_length(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 128; + sink2->dc_edid.length = 256; + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_different_edid_data - Test Are sinks equal different edid data + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_different_edid_data(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 4; + sink2->dc_edid.length = 4; + memset(sink1->dc_edid.raw_edid, 0xAA, 4); + memset(sink2->dc_edid.raw_edid, 0xBB, 4); + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_identical - Test Are sinks equal identical + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_identical(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 4; + sink2->dc_edid.length = 4; + memset(sink1->dc_edid.raw_edid, 0xAA, 4); + memset(sink2->dc_edid.raw_edid, 0xAA, 4); + + KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_zero_length - Test Are sinks equal zero length + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_zero_length(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_DISPLAY_PORT; + sink2->sink_signal = SIGNAL_TYPE_DISPLAY_PORT; + sink1->dc_edid.length = 0; + sink2->dc_edid.length = 0; + + KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_full_edid_identical - Test Are sinks equal full edid identical + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_full_edid_identical(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 128; + sink2->dc_edid.length = 128; + memset(sink1->dc_edid.raw_edid, 0x5A, 128); + memset(sink2->dc_edid.raw_edid, 0x5A, 128); + + KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_full_edid_last_byte_differs - Test Are sinks equal last byte differs + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_full_edid_last_byte_differs(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 128; + sink2->dc_edid.length = 128; + memset(sink1->dc_edid.raw_edid, 0x5A, 128); + memset(sink2->dc_edid.raw_edid, 0x5A, 128); + sink2->dc_edid.raw_edid[127] = 0x5B; + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/* Tests for dmub_notification_type_str() */ + +/** + * dm_test_notification_str_no_data - Test Notification str no data + * @test: The KUnit test context + */ +static void dm_test_notification_str_no_data(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_NO_DATA), "NO_DATA"); +} + +/** + * dm_test_notification_str_aux_reply - Test Notification str aux reply + * @test: The KUnit test context + */ +static void dm_test_notification_str_aux_reply(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_AUX_REPLY), "AUX_REPLY"); +} + +/** + * dm_test_notification_str_hpd - Test Notification str hpd + * @test: The KUnit test context + */ +static void dm_test_notification_str_hpd(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD), "HPD"); +} + +/** + * dm_test_notification_str_hpd_irq - Test Notification str hpd irq + * @test: The KUnit test context + */ +static void dm_test_notification_str_hpd_irq(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD_IRQ), "HPD_IRQ"); +} + +/** + * dm_test_notification_str_set_config - Test Notification str set config + * @test: The KUnit test context + */ +static void dm_test_notification_str_set_config(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_SET_CONFIG_REPLY), + "SET_CONFIG_REPLY"); +} + +/** + * dm_test_notification_str_dpia - Test Notification str dpia + * @test: The KUnit test context + */ +static void dm_test_notification_str_dpia(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_DPIA_NOTIFICATION), + "DPIA_NOTIFICATION"); +} + +/** + * dm_test_notification_str_hpd_sense - Test Notification str hpd sense + * @test: The KUnit test context + */ +static void dm_test_notification_str_hpd_sense(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD_SENSE_NOTIFY), + "HPD_SENSE_NOTIFY"); +} + +/** + * dm_test_notification_str_fused_io - Test Notification str fused io + * @test: The KUnit test context + */ +static void dm_test_notification_str_fused_io(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_FUSED_IO), + "FUSED_IO"); +} + +/** + * dm_test_notification_str_unknown - Test Notification str unknown + * @test: The KUnit test context + */ +static void dm_test_notification_str_unknown(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_MAX), ""); +} + +/* Tests for amdgpu_dm_irq_init() */ + +/** + * dm_test_irq_init_initializes_lists - Test irq init initializes list heads + * @test: The KUnit test context + */ +static void dm_test_irq_init_initializes_lists(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[src])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[src])); + } +} + +/* Tests for amdgpu_dm_irq_register_interrupt() */ + +/** + * dm_test_irq_register_rejects_null_params - Test register rejects null params + * @test: The KUnit test context + */ +static void dm_test_irq_register_rejects_null_params(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, NULL, + dm_test_irq_handler, NULL)); + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, &int_params, NULL, NULL)); +} + +/** + * dm_test_irq_register_rejects_invalid_context - Test register rejects context + * @test: The KUnit test context + */ +static void dm_test_irq_register_rejects_invalid_context(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + int_params.int_context = INTERRUPT_CONTEXT_NUMBER; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, NULL)); +} + +/** + * dm_test_irq_register_rejects_invalid_source - Test register rejects source + * @test: The KUnit test context + */ +static void dm_test_irq_register_rejects_invalid_source(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_INVALID; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, NULL)); +} + +/** + * dm_test_irq_register_adds_low_context_handler - Test register adds low handler + * @test: The KUnit test context + */ +static void dm_test_irq_register_adds_low_context_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1])); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); +} + +/** + * dm_test_irq_register_adds_high_context_handler - Test register adds high handler + * @test: The KUnit test context + */ +static void dm_test_irq_register_adds_high_context_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD2; + + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD2])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD2])); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, + dm_test_irq_handler); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD2])); +} + +/** + * dm_test_irq_register_multiple_handlers - Test register keeps multiple handlers + * @test: The KUnit test context + */ +static void dm_test_irq_register_multiple_handlers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + struct list_head *hnd_list; + void *handler1, *handler2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + handler1 = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler1); + handler2 = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_alt, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler2); + + hnd_list = &adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]; + KUNIT_EXPECT_EQ(test, list_count_nodes(hnd_list), 2); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler_alt); + KUNIT_EXPECT_TRUE(test, list_empty(hnd_list)); +} + +/** + * dm_test_irq_register_separate_contexts - Test register same source in two contexts + * @test: The KUnit test context + */ +static void dm_test_irq_register_separate_contexts(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.irq_source = DC_IRQ_SOURCE_HPD5; + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD5])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5])); + + /* + * A single unregister call stops at the first context where the handler + * is found (low context), leaving the high context handler in place. + */ + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD5, + dm_test_irq_handler); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD5])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5])); + + /* A second call removes the remaining high context handler. */ + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD5, + dm_test_irq_handler); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5])); +} + +/* Tests for amdgpu_dm_irq_unregister_interrupt() */ + +/** + * dm_test_irq_unregister_rejects_invalid_source - Test unregister rejects source + * @test: The KUnit test context + */ +static void dm_test_irq_unregister_rejects_invalid_source(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_INVALID, + dm_test_irq_handler); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1])); +} + +/** + * dm_test_irq_unregister_rejects_null_handler - Test unregister rejects handler + * @test: The KUnit test context + */ +static void dm_test_irq_unregister_rejects_null_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + DAL_INVALID_IRQ_HANDLER_IDX); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1])); +} + +/** + * dm_test_irq_unregister_handler_not_found - Test unregister keeps unmatched handler + * @test: The KUnit test context + */ +static void dm_test_irq_unregister_handler_not_found(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Unregister a handler that was never registered for this source. */ + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler_alt); + + /* The originally registered handler must still be present. */ + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); +} + +/* Tests for amdgpu_dm_irq_fini() */ + +/** + * dm_test_irq_fini_removes_registered_handlers - Test fini removes handlers + * @test: The KUnit test context + */ +static void dm_test_irq_fini_removes_registered_handlers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD3; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD4; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_fini(adev); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD3])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD4])); +} + +/** + * dm_test_irq_fini_on_empty_tables - Test fini on tables with no handlers + * @test: The KUnit test context + */ +static void dm_test_irq_fini_on_empty_tables(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_fini(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[src])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[src])); + } +} + +/* Tests for amdgpu_dm_get_crtc_by_otg_inst() */ + +/** + * dm_test_get_crtc_by_otg_inst_returns_match - Test CRTC lookup by OTG instance + * @test: The KUnit test context + */ +static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test) +{ + struct amdgpu_crtc *acrtc_a, *acrtc_b; + struct amdgpu_device *adev; + 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); + + acrtc_a = kunit_kzalloc(test, sizeof(*acrtc_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_a); + acrtc_b = kunit_kzalloc(test, sizeof(*acrtc_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_b); + + INIT_LIST_HEAD(&acrtc_a->base.head); + INIT_LIST_HEAD(&acrtc_b->base.head); + acrtc_a->otg_inst = 1; + acrtc_b->otg_inst = 3; + + list_add_tail(&acrtc_a->base.head, &drm->mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, + acrtc_a), 0); + list_add_tail(&acrtc_b->base.head, &drm->mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, + acrtc_b), 0); + + KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 3), acrtc_b); +} + +/** + * dm_test_get_crtc_by_otg_inst_returns_null - Test CRTC lookup misses unknown OTG + * @test: The KUnit test context + */ +static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + 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); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->otg_inst = 2; + + list_add_tail(&acrtc->base.head, &drm->mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, + acrtc), 0); + + KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 5)); +} + +/** + * dm_test_get_crtc_by_otg_inst_empty_list - Test CRTC lookup on empty CRTC list + * @test: The KUnit test context + */ +static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test) +{ + struct amdgpu_device *adev; + 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); + + KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0)); +} + +static struct kunit_case amdgpu_dm_irq_tests[] = { + /* amdgpu_dm_hpd_to_dal_irq_source */ + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd2), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd3), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd4), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd5), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd6), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_invalid), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_out_of_range), + /* are_sinks_equal */ + KUNIT_CASE(dm_test_are_sinks_equal_both_null), + KUNIT_CASE(dm_test_are_sinks_equal_first_null), + KUNIT_CASE(dm_test_are_sinks_equal_second_null), + KUNIT_CASE(dm_test_are_sinks_equal_different_signal), + KUNIT_CASE(dm_test_are_sinks_equal_different_edid_length), + KUNIT_CASE(dm_test_are_sinks_equal_different_edid_data), + KUNIT_CASE(dm_test_are_sinks_equal_identical), + KUNIT_CASE(dm_test_are_sinks_equal_zero_length), + KUNIT_CASE(dm_test_are_sinks_equal_full_edid_identical), + KUNIT_CASE(dm_test_are_sinks_equal_full_edid_last_byte_differs), + /* dmub_notification_type_str */ + KUNIT_CASE(dm_test_notification_str_no_data), + KUNIT_CASE(dm_test_notification_str_aux_reply), + KUNIT_CASE(dm_test_notification_str_hpd), + KUNIT_CASE(dm_test_notification_str_hpd_irq), + KUNIT_CASE(dm_test_notification_str_set_config), + KUNIT_CASE(dm_test_notification_str_dpia), + KUNIT_CASE(dm_test_notification_str_hpd_sense), + KUNIT_CASE(dm_test_notification_str_fused_io), + KUNIT_CASE(dm_test_notification_str_unknown), + /* amdgpu_dm_irq_init */ + KUNIT_CASE(dm_test_irq_init_initializes_lists), + /* amdgpu_dm_irq_register_interrupt */ + KUNIT_CASE(dm_test_irq_register_rejects_null_params), + KUNIT_CASE(dm_test_irq_register_rejects_invalid_context), + KUNIT_CASE(dm_test_irq_register_rejects_invalid_source), + KUNIT_CASE(dm_test_irq_register_adds_low_context_handler), + KUNIT_CASE(dm_test_irq_register_adds_high_context_handler), + KUNIT_CASE(dm_test_irq_register_multiple_handlers), + KUNIT_CASE(dm_test_irq_register_separate_contexts), + /* amdgpu_dm_irq_unregister_interrupt */ + KUNIT_CASE(dm_test_irq_unregister_rejects_invalid_source), + KUNIT_CASE(dm_test_irq_unregister_rejects_null_handler), + KUNIT_CASE(dm_test_irq_unregister_handler_not_found), + /* amdgpu_dm_irq_fini */ + KUNIT_CASE(dm_test_irq_fini_removes_registered_handlers), + KUNIT_CASE(dm_test_irq_fini_on_empty_tables), + /* amdgpu_dm_get_crtc_by_otg_inst */ + KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_match), + KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_null), + KUNIT_CASE(dm_test_get_crtc_by_otg_inst_empty_list), + {} +}; + +static struct kunit_suite amdgpu_dm_irq_test_suite = { + .name = "amdgpu_dm_irq", + .test_cases = amdgpu_dm_irq_tests, +}; + +kunit_test_suite(amdgpu_dm_irq_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_irq"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From dbfad676ff1ad260a2f0c3b1eeb8e663eaab1126 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 16:29:06 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_crtc Add KUnit coverage for functions in amdgpu_dm_crtc.c: - amdgpu_dm_crtc_modeset_required: verify active+needs_modeset combinations (mode_changed, active_changed, connectors_changed) - amdgpu_dm_crtc_vrr_active_irq: verify all VRR state enum values - amdgpu_dm_crtc_vrr_active: verify all VRR state enum values - amdgpu_dm_is_headless: null adev, no connectors, writeback-only, disconnected display, connected display, and mixed connector cases - amdgpu_dm_crtc_helper_mode_fixup: verify it accepts the mode - amdgpu_dm_crtc_set_vupdate_irq: verify the otg_inst == -1 early return using a DRM mock device - idle_create_workqueue: verify the idle workqueue is allocated and initialized in a disabled, non-running state Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 14 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 6 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 532 +++++++++++++++++++++ 4 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c 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 3dcedaa67ed8..f7fcce6e76bb 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,6 +34,7 @@ #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" +#include "amdgpu_dm_kunit_helpers.h" #include "modules/inc/mod_power.h" #define HPD_DETECTION_PERIOD_uS 2000000 @@ -65,6 +66,7 @@ bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state, { return crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state); } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_modeset_required); bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc) @@ -74,6 +76,7 @@ bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc) acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_FIXED; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_vrr_active_irq); int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) { @@ -93,12 +96,14 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) acrtc->crtc_id, enable ? "en" : "dis", rc); return rc; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_set_vupdate_irq); bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state) { return dm_state->freesync_config.state == VRR_STATE_ACTIVE_VARIABLE || dm_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_vrr_active); /** * amdgpu_dm_crtc_set_static_screen_optimze() - Toggle static screen optimizations. @@ -156,6 +161,7 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev) drm_connector_list_iter_end(&iter); return is_headless; } +EXPORT_IF_KUNIT(amdgpu_dm_is_headless); static void amdgpu_dm_idle_worker(struct work_struct *work) { @@ -207,6 +213,7 @@ struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev) return idle_work; } +EXPORT_IF_KUNIT(idle_create_workqueue); static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) { @@ -595,12 +602,13 @@ static void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc, amdgpu_dm_crtc_count_crtc_active_planes(new_crtc_state); } -static bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +STATIC_IFN_KUNIT bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) { return true; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_helper_mode_fixup); static int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *state) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h index e9fb52f0e66d..d8b004f613ab 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h @@ -42,6 +42,12 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable); bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +#endif + bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state); int amdgpu_dm_crtc_enable_vblank(struct drm_crtc *crtc); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 583604914753..cde8f7748bc5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_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_crtc_test.o 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 new file mode 100644 index 000000000000..c83bd3e074f1 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_crtc.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_irq_params.h" + +/* Tests for amdgpu_dm_crtc_modeset_required() */ + +/** + * dm_test_crtc_modeset_required_active_mode_changed - Test Crtc modeset required active mode changed + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_active_mode_changed(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.mode_changed = true; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_active_active_changed - Test Crtc modeset required active active changed + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_active_active_changed(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.active_changed = true; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_active_connectors_changed - Test Crtc modeset required active connectors changed + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_active_connectors_changed(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.connectors_changed = true; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_inactive - Test Crtc modeset required inactive + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_inactive(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = false; + state.mode_changed = true; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_no_changes - Test Crtc modeset required no changes + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_no_changes(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.mode_changed = false; + state.active_changed = false; + state.connectors_changed = false; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/* Tests for amdgpu_dm_crtc_vrr_active_irq() */ + +/** + * dm_test_crtc_vrr_active_irq_variable - Test Crtc vrr active irq variable + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_variable(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_fixed - Test Crtc vrr active irq fixed + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_fixed(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_inactive - Test Crtc vrr active irq inactive + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_inactive(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_disabled - Test Crtc vrr active irq disabled + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_disabled(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_DISABLED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_unsupported - Test Crtc vrr active irq unsupported + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_unsupported(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_UNSUPPORTED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/* Tests for amdgpu_dm_crtc_vrr_active() */ + +/** + * dm_test_crtc_vrr_active_variable - Test Crtc vrr active variable + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_variable(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_fixed - Test Crtc vrr active fixed + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_fixed(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_inactive - Test Crtc vrr active inactive + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_inactive(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_disabled - Test Crtc vrr active disabled + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_disabled(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_DISABLED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_unsupported - Test Crtc vrr active unsupported + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_unsupported(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_UNSUPPORTED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/* Tests for amdgpu_dm_is_headless() */ + +static void dm_test_add_connector(struct drm_device *dev, + struct drm_connector *connector, + int connector_type, + enum drm_connector_status status) +{ + INIT_LIST_HEAD(&connector->head); + kref_init(&connector->base.refcount); + connector->connector_type = connector_type; + connector->status = status; + list_add_tail(&connector->head, &dev->mode_config.connector_list); +} + +/** + * dm_test_crtc_is_headless_null_adev - Test Crtc is headless null adev + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_null_adev(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(NULL)); +} + +/** + * dm_test_crtc_is_headless_no_connectors - Test Crtc is headless no connectors + * @test: The KUnit test context + */ +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); + + 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)); +} + +/** + * dm_test_crtc_is_headless_writeback_only - Test Crtc is headless writeback only + * @test: The KUnit test context + */ +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_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, + connector_status_connected); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_disconnected_display - Test Crtc is headless disconnected display + * @test: The KUnit test context + */ +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_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, + connector_status_disconnected); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_connected_display - Test Crtc is headless connected display + * @test: The KUnit test context + */ +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_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, + connector_status_connected); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_mixed_connectors - Test headless skips WB and finds display + * @test: The KUnit test context + */ +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_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, + connector_status_connected); + dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_DisplayPort, + connector_status_connected); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_headless(adev)); +} + +/* Tests for amdgpu_dm_crtc_helper_mode_fixup() */ + +/** + * dm_test_crtc_helper_mode_fixup_returns_true - Test mode_fixup accepts mode + * @test: The KUnit test context + */ +static void dm_test_crtc_helper_mode_fixup_returns_true(struct kunit *test) +{ + struct drm_display_mode mode = { 0 }; + struct drm_display_mode adjusted_mode = { 0 }; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_helper_mode_fixup(NULL, &mode, &adjusted_mode)); +} + +/* Tests for amdgpu_dm_crtc_set_vupdate_irq() */ + +/** + * dm_test_crtc_set_vupdate_irq_no_otg - Test vupdate irq with unassigned OTG + * @test: The KUnit test context + * + * When the CRTC has no OTG instance assigned (otg_inst == -1) the function + * must return 0 immediately without touching the DC interrupt state. + */ +static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + 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); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->base.dev = drm; + acrtc->otg_inst = -1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, false), 0); +} + +/* Tests for idle_create_workqueue() */ + +/** + * dm_test_idle_create_workqueue - Test idle workqueue creation + * @test: The KUnit test context + * + * Verify that idle_create_workqueue() allocates an idle workqueue tied to the + * device's display manager and initializes it in a disabled, non-running state. + */ +static void dm_test_idle_create_workqueue(struct kunit *test) +{ + struct amdgpu_device *adev; + struct idle_workqueue *idle_work; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + idle_work = idle_create_workqueue(adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work); + + KUNIT_EXPECT_PTR_EQ(test, idle_work->dm, &adev->dm); + KUNIT_EXPECT_FALSE(test, idle_work->enable); + KUNIT_EXPECT_FALSE(test, idle_work->running); + + kfree(idle_work); +} + +static struct kunit_case amdgpu_dm_crtc_tests[] = { + /* amdgpu_dm_crtc_modeset_required */ + KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed), + KUNIT_CASE(dm_test_crtc_modeset_required_active_active_changed), + KUNIT_CASE(dm_test_crtc_modeset_required_active_connectors_changed), + KUNIT_CASE(dm_test_crtc_modeset_required_inactive), + KUNIT_CASE(dm_test_crtc_modeset_required_no_changes), + /* amdgpu_dm_crtc_vrr_active_irq */ + KUNIT_CASE(dm_test_crtc_vrr_active_irq_variable), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_fixed), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_inactive), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_disabled), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_unsupported), + /* amdgpu_dm_crtc_vrr_active */ + KUNIT_CASE(dm_test_crtc_vrr_active_variable), + KUNIT_CASE(dm_test_crtc_vrr_active_fixed), + KUNIT_CASE(dm_test_crtc_vrr_active_inactive), + KUNIT_CASE(dm_test_crtc_vrr_active_disabled), + KUNIT_CASE(dm_test_crtc_vrr_active_unsupported), + /* amdgpu_dm_is_headless */ + KUNIT_CASE(dm_test_crtc_is_headless_null_adev), + KUNIT_CASE(dm_test_crtc_is_headless_no_connectors), + KUNIT_CASE(dm_test_crtc_is_headless_writeback_only), + KUNIT_CASE(dm_test_crtc_is_headless_disconnected_display), + KUNIT_CASE(dm_test_crtc_is_headless_connected_display), + KUNIT_CASE(dm_test_crtc_is_headless_mixed_connectors), + /* amdgpu_dm_crtc_helper_mode_fixup */ + KUNIT_CASE(dm_test_crtc_helper_mode_fixup_returns_true), + /* amdgpu_dm_crtc_set_vupdate_irq */ + KUNIT_CASE(dm_test_crtc_set_vupdate_irq_no_otg), + /* idle_create_workqueue */ + KUNIT_CASE(dm_test_idle_create_workqueue), + {} +}; + +static struct kunit_suite amdgpu_dm_crtc_test_suite = { + .name = "amdgpu_dm_crtc", + .test_cases = amdgpu_dm_crtc_tests, +}; + +kunit_test_suite(amdgpu_dm_crtc_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_crtc"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 6c61907396853a41ce316073411758adf848f1c1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 6 May 2026 15:54:47 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_services Add amdgpu_dm_services_test.c with KUnit coverage for five functions in amdgpu_dm_services.c: - dm_get_elapse_time_in_ns(): four arithmetic cases covering zero delta, positive delta, ULLONG_MAX span, and unsigned wraparound. - dm_perf_trace_timestamp(): one case verifying the function dereferences ctx->perf_trace safely (the tracepoint is a no-op without an attached probe). - dm_trace_smu_enter(): two cases for the empty stub with NULL ctx and with non-zero parameters. - dm_trace_smu_exit(): three cases for the empty stub covering success, failure, and a non-zero response value. - dm_query_extended_brightness_caps(): four guard-clause cases (NULL ctx, NULL caps, NULL ctx->driver_context, NULL ctx with LCD2) plus two success cases covering the LCD1 slot with luminance data copy and a non-LCD1 display using the second backlight slot with zero data points. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 6 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_services_test.c | 313 +++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c 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 0fdcf70256cc..6c0464754ed8 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,6 +36,7 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "amdgpu_dm_trace.h" +#include "amdgpu_dm_kunit_helpers.h" unsigned long long dm_get_elapse_time_in_ns(struct dc_context *ctx, @@ -44,6 +45,7 @@ { return current_time_stamp - last_time_stamp; } +EXPORT_IF_KUNIT(dm_get_elapse_time_in_ns); void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc_context *ctx) { @@ -53,14 +55,17 @@ void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc &ctx->perf_trace->last_entry_write, func_name, line); } +EXPORT_IF_KUNIT(dm_perf_trace_timestamp); void dm_trace_smu_enter(uint32_t msg_id, uint32_t param_in, unsigned int delay, struct dc_context *ctx) { } +EXPORT_IF_KUNIT(dm_trace_smu_enter); void dm_trace_smu_exit(bool success, uint32_t response, struct dc_context *ctx) { } +EXPORT_IF_KUNIT(dm_trace_smu_exit); /**** power component interfaces ****/ @@ -90,3 +95,4 @@ bool dm_query_extended_brightness_caps(struct dc_context *ctx, sizeof(struct dm_bl_data_point) * pCaps->num_data_points); return true; } +EXPORT_IF_KUNIT(dm_query_extended_brightness_caps); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index cde8f7748bc5..364b4f3c783f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -28,3 +28,4 @@ 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_crtc_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c new file mode 100644 index 000000000000..e48bac7fb024 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_services.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "dm_services.h" +#include "dm_services_types.h" + +/* Tests for dm_get_elapse_time_in_ns() */ + +/** + * dm_test_get_elapse_time_zero_delta - Test Get elapse time zero delta + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_zero_delta(struct kunit *test) +{ + unsigned long long ts = 1000000ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, ts, ts), 0ULL); +} + +/** + * dm_test_get_elapse_time_positive_delta - Test Get elapse time positive delta + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_positive_delta(struct kunit *test) +{ + unsigned long long current_ts = 5000000ULL; + unsigned long long last_ts = 1000000ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts), + 4000000ULL); +} + +/** + * dm_test_get_elapse_time_large_delta - Test Get elapse time large delta + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_large_delta(struct kunit *test) +{ + unsigned long long current_ts = ULLONG_MAX; + unsigned long long last_ts = 0ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts), + ULLONG_MAX); +} + +/** + * dm_test_get_elapse_time_wraparound - Test Get elapse time wraparound + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_wraparound(struct kunit *test) +{ + /* Unsigned wraparound: result = ULLONG_MAX - last + current + 1 */ + unsigned long long current_ts = 5ULL; + unsigned long long last_ts = ULLONG_MAX - 4ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts), + 10ULL); +} + +/* Tests for dm_perf_trace_timestamp() */ + +/** + * dm_test_perf_trace_timestamp_basic - Test Perf trace timestamp basic + * @test: The KUnit test context + * + * The tracepoint is a no-op without an attached probe, so this verifies the + * function dereferences ctx->perf_trace safely and does not crash. + */ +static void dm_test_perf_trace_timestamp_basic(struct kunit *test) +{ + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->perf_trace = kunit_kzalloc(test, sizeof(*ctx->perf_trace), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->perf_trace); + + ctx->perf_trace->read_count = 10; + ctx->perf_trace->write_count = 20; + + dm_perf_trace_timestamp(__func__, __LINE__, ctx); +} + +/* Tests for dm_trace_smu_enter() */ + +/** + * dm_test_trace_smu_enter_null_ctx - Test Trace smu enter null ctx + * @test: The KUnit test context + */ +static void dm_test_trace_smu_enter_null_ctx(struct kunit *test) +{ + /* Empty stub — must not crash with NULL ctx */ + dm_trace_smu_enter(0, 0, 0, NULL); +} + +/** + * dm_test_trace_smu_enter_with_params - Test Trace smu enter with params + * @test: The KUnit test context + */ +static void dm_test_trace_smu_enter_with_params(struct kunit *test) +{ + /* Exercise non-zero msg_id, param_in, and delay */ + dm_trace_smu_enter(0xFF, 0x12345678, 1000, NULL); +} + +/* Tests for dm_trace_smu_exit() */ + +/** + * dm_test_trace_smu_exit_success_null_ctx - Test Trace smu exit success null ctx + * @test: The KUnit test context + */ +static void dm_test_trace_smu_exit_success_null_ctx(struct kunit *test) +{ + /* Empty stub — must not crash on success path with NULL ctx */ + dm_trace_smu_exit(true, 0x0, NULL); +} + +/** + * dm_test_trace_smu_exit_failure_null_ctx - Test Trace smu exit failure null ctx + * @test: The KUnit test context + */ +static void dm_test_trace_smu_exit_failure_null_ctx(struct kunit *test) +{ + /* Empty stub — must not crash on failure path with NULL ctx */ + dm_trace_smu_exit(false, 0x0, NULL); +} + +/** + * dm_test_trace_smu_exit_with_response - Test Trace smu exit with response + * @test: The KUnit test context + */ +static void dm_test_trace_smu_exit_with_response(struct kunit *test) +{ + /* Exercise non-zero response value */ + dm_trace_smu_exit(true, 0xDEADBEEF, NULL); +} + +/* Tests for dm_query_extended_brightness_caps() */ + +/** + * dm_test_query_brightness_caps_null_ctx - Test Query brightness caps null ctx + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_null_ctx(struct kunit *test) +{ + struct dm_acpi_atif_backlight_caps caps = {}; + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(NULL, AcpiDisplayType_LCD1, &caps)); +} + +/** + * dm_test_query_brightness_caps_null_caps - Test Query brightness caps null caps + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_null_caps(struct kunit *test) +{ + struct dc_context ctx = {}; + + ctx.driver_context = (void *)0x1; /* non-NULL sentinel */ + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, NULL)); +} + +/** + * dm_test_query_brightness_caps_null_driver_ctx - Test Query brightness caps null driver ctx + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_null_driver_ctx(struct kunit *test) +{ + struct dc_context ctx = {}; + struct dm_acpi_atif_backlight_caps caps = {}; + + ctx.driver_context = NULL; + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, &caps)); +} + +/** + * dm_test_query_brightness_caps_lcd2_null_ctx - Test Query brightness caps lcd2 null ctx + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_lcd2_null_ctx(struct kunit *test) +{ + struct dm_acpi_atif_backlight_caps caps = {}; + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(NULL, AcpiDisplayType_LCD2, &caps)); +} + +/** + * dm_test_query_brightness_caps_lcd1_success - Test Query brightness caps lcd1 success + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_lcd1_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_backlight_caps *source_caps; + struct dc_context ctx = {}; + struct dm_acpi_atif_backlight_caps caps = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + source_caps = &adev->dm.backlight_caps[0]; + source_caps->caps_valid = true; + source_caps->min_input_signal = 12; + source_caps->max_input_signal = 240; + source_caps->ac_level = 80; + source_caps->dc_level = 40; + source_caps->data_points = 2; + source_caps->luminance_data[0].luminance = 10; + source_caps->luminance_data[0].input_signal = 22; + source_caps->luminance_data[1].luminance = 90; + source_caps->luminance_data[1].input_signal = 200; + ctx.driver_context = adev; + + KUNIT_EXPECT_TRUE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, &caps)); + KUNIT_EXPECT_EQ(test, caps.num_data_points, 2); + KUNIT_EXPECT_EQ(test, caps.max_input_signal, 240); + KUNIT_EXPECT_EQ(test, caps.min_input_signal, 12); + KUNIT_EXPECT_EQ(test, caps.ac_level_percentage, 80); + KUNIT_EXPECT_EQ(test, caps.dc_level_percentage, 40); + KUNIT_EXPECT_EQ(test, caps.data_points[0].luminance, 10); + KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 22); + KUNIT_EXPECT_EQ(test, caps.data_points[1].luminance, 90); + KUNIT_EXPECT_EQ(test, caps.data_points[1].signal_level, 200); +} + +/** + * dm_test_query_brightness_caps_non_lcd1_uses_second_slot - Test Query brightness caps non lcd1 uses second slot + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_non_lcd1_uses_second_slot(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_backlight_caps *source_caps; + struct dc_context ctx = {}; + struct dm_acpi_atif_backlight_caps caps = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->dm.backlight_caps[0].caps_valid = true; + adev->dm.backlight_caps[0].min_input_signal = 1; + adev->dm.backlight_caps[0].max_input_signal = 2; + source_caps = &adev->dm.backlight_caps[1]; + source_caps->caps_valid = true; + source_caps->min_input_signal = 33; + source_caps->max_input_signal = 199; + source_caps->ac_level = 70; + source_caps->dc_level = 30; + source_caps->data_points = 0; + ctx.driver_context = adev; + + KUNIT_EXPECT_TRUE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_DFP1, &caps)); + KUNIT_EXPECT_EQ(test, caps.num_data_points, 0); + KUNIT_EXPECT_EQ(test, caps.max_input_signal, 199); + KUNIT_EXPECT_EQ(test, caps.min_input_signal, 33); + KUNIT_EXPECT_EQ(test, caps.ac_level_percentage, 70); + KUNIT_EXPECT_EQ(test, caps.dc_level_percentage, 30); + KUNIT_EXPECT_EQ(test, caps.data_points[0].luminance, 0); + KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 0); +} + +static struct kunit_case amdgpu_dm_services_test_cases[] = { + /* dm_get_elapse_time_in_ns */ + KUNIT_CASE(dm_test_get_elapse_time_zero_delta), + KUNIT_CASE(dm_test_get_elapse_time_positive_delta), + KUNIT_CASE(dm_test_get_elapse_time_large_delta), + KUNIT_CASE(dm_test_get_elapse_time_wraparound), + /* dm_perf_trace_timestamp */ + KUNIT_CASE(dm_test_perf_trace_timestamp_basic), + /* dm_trace_smu_enter */ + KUNIT_CASE(dm_test_trace_smu_enter_null_ctx), + KUNIT_CASE(dm_test_trace_smu_enter_with_params), + /* dm_trace_smu_exit */ + KUNIT_CASE(dm_test_trace_smu_exit_success_null_ctx), + KUNIT_CASE(dm_test_trace_smu_exit_failure_null_ctx), + KUNIT_CASE(dm_test_trace_smu_exit_with_response), + /* dm_query_extended_brightness_caps */ + KUNIT_CASE(dm_test_query_brightness_caps_null_ctx), + KUNIT_CASE(dm_test_query_brightness_caps_null_caps), + KUNIT_CASE(dm_test_query_brightness_caps_null_driver_ctx), + KUNIT_CASE(dm_test_query_brightness_caps_lcd2_null_ctx), + KUNIT_CASE(dm_test_query_brightness_caps_lcd1_success), + KUNIT_CASE(dm_test_query_brightness_caps_non_lcd1_uses_second_slot), + {} +}; + +static struct kunit_suite amdgpu_dm_services_test_suite = { + .name = "amdgpu_dm_services", + .test_cases = amdgpu_dm_services_test_cases, +}; + +kunit_test_suite(amdgpu_dm_services_test_suite); + +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_services"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From d974b7865f170803c4bda5706f13fb1e22506cb7 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 6 May 2026 16:39:07 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_helpers Add amdgpu_dm_helpers_test.c with 32 KUnit test cases covering the following functions in amdgpu_dm_helpers.c: - edid_extract_panel_id(): basic extraction with known mfg_id and prod_code; zero inputs produce zero output. - dm_is_freesync_pcon_whitelist(): every entry in the whitelist table returns true; an unknown ID and a zero ID return false. - populate_hdmi_info_from_connector(): scdc_present is copied from hdmi->scdc.supported for both true and false; FRL DSC fields map 10bpc and 12bpc correctly and ignore unknown values. - dm_get_adaptive_sync_support_type(): five cases covering the default non-converter path, HDMI converter without conditions, partial conditions, all conditions met with a whitelist device (FREESYNC_TYPE_PCON_IN_WHITELIST), and all conditions met with a non-whitelisted device. - dm_helpers_is_fullscreen() / dm_helpers_is_hdr_on(): stubs always return false. - get_max_frl_rate(): all six valid lane/rate combinations plus the unknown combination returning 0. - dm_dtn_log_begin()/dm_dtn_log_append_v()/dm_dtn_log_end(): buffer accumulation and NULL-context handling without crashing. - dm_helpers_dp_read_dpcd()/dm_helpers_dp_write_dpcd(): NULL link private data returns false. - dm_helpers_dp_mst_start_top_mgr()/dm_helpers_dp_mst_stop_top_mgr(): NULL link private data and the boot path. - dm_helpers_dp_write_hblank_reduction(): stub returns false. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 58 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 20 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 645 +++++++++++++++++++++ 4 files changed, 707 insertions(+), 17 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c 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 eef031022be2..71e2627f9a9d 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,6 +48,8 @@ #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) #define MCCS_SRC_ADDR 0x51 @@ -88,12 +90,13 @@ union vcp_reply { unsigned char raw[11]; }; -static u32 edid_extract_panel_id(struct edid *edid) +STATIC_IFN_KUNIT u32 edid_extract_panel_id(struct edid *edid) { return (u32)edid->mfg_id[0] << 24 | (u32)edid->mfg_id[1] << 16 | (u32)EDID_PRODUCT_ID(edid); } +EXPORT_IF_KUNIT(edid_extract_panel_id); static void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps) @@ -495,6 +498,7 @@ void dm_dtn_log_begin(struct dc_context *ctx, dm_dtn_log_append_v(ctx, log_ctx, "%s", msg); } +EXPORT_IF_KUNIT(dm_dtn_log_begin); __printf(3, 4) void dm_dtn_log_append_v(struct dc_context *ctx, @@ -557,6 +561,7 @@ void dm_dtn_log_append_v(struct dc_context *ctx, if (n > 0) log_ctx->pos += n; } +EXPORT_IF_KUNIT(dm_dtn_log_append_v); void dm_dtn_log_end(struct dc_context *ctx, struct dc_log_buffer_ctx *log_ctx) @@ -570,6 +575,7 @@ void dm_dtn_log_end(struct dc_context *ctx, dm_dtn_log_append_v(ctx, log_ctx, "%s", msg); } +EXPORT_IF_KUNIT(dm_dtn_log_end); bool dm_helpers_dp_mst_start_top_mgr( struct dc_context *ctx, @@ -604,6 +610,7 @@ bool dm_helpers_dp_mst_start_top_mgr( return true; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_start_top_mgr); bool dm_helpers_dp_mst_stop_top_mgr( struct dc_context *ctx, @@ -626,6 +633,7 @@ bool dm_helpers_dp_mst_stop_top_mgr( return false; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_stop_top_mgr); bool dm_helpers_dp_read_dpcd( struct dc_context *ctx, @@ -643,6 +651,7 @@ bool dm_helpers_dp_read_dpcd( return drm_dp_dpcd_read(&aconnector->dm_dp_aux.aux, address, data, size) == size; } +EXPORT_IF_KUNIT(dm_helpers_dp_read_dpcd); bool dm_helpers_dp_write_dpcd( struct dc_context *ctx, @@ -659,6 +668,7 @@ bool dm_helpers_dp_write_dpcd( return drm_dp_dpcd_write(&aconnector->dm_dp_aux.aux, address, (uint8_t *)data, size) > 0; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_dpcd); bool dm_helpers_submit_i2c( struct dc_context *ctx, @@ -974,6 +984,7 @@ bool dm_helpers_dp_write_hblank_reduction(struct dc_context *ctx, const struct d // TODO return false; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_hblank_reduction); bool dm_helpers_is_dp_sink_present(struct dc_link *link) { @@ -1091,7 +1102,7 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn return edid; } -static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) +STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) { uint8_t max_frl_rate; @@ -1112,6 +1123,7 @@ static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) return max_frl_rate; } +EXPORT_IF_KUNIT(get_max_frl_rate); static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) { @@ -1156,6 +1168,7 @@ void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hd } } } +EXPORT_IF_KUNIT(populate_hdmi_info_from_connector); enum dc_edid_status dm_helpers_read_local_edid( struct dc_context *ctx, @@ -1556,24 +1569,32 @@ void dm_helpers_dp_mst_update_branch_bandwidth( // TODO } -static bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id) +STATIC_IFN_KUNIT const uint32_t dm_freesync_pcon_whitelist[] = { + DP_BRANCH_DEVICE_ID_0060AD, + DP_BRANCH_DEVICE_ID_00E04C, + DP_BRANCH_DEVICE_ID_90CC24, + DP_BRANCH_DEVICE_ID_001CF8, + DP_BRANCH_DEVICE_ID_001FF2, +}; +EXPORT_IF_KUNIT(dm_freesync_pcon_whitelist); + +STATIC_IFN_KUNIT uint32_t dm_freesync_pcon_whitelist_count(void) { - bool ret_val = false; - - switch (branch_dev_id) { - case DP_BRANCH_DEVICE_ID_0060AD: - case DP_BRANCH_DEVICE_ID_00E04C: - case DP_BRANCH_DEVICE_ID_90CC24: - case DP_BRANCH_DEVICE_ID_001CF8: - case DP_BRANCH_DEVICE_ID_001FF2: - ret_val = true; - break; - default: - break; - } + return ARRAY_SIZE(dm_freesync_pcon_whitelist); +} +EXPORT_IF_KUNIT(dm_freesync_pcon_whitelist_count); + +STATIC_IFN_KUNIT bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id) +{ + u32 i; - return ret_val; + for (i = 0; i < dm_freesync_pcon_whitelist_count(); i++) + if (dm_freesync_pcon_whitelist[i] == branch_dev_id) + return true; + + return false; } +EXPORT_IF_KUNIT(dm_is_freesync_pcon_whitelist); enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link) { @@ -1593,18 +1614,21 @@ enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link) return as_type; } +EXPORT_IF_KUNIT(dm_get_adaptive_sync_support_type); bool dm_helpers_is_fullscreen(struct dc_context *ctx, struct dc_stream_state *stream) { // TODO return false; } +EXPORT_IF_KUNIT(dm_helpers_is_fullscreen); bool dm_helpers_is_hdr_on(struct dc_context *ctx, struct dc_stream_state *stream) { // TODO return false; } +EXPORT_IF_KUNIT(dm_helpers_is_hdr_on); static int mccs_operation_vcp_request(unsigned int vcp_code, struct dc_link *link, union vcp_reply *reply) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h new file mode 100644 index 000000000000..2ac9762895ec --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef __AMDGPU_DM_HELPERS_H__ +#define __AMDGPU_DM_HELPERS_H__ + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#include + +/* Exported for KUnit testing */ +u32 edid_extract_panel_id(struct edid *edid); +uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); +bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); +extern const uint32_t dm_freesync_pcon_whitelist[]; +uint32_t dm_freesync_pcon_whitelist_count(void); +#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ + +#endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 364b4f3c783f..a067332f9f41 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -29,3 +29,4 @@ 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_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_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c new file mode 100644 index 000000000000..14004ff87c9b --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_helpers.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "dm_helpers.h" +#include "ddc_service_types.h" +#include "amdgpu_dm_helpers.h" + +/* Tests for edid_extract_panel_id() */ + +/** + * dm_test_edid_extract_panel_id_basic - Test Edid extract panel id basic + * @test: The KUnit test context + */ +static void dm_test_edid_extract_panel_id_basic(struct kunit *test) +{ + struct edid *edid; + u32 panel_id; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->mfg_id[0] = 0x12; + edid->mfg_id[1] = 0x34; + edid->prod_code[0] = 0xAB; + edid->prod_code[1] = 0xCD; + + panel_id = edid_extract_panel_id(edid); + + /* + * Expected: (0x12 << 24) | (0x34 << 16) | EDID_PRODUCT_ID(edid) + * EDID_PRODUCT_ID = prod_code[0] | (prod_code[1] << 8) = 0xAB | 0xCD00 = 0xCDAB + * Result: 0x12340000 | 0x0000CDAB = 0x1234CDAB + */ + KUNIT_EXPECT_EQ(test, panel_id, (u32)0x1234CDAB); +} + +/** + * dm_test_edid_extract_panel_id_zeros - Test Edid extract panel id zeros + * @test: The KUnit test context + */ +static void dm_test_edid_extract_panel_id_zeros(struct kunit *test) +{ + struct edid *edid; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + KUNIT_EXPECT_EQ(test, edid_extract_panel_id(edid), 0U); +} + +/* Tests for dm_is_freesync_pcon_whitelist() */ + +/** + * dm_test_freesync_pcon_whitelist_all_known - Test all known Freesync Pcon whitelist entries + * @test: The KUnit test context + * + * Iterates over the driver's whitelist table directly so that any ID added + * to dm_freesync_pcon_whitelist[] is automatically covered by this test. + */ +static void dm_test_freesync_pcon_whitelist_all_known(struct kunit *test) +{ + u32 i; + + for (i = 0; i < dm_freesync_pcon_whitelist_count(); i++) + KUNIT_EXPECT_TRUE(test, + dm_is_freesync_pcon_whitelist(dm_freesync_pcon_whitelist[i])); +} + +/** + * dm_test_freesync_pcon_whitelist_not_in_list - Test Freesync pcon whitelist not in list + * @test: The KUnit test context + */ +static void dm_test_freesync_pcon_whitelist_not_in_list(struct kunit *test) +{ + /* 0xFFFFFF is not a known whitelist device */ + KUNIT_EXPECT_FALSE(test, dm_is_freesync_pcon_whitelist(0xFFFFFF)); +} + +/** + * dm_test_freesync_pcon_whitelist_zero - Test Freesync pcon whitelist zero + * @test: The KUnit test context + */ +static void dm_test_freesync_pcon_whitelist_zero(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, dm_is_freesync_pcon_whitelist(0)); +} + +/* Tests for populate_hdmi_info_from_connector() */ + +/** + * dm_test_populate_hdmi_scdc_present_true - Test Populate hdmi scdc present true + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_scdc_present_true(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->scdc.supported = true; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_TRUE(test, caps->scdc_present); +} + +/** + * dm_test_populate_hdmi_scdc_present_false - Test Populate hdmi scdc present false + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_scdc_present_false(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->scdc.supported = false; + caps->scdc_present = true; /* pre-set to confirm it gets cleared */ + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_FALSE(test, caps->scdc_present); +} + +/** + * dm_test_populate_hdmi_frl_dsc_10bpc - Test HDMI FRL DSC 10 bpc caps + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_frl_dsc_10bpc(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->max_lanes = 4; + hdmi->max_frl_rate_per_lane = 12; + hdmi->dsc_cap.v_1p2 = true; + hdmi->dsc_cap.bpc_supported = 10; + hdmi->dsc_cap.all_bpp = true; + hdmi->dsc_cap.native_420 = true; + hdmi->dsc_cap.max_slices = 8; + hdmi->dsc_cap.clk_per_slice = 400; + hdmi->dsc_cap.max_lanes = 4; + hdmi->dsc_cap.max_frl_rate_per_lane = 10; + hdmi->dsc_cap.total_chunk_kbytes = 7; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 6); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_12bpc); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_all_bpp); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_native_420); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 5); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 5); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_total_chunk_kbytes, 7); +} + +/** + * dm_test_populate_hdmi_frl_dsc_12bpc - Test HDMI FRL DSC 12 bpc caps + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_frl_dsc_12bpc(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->max_lanes = 3; + hdmi->max_frl_rate_per_lane = 6; + hdmi->dsc_cap.v_1p2 = true; + hdmi->dsc_cap.bpc_supported = 12; + hdmi->dsc_cap.max_slices = 16; + hdmi->dsc_cap.clk_per_slice = 400; + hdmi->dsc_cap.max_lanes = 3; + hdmi->dsc_cap.max_frl_rate_per_lane = 3; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 2); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_12bpc); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 7); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 1); +} + +/** + * dm_test_populate_hdmi_frl_dsc_unknown_values - Test HDMI FRL DSC unknown values + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_frl_dsc_unknown_values(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->max_lanes = 2; + hdmi->max_frl_rate_per_lane = 3; + hdmi->dsc_cap.v_1p2 = true; + hdmi->dsc_cap.bpc_supported = 8; + hdmi->dsc_cap.max_slices = 3; + hdmi->dsc_cap.clk_per_slice = 340; + hdmi->dsc_cap.max_lanes = 2; + hdmi->dsc_cap.max_frl_rate_per_lane = 12; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 0); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_12bpc); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 0); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 0); +} + +/* Tests for dm_get_adaptive_sync_support_type() */ + +/** + * dm_test_adaptive_sync_type_none_default - Test Adaptive sync type none default + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_none_default(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* dongle_type = 0 (DISPLAY_DONGLE_NONE) → default case → TYPE_NONE */ + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/** + * dm_test_adaptive_sync_type_converter_no_conditions - Converter without caps + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_converter_no_conditions(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* HDMI converter but no adaptive sync cap → still NONE */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/** + * dm_test_adaptive_sync_type_converter_partial_conditions - Partial caps + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_converter_partial_conditions(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Cap set and whitelist ID, but allow_invalid_MSA_timing_param = false */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + link->dpcd_caps.allow_invalid_MSA_timing_param = false; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/** + * dm_test_adaptive_sync_type_pcon_whitelist - Test Adaptive sync type pcon whitelist + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_pcon_whitelist(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* All conditions met → FREESYNC_TYPE_PCON_IN_WHITELIST */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + link->dpcd_caps.allow_invalid_MSA_timing_param = true; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)FREESYNC_TYPE_PCON_IN_WHITELIST); +} + +/** + * dm_test_adaptive_sync_type_converter_nonwhitelist - Converter not whitelisted + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_converter_nonwhitelist(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* All conditions met but branch_dev_id not in whitelist → NONE */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + link->dpcd_caps.allow_invalid_MSA_timing_param = true; + link->dpcd_caps.branch_dev_id = 0xFFFFFF; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/* Tests for dm_helpers_is_fullscreen() and dm_helpers_is_hdr_on() */ + +/** + * dm_test_helpers_is_fullscreen_returns_false - Test Helpers is fullscreen returns false + * @test: The KUnit test context + */ +static void dm_test_helpers_is_fullscreen_returns_false(struct kunit *test) +{ + /* Stub — always returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_is_fullscreen(NULL, NULL)); +} + +/** + * dm_test_helpers_is_hdr_on_returns_false - Test Helpers is hdr on returns false + * @test: The KUnit test context + */ +static void dm_test_helpers_is_hdr_on_returns_false(struct kunit *test) +{ + /* Stub — always returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_is_hdr_on(NULL, NULL)); +} + +/* Tests for get_max_frl_rate() */ + +/** + * dm_test_get_max_frl_rate_3lanes_3gbps - Test Get max frl rate 3lanes 3gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_3lanes_3gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(3, 3), 1); +} + +/** + * dm_test_get_max_frl_rate_3lanes_6gbps - Test Get max frl rate 3lanes 6gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_3lanes_6gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(3, 6), 2); +} + +/** + * dm_test_get_max_frl_rate_4lanes_6gbps - Test Get max frl rate 4lanes 6gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_6gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 6), 3); +} + +/** + * dm_test_get_max_frl_rate_4lanes_8gbps - Test Get max frl rate 4lanes 8gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_8gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 8), 4); +} + +/** + * dm_test_get_max_frl_rate_4lanes_10gbps - Test Get max frl rate 4lanes 10gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_10gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 10), 5); +} + +/** + * dm_test_get_max_frl_rate_4lanes_12gbps - Test Get max frl rate 4lanes 12gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_12gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 12), 6); +} + +/** + * dm_test_get_max_frl_rate_unknown - Test Get max frl rate unknown + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_unknown(struct kunit *test) +{ + /* Unknown lane/rate combination → 0 */ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(2, 3), 0); +} + +/* Tests for dm_dtn_log_begin() / dm_dtn_log_append_v() / dm_dtn_log_end() */ + +/** + * dm_test_dtn_log_buffer_accumulates - Test DTN log buffer accumulation + * @test: The KUnit test context + */ +static void dm_test_dtn_log_buffer_accumulates(struct kunit *test) +{ + struct dc_log_buffer_ctx log_ctx = {0}; + + dm_dtn_log_begin(NULL, &log_ctx); + dm_dtn_log_append_v(NULL, &log_ctx, "x=%d\n", 7); + dm_dtn_log_end(NULL, &log_ctx); + + KUNIT_ASSERT_NOT_NULL(test, log_ctx.buf); + KUNIT_EXPECT_STREQ(test, log_ctx.buf, "[dtn begin]\nx=7\n[dtn end]\n"); + KUNIT_EXPECT_EQ(test, log_ctx.pos, strlen("[dtn begin]\nx=7\n[dtn end]\n")); + + kvfree(log_ctx.buf); +} + +/** + * dm_test_dtn_log_null_ctx_no_crash - Test DTN log helpers with NULL log buffer + * @test: The KUnit test context + */ +static void dm_test_dtn_log_null_ctx_no_crash(struct kunit *test) +{ + /* NULL log_ctx redirects to dmesg and must not dereference a buffer */ + dm_dtn_log_begin(NULL, NULL); + dm_dtn_log_append_v(NULL, NULL, "value %d\n", 1); + dm_dtn_log_end(NULL, NULL); + + KUNIT_EXPECT_TRUE(test, true); +} + +/* Tests for dm_helpers_dp_read_dpcd() / dm_helpers_dp_write_dpcd() */ + +/** + * dm_test_dp_read_dpcd_null_priv - Test DPCD read returns false without connector + * @test: The KUnit test context + */ +static void dm_test_dp_read_dpcd_null_priv(struct kunit *test) +{ + struct dc_link *link; + uint8_t data = 0; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* link->priv (aconnector) is NULL → early return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_read_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/** + * dm_test_dp_write_dpcd_null_priv - Test DPCD write returns false without connector + * @test: The KUnit test context + */ +static void dm_test_dp_write_dpcd_null_priv(struct kunit *test) +{ + struct dc_link *link; + uint8_t data = 0; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* link->priv (aconnector) is NULL → early return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/* Tests for dm_helpers_dp_mst_start_top_mgr() / dm_helpers_dp_mst_stop_top_mgr() */ + +/** + * dm_test_mst_start_top_mgr_null_priv - Test MST start returns false without connector + * @test: The KUnit test context + */ +static void dm_test_mst_start_top_mgr_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, false)); +} + +/** + * dm_test_mst_stop_top_mgr_null_priv - Test MST stop returns false without connector + * @test: The KUnit test context + */ +static void dm_test_mst_stop_top_mgr_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_stop_top_mgr(NULL, link)); +} + +/** + * dm_test_mst_start_top_mgr_boot - Test MST start boot path on a connector-backed link + * @test: The KUnit test context + * + * Uses the DRM KUnit mock device to back the connector so the link is a + * realistic connector-backed link. The boot path short-circuits and returns + * true without touching the MST topology manager. + */ +static void dm_test_mst_start_top_mgr_boot(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + struct dc_link *link; + + 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); + aconnector->base.dev = drm; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + link->priv = aconnector; + + KUNIT_EXPECT_TRUE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, true)); +} + +/* Tests for dm_helpers_dp_write_hblank_reduction() */ + +/** + * dm_test_dp_write_hblank_reduction_false - Test hblank reduction stub returns false + * @test: The KUnit test context + */ +static void dm_test_dp_write_hblank_reduction_false(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_hblank_reduction(NULL, NULL)); +} + +static struct kunit_case amdgpu_dm_helpers_test_cases[] = { + /* edid_extract_panel_id */ + KUNIT_CASE(dm_test_edid_extract_panel_id_basic), + KUNIT_CASE(dm_test_edid_extract_panel_id_zeros), + /* dm_is_freesync_pcon_whitelist */ + KUNIT_CASE(dm_test_freesync_pcon_whitelist_all_known), + KUNIT_CASE(dm_test_freesync_pcon_whitelist_not_in_list), + KUNIT_CASE(dm_test_freesync_pcon_whitelist_zero), + /* populate_hdmi_info_from_connector */ + KUNIT_CASE(dm_test_populate_hdmi_scdc_present_true), + KUNIT_CASE(dm_test_populate_hdmi_scdc_present_false), + KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_10bpc), + KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_12bpc), + KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_unknown_values), + /* dm_get_adaptive_sync_support_type */ + KUNIT_CASE(dm_test_adaptive_sync_type_none_default), + KUNIT_CASE(dm_test_adaptive_sync_type_converter_no_conditions), + KUNIT_CASE(dm_test_adaptive_sync_type_converter_partial_conditions), + KUNIT_CASE(dm_test_adaptive_sync_type_pcon_whitelist), + KUNIT_CASE(dm_test_adaptive_sync_type_converter_nonwhitelist), + /* dm_helpers_is_fullscreen / dm_helpers_is_hdr_on */ + KUNIT_CASE(dm_test_helpers_is_fullscreen_returns_false), + KUNIT_CASE(dm_test_helpers_is_hdr_on_returns_false), + /* get_max_frl_rate */ + KUNIT_CASE(dm_test_get_max_frl_rate_3lanes_3gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_3lanes_6gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_6gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_8gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_10gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_12gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_unknown), + /* dm_dtn_log_begin / dm_dtn_log_append_v / dm_dtn_log_end */ + KUNIT_CASE(dm_test_dtn_log_buffer_accumulates), + KUNIT_CASE(dm_test_dtn_log_null_ctx_no_crash), + /* dm_helpers_dp_read_dpcd / dm_helpers_dp_write_dpcd */ + KUNIT_CASE(dm_test_dp_read_dpcd_null_priv), + KUNIT_CASE(dm_test_dp_write_dpcd_null_priv), + /* dm_helpers_dp_mst_start_top_mgr / dm_helpers_dp_mst_stop_top_mgr */ + KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv), + KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv), + KUNIT_CASE(dm_test_mst_start_top_mgr_boot), + /* dm_helpers_dp_write_hblank_reduction */ + KUNIT_CASE(dm_test_dp_write_hblank_reduction_false), + {} +}; + +static struct kunit_suite amdgpu_dm_helpers_test_suite = { + .name = "amdgpu_dm_helpers", + .test_cases = amdgpu_dm_helpers_test_cases, +}; + +kunit_test_suite(amdgpu_dm_helpers_test_suite); + +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_helpers"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 43531d423240095579ef8df6efc91e8f25840a04 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 7 May 2026 10:56:40 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_quirks Add KUnit test file amdgpu_dm_quirks_test.c covering retrieve_dmi_info(). Three test cases are provided: - Verify aux_hpd_discon_quirk is reset to false even when previously true - Verify edp0_on_dp1_quirk is reset to false even when previously true - Verify both quirks remain false on a zero-initialised dm when no DMI match is found (expected in UML/KUnit environment) Register the new test object in the tests/Makefile under CONFIG_DRM_AMD_DC_KUNIT_TEST. Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 103 +++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c 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 1da07ebf9217..cf28d50c3b5e 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,6 +28,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_kunit_helpers.h" struct amdgpu_dm_quirks { bool aux_hpd_discon; @@ -176,3 +177,4 @@ void retrieve_dmi_info(struct amdgpu_display_manager *dm) drm_info(dev, "support_edp0_on_dp1 attached\n"); } } +EXPORT_IF_KUNIT(retrieve_dmi_info); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index a067332f9f41..168ad064e7cb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_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 +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_quirks_test.o 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 new file mode 100644 index 000000000000..a09f31ee0a2a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_quirks.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" + +/* Tests for retrieve_dmi_info() */ + +/* + * Verify that retrieve_dmi_info() always initialises aux_hpd_discon_quirk to + * false, even when the caller had previously set it to true. + */ +/** + * dm_test_quirks_aux_hpd_discon_reset - Test Quirks aux hpd discon reset + * @test: The KUnit test context + */ +static void dm_test_quirks_aux_hpd_discon_reset(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + dm->aux_hpd_discon_quirk = true; + + retrieve_dmi_info(dm); + + /* + * In a KUnit / UML environment no real DMI table is present, so + * dmi_check_system() returns 0 and retrieve_dmi_info() leaves the + * quirk at its initialised-to-false value. + */ + KUNIT_EXPECT_FALSE(test, dm->aux_hpd_discon_quirk); +} + +/* + * Verify that retrieve_dmi_info() always initialises edp0_on_dp1_quirk to + * false, even when the caller had previously set it to true. + */ +/** + * dm_test_quirks_edp0_on_dp1_reset - Test Quirks edp0 on dp1 reset + * @test: The KUnit test context + */ +static void dm_test_quirks_edp0_on_dp1_reset(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + dm->edp0_on_dp1_quirk = true; + + retrieve_dmi_info(dm); + + KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk); +} + +/* + * Verify that when no DMI match is found both quirks remain false after a + * fresh (zero-initialised) dm is passed to retrieve_dmi_info(). + */ +/** + * dm_test_quirks_no_dmi_match_both_false - Test Quirks no dmi match both false + * @test: The KUnit test context + */ +static void dm_test_quirks_no_dmi_match_both_false(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + retrieve_dmi_info(dm); + + KUNIT_EXPECT_FALSE(test, dm->aux_hpd_discon_quirk); + KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk); +} + +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), + {} +}; + +static struct kunit_suite amdgpu_dm_quirks_test_suite = { + .name = "amdgpu_dm_quirks", + .test_cases = amdgpu_dm_quirks_tests, +}; + +kunit_test_suite(amdgpu_dm_quirks_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_quirks"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 652021e4be963b5ec1c86ba844fd40d0e01decc9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 29 May 2026 17:12:31 -0600 Subject: drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu Expand KUnit coverage of amdgpu_dm_pp_smu.c and extract several pure translation helpers so they can be unit tested in isolation. Extract pure logic into testable helpers: - build_pm_display_cfg() from dm_pp_apply_display_requirements() - build_wm_clock_ranges_soc15() from pp_rv_set_wm_ranges() - cap_clock_levels_to_validation() from dm_pp_get_clock_levels_by_type() - pp_smu_nv_clock_id_to_pp() from pp_nv_set_voltage_by_freq() Tests cover: - pp_to_dc_clock_levels: within-limit copy and count capping - pp_to_dc_clock_levels_with_latency: field copy and count capping - pp_to_dc_clock_levels_with_voltage: field copy and count capping - dm_pp_get_funcs: RV, RV 1.01, NV, RN, and unsupported versions - dm_pp_apply_display_requirements: DPM-disabled early-return path - dm_pp_apply_clock_for_voltage_request: invalid clock type path - build_pm_display_cfg: scalar field scaling and per-display mapping - build_wm_clock_ranges_soc15: DMIF and MCIF range translation - cap_clock_levels_to_validation: engine/memory capping and floor - pp_smu_nv_clock_id_to_pp: valid ids and invalid-id rejection Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 234 ++++--- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 22 + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 736 +++++++++++++++++++++ 3 files changed, 889 insertions(+), 103 deletions(-) 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 ca7141dbdf6a..e0fe4cb97f31 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 @@ -36,72 +36,64 @@ #include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_pp_smu.h" -bool dm_pp_apply_display_requirements( - const struct dc_context *ctx, +STATIC_IFN_KUNIT void build_pm_display_cfg( + struct amd_pp_display_configuration *pm_display_cfg, const struct dm_pp_display_configuration *pp_display_cfg) { - struct amdgpu_device *adev = ctx->driver_context; int i; - if (adev->pm.dpm_enabled) { + memset(pm_display_cfg, 0, sizeof(*pm_display_cfg)); - memset(&adev->pm.pm_display_cfg, 0, - sizeof(adev->pm.pm_display_cfg)); + pm_display_cfg->cpu_cc6_disable = pp_display_cfg->cpu_cc6_disable; + pm_display_cfg->cpu_pstate_disable = pp_display_cfg->cpu_pstate_disable; + pm_display_cfg->cpu_pstate_separation_time = pp_display_cfg->cpu_pstate_separation_time; + pm_display_cfg->nb_pstate_switch_disable = pp_display_cfg->nb_pstate_switch_disable; - adev->pm.pm_display_cfg.cpu_cc6_disable = - pp_display_cfg->cpu_cc6_disable; + pm_display_cfg->num_display = pp_display_cfg->display_count; + pm_display_cfg->num_path_including_non_display = pp_display_cfg->display_count; - adev->pm.pm_display_cfg.cpu_pstate_disable = - pp_display_cfg->cpu_pstate_disable; + pm_display_cfg->min_core_set_clock = pp_display_cfg->min_engine_clock_khz/10; + pm_display_cfg->min_core_set_clock_in_sr = + pp_display_cfg->min_engine_clock_deep_sleep_khz/10; + pm_display_cfg->min_mem_set_clock = pp_display_cfg->min_memory_clock_khz/10; - adev->pm.pm_display_cfg.cpu_pstate_separation_time = - pp_display_cfg->cpu_pstate_separation_time; + pm_display_cfg->min_dcef_deep_sleep_set_clk = + pp_display_cfg->min_engine_clock_deep_sleep_khz/10; + pm_display_cfg->min_dcef_set_clk = pp_display_cfg->min_dcfclock_khz/10; - adev->pm.pm_display_cfg.nb_pstate_switch_disable = - pp_display_cfg->nb_pstate_switch_disable; + pm_display_cfg->multi_monitor_in_sync = pp_display_cfg->all_displays_in_sync; + pm_display_cfg->min_vblank_time = pp_display_cfg->avail_mclk_switch_time_us; - adev->pm.pm_display_cfg.num_display = - pp_display_cfg->display_count; - adev->pm.pm_display_cfg.num_path_including_non_display = - pp_display_cfg->display_count; + pm_display_cfg->display_clk = pp_display_cfg->disp_clk_khz/10; - adev->pm.pm_display_cfg.min_core_set_clock = - pp_display_cfg->min_engine_clock_khz/10; - adev->pm.pm_display_cfg.min_core_set_clock_in_sr = - pp_display_cfg->min_engine_clock_deep_sleep_khz/10; - adev->pm.pm_display_cfg.min_mem_set_clock = - pp_display_cfg->min_memory_clock_khz/10; + pm_display_cfg->dce_tolerable_mclk_in_active_latency = + pp_display_cfg->avail_mclk_switch_time_in_disp_active_us; - adev->pm.pm_display_cfg.min_dcef_deep_sleep_set_clk = - pp_display_cfg->min_engine_clock_deep_sleep_khz/10; - adev->pm.pm_display_cfg.min_dcef_set_clk = - pp_display_cfg->min_dcfclock_khz/10; + pm_display_cfg->crtc_index = pp_display_cfg->crtc_index; + pm_display_cfg->line_time_in_us = pp_display_cfg->line_time_in_us; - adev->pm.pm_display_cfg.multi_monitor_in_sync = - pp_display_cfg->all_displays_in_sync; - adev->pm.pm_display_cfg.min_vblank_time = - pp_display_cfg->avail_mclk_switch_time_us; + pm_display_cfg->vrefresh = pp_display_cfg->disp_configs[0].v_refresh; + pm_display_cfg->crossfire_display_index = -1; + pm_display_cfg->min_bus_bandwidth = 0; - adev->pm.pm_display_cfg.display_clk = - pp_display_cfg->disp_clk_khz/10; - - adev->pm.pm_display_cfg.dce_tolerable_mclk_in_active_latency = - pp_display_cfg->avail_mclk_switch_time_in_disp_active_us; + for (i = 0; i < pp_display_cfg->display_count; i++) { + const struct dm_pp_single_disp_config *dc_cfg = + &pp_display_cfg->disp_configs[i]; + pm_display_cfg->displays[i].controller_id = dc_cfg->pipe_idx + 1; + pm_display_cfg->displays[i].pixel_clock = dc_cfg->pixel_clock; + } +} +EXPORT_IF_KUNIT(build_pm_display_cfg); - adev->pm.pm_display_cfg.crtc_index = pp_display_cfg->crtc_index; - adev->pm.pm_display_cfg.line_time_in_us = - pp_display_cfg->line_time_in_us; +bool dm_pp_apply_display_requirements( + const struct dc_context *ctx, + const struct dm_pp_display_configuration *pp_display_cfg) +{ + struct amdgpu_device *adev = ctx->driver_context; - adev->pm.pm_display_cfg.vrefresh = pp_display_cfg->disp_configs[0].v_refresh; - adev->pm.pm_display_cfg.crossfire_display_index = -1; - adev->pm.pm_display_cfg.min_bus_bandwidth = 0; + if (adev->pm.dpm_enabled) { - for (i = 0; i < pp_display_cfg->display_count; i++) { - const struct dm_pp_single_disp_config *dc_cfg = - &pp_display_cfg->disp_configs[i]; - adev->pm.pm_display_cfg.displays[i].controller_id = dc_cfg->pipe_idx + 1; - adev->pm.pm_display_cfg.displays[i].pixel_clock = dc_cfg->pixel_clock; - } + build_pm_display_cfg(&adev->pm.pm_display_cfg, pp_display_cfg); amdgpu_dpm_display_configuration_change(adev, &adev->pm.pm_display_cfg); @@ -110,6 +102,7 @@ bool dm_pp_apply_display_requirements( return true; } +EXPORT_IF_KUNIT(dm_pp_apply_display_requirements); STATIC_IFN_KUNIT void get_default_clock_levels( enum dm_pp_clock_type clk_type, @@ -187,7 +180,7 @@ STATIC_IFN_KUNIT enum amd_pp_clock_type dc_to_pp_clock_type( } EXPORT_IF_KUNIT(dc_to_pp_clock_type); -static void pp_to_dc_clock_levels( +STATIC_IFN_KUNIT void pp_to_dc_clock_levels( const struct amd_pp_clocks *pp_clks, struct dm_pp_clock_levels *dc_clks, enum dm_pp_clock_type dc_clk_type) @@ -212,8 +205,9 @@ static void pp_to_dc_clock_levels( dc_clks->clocks_in_khz[i] = pp_clks->clock[i]; } } +EXPORT_IF_KUNIT(pp_to_dc_clock_levels); -static void pp_to_dc_clock_levels_with_latency( +STATIC_IFN_KUNIT void pp_to_dc_clock_levels_with_latency( const struct pp_clock_levels_with_latency *pp_clks, struct dm_pp_clock_levels_with_latency *clk_level_info, enum dm_pp_clock_type dc_clk_type) @@ -239,8 +233,9 @@ static void pp_to_dc_clock_levels_with_latency( clk_level_info->data[i].latency_in_us = pp_clks->data[i].latency_in_us; } } +EXPORT_IF_KUNIT(pp_to_dc_clock_levels_with_latency); -static void pp_to_dc_clock_levels_with_voltage( +STATIC_IFN_KUNIT void pp_to_dc_clock_levels_with_voltage( const struct pp_clock_levels_with_voltage *pp_clks, struct dm_pp_clock_levels_with_voltage *clk_level_info, enum dm_pp_clock_type dc_clk_type) @@ -267,6 +262,41 @@ static void pp_to_dc_clock_levels_with_voltage( clk_level_info->data[i].voltage_in_mv = pp_clks->data[i].voltage_in_mv; } } +EXPORT_IF_KUNIT(pp_to_dc_clock_levels_with_voltage); + +STATIC_IFN_KUNIT void cap_clock_levels_to_validation( + struct dm_pp_clock_levels *dc_clks, + enum dm_pp_clock_type clk_type, + const struct amd_pp_simple_clock_info *validation_clks) +{ + uint32_t i; + + /* Determine the highest non-boosted level from the Validation Clocks */ + if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) { + for (i = 0; i < dc_clks->num_levels; i++) { + if (dc_clks->clocks_in_khz[i] > validation_clks->engine_max_clock) { + /* This clock is higher the validation clock. + * Than means the previous one is the highest + * non-boosted one. + */ + DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n", + dc_clks->num_levels, i); + dc_clks->num_levels = i > 0 ? i : 1; + break; + } + } + } else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) { + for (i = 0; i < dc_clks->num_levels; i++) { + if (dc_clks->clocks_in_khz[i] > validation_clks->memory_max_clock) { + DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n", + dc_clks->num_levels, i); + dc_clks->num_levels = i > 0 ? i : 1; + break; + } + } + } +} +EXPORT_IF_KUNIT(cap_clock_levels_to_validation); bool dm_pp_get_clock_levels_by_type( const struct dc_context *ctx, @@ -276,7 +306,6 @@ bool dm_pp_get_clock_levels_by_type( struct amdgpu_device *adev = ctx->driver_context; struct amd_pp_clocks pp_clks = { 0 }; struct amd_pp_simple_clock_info validation_clks = { 0 }; - uint32_t i; if (amdgpu_dpm_get_clock_by_type(adev, dc_to_pp_clock_type(clk_type), &pp_clks)) { @@ -304,30 +333,7 @@ bool dm_pp_get_clock_levels_by_type( validation_clks.engine_max_clock *= 10; validation_clks.memory_max_clock *= 10; - /* Determine the highest non-boosted level from the Validation Clocks */ - if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) { - for (i = 0; i < dc_clks->num_levels; i++) { - if (dc_clks->clocks_in_khz[i] > validation_clks.engine_max_clock) { - /* This clock is higher the validation clock. - * Than means the previous one is the highest - * non-boosted one. - */ - DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n", - dc_clks->num_levels, i); - dc_clks->num_levels = i > 0 ? i : 1; - break; - } - } - } else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) { - for (i = 0; i < dc_clks->num_levels; i++) { - if (dc_clks->clocks_in_khz[i] > validation_clks.memory_max_clock) { - DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n", - dc_clks->num_levels, i); - dc_clks->num_levels = i > 0 ? i : 1; - break; - } - } - } + cap_clock_levels_to_validation(dc_clks, clk_type, &validation_clks); return true; } @@ -411,26 +417,26 @@ bool dm_pp_apply_clock_for_voltage_request( return true; } +EXPORT_IF_KUNIT(dm_pp_apply_clock_for_voltage_request); -static void pp_rv_set_wm_ranges(struct pp_smu *pp, - struct pp_smu_wm_range_sets *ranges) +STATIC_IFN_KUNIT void build_wm_clock_ranges_soc15( + const struct pp_smu_wm_range_sets *ranges, + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm_with_clock_ranges) { - const struct dc_context *ctx = pp->dm; - struct amdgpu_device *adev = ctx->driver_context; - struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges; - struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks = wm_with_clock_ranges.wm_dmif_clocks_ranges; - struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks = wm_with_clock_ranges.wm_mcif_clocks_ranges; + struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks = + wm_with_clock_ranges->wm_dmif_clocks_ranges; + struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks = + wm_with_clock_ranges->wm_mcif_clocks_ranges; int32_t i; - wm_with_clock_ranges.num_wm_dmif_sets = ranges->num_reader_wm_sets; - wm_with_clock_ranges.num_wm_mcif_sets = ranges->num_writer_wm_sets; + wm_with_clock_ranges->num_wm_dmif_sets = ranges->num_reader_wm_sets; + wm_with_clock_ranges->num_wm_mcif_sets = ranges->num_writer_wm_sets; - for (i = 0; i < wm_with_clock_ranges.num_wm_dmif_sets; i++) { + for (i = 0; i < wm_with_clock_ranges->num_wm_dmif_sets; i++) { if (ranges->reader_wm_sets[i].wm_inst > 3) wm_dce_clocks[i].wm_set_id = WM_SET_A; else - wm_dce_clocks[i].wm_set_id = - ranges->reader_wm_sets[i].wm_inst; + wm_dce_clocks[i].wm_set_id = ranges->reader_wm_sets[i].wm_inst; wm_dce_clocks[i].wm_max_dcfclk_clk_in_khz = ranges->reader_wm_sets[i].max_drain_clk_mhz * 1000; wm_dce_clocks[i].wm_min_dcfclk_clk_in_khz = @@ -441,12 +447,11 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp, ranges->reader_wm_sets[i].min_fill_clk_mhz * 1000; } - for (i = 0; i < wm_with_clock_ranges.num_wm_mcif_sets; i++) { + for (i = 0; i < wm_with_clock_ranges->num_wm_mcif_sets; i++) { if (ranges->writer_wm_sets[i].wm_inst > 3) wm_soc_clocks[i].wm_set_id = WM_SET_A; else - wm_soc_clocks[i].wm_set_id = - ranges->writer_wm_sets[i].wm_inst; + wm_soc_clocks[i].wm_set_id = ranges->writer_wm_sets[i].wm_inst; wm_soc_clocks[i].wm_max_socclk_clk_in_khz = ranges->writer_wm_sets[i].max_fill_clk_mhz * 1000; wm_soc_clocks[i].wm_min_socclk_clk_in_khz = @@ -456,6 +461,17 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp, wm_soc_clocks[i].wm_min_mem_clk_in_khz = ranges->writer_wm_sets[i].min_drain_clk_mhz * 1000; } +} +EXPORT_IF_KUNIT(build_wm_clock_ranges_soc15); + +static void pp_rv_set_wm_ranges(struct pp_smu *pp, + struct pp_smu_wm_range_sets *ranges) +{ + const struct dc_context *ctx = pp->dm; + struct amdgpu_device *adev = ctx->driver_context; + struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges; + + build_wm_clock_ranges_soc15(ranges, &wm_with_clock_ranges); amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, &wm_with_clock_ranges); @@ -604,27 +620,38 @@ static enum pp_smu_status pp_nv_set_pstate_handshake_support( return PP_SMU_RESULT_OK; } -static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, - enum pp_smu_nv_clock_id clock_id, int mhz) +STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, + enum amd_pp_clock_type *clock_type) { - const struct dc_context *ctx = pp->dm; - struct amdgpu_device *adev = ctx->driver_context; - struct pp_display_clock_request clock_req; - int ret = 0; - switch (clock_id) { case PP_SMU_NV_DISPCLK: - clock_req.clock_type = amd_pp_disp_clock; + *clock_type = amd_pp_disp_clock; break; case PP_SMU_NV_PHYCLK: - clock_req.clock_type = amd_pp_phy_clock; + *clock_type = amd_pp_phy_clock; break; case PP_SMU_NV_PIXELCLK: - clock_req.clock_type = amd_pp_pixel_clock; + *clock_type = amd_pp_pixel_clock; break; default: - break; + return false; } + + return true; +} +EXPORT_IF_KUNIT(pp_smu_nv_clock_id_to_pp); + +static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, + enum pp_smu_nv_clock_id clock_id, int mhz) +{ + const struct dc_context *ctx = pp->dm; + struct amdgpu_device *adev = ctx->driver_context; + struct pp_display_clock_request clock_req; + int ret = 0; + + if (!pp_smu_nv_clock_id_to_pp(clock_id, &clock_req.clock_type)) + return PP_SMU_RESULT_FAIL; + clock_req.clock_freq_in_khz = mhz * 1000; /* 0: successful or smu.ppt_funcs->display_clock_voltage_request = NULL @@ -744,3 +771,4 @@ void dm_pp_get_funcs( break; } } +EXPORT_IF_KUNIT(dm_pp_get_funcs); 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 827b60d5affe..e851e3ee5b63 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 @@ -8,9 +8,31 @@ #include "dm_pp_interface.h" +struct amd_pp_display_configuration; +struct pp_smu_wm_range_sets; +struct dm_pp_wm_sets_with_clock_ranges_soc15; + #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void build_pm_display_cfg(struct amd_pp_display_configuration *pm_display_cfg, + const struct dm_pp_display_configuration *pp_display_cfg); +void build_wm_clock_ranges_soc15(const struct pp_smu_wm_range_sets *ranges, + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm_with_clock_ranges); void get_default_clock_levels(enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks); enum amd_pp_clock_type dc_to_pp_clock_type(enum dm_pp_clock_type dm_pp_clk_type); +void pp_to_dc_clock_levels(const struct amd_pp_clocks *pp_clks, + struct dm_pp_clock_levels *dc_clks, + enum dm_pp_clock_type dc_clk_type); +void pp_to_dc_clock_levels_with_latency(const struct pp_clock_levels_with_latency *pp_clks, + struct dm_pp_clock_levels_with_latency *clk_level_info, + enum dm_pp_clock_type dc_clk_type); +void pp_to_dc_clock_levels_with_voltage(const struct pp_clock_levels_with_voltage *pp_clks, + struct dm_pp_clock_levels_with_voltage *clk_level_info, + enum dm_pp_clock_type dc_clk_type); +void cap_clock_levels_to_validation(struct dm_pp_clock_levels *dc_clks, + enum dm_pp_clock_type clk_type, + const struct amd_pp_simple_clock_info *validation_clks); +bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, + enum amd_pp_clock_type *clock_type); #endif #endif /* __AMDGPU_DM_PP_SMU_H__ */ 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 556473f55ebe..dbb6dfd5c284 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 @@ -9,6 +9,9 @@ #include #include "dc.h" +#include "dm_services.h" +#include "dm_pp_smu.h" +#include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_pp_smu.h" @@ -210,6 +213,704 @@ static void dm_test_dc_to_pp_clock_type_invalid(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(0), 0); } +/* ---- Tests for pp_to_dc_clock_levels ---- */ + +/** + * dm_test_pp_to_dc_clock_levels_within_limit - Test normal copy within limit + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels correctly copies clock values when the + * count is within DM_PP_MAX_CLOCK_LEVELS. + */ +static void dm_test_pp_to_dc_clock_levels_within_limit(struct kunit *test) +{ + struct amd_pp_clocks pp_clks = {}; + struct dm_pp_clock_levels dc_clks = {}; + + pp_clks.count = 3; + pp_clks.clock[0] = 300000; + pp_clks.clock[1] = 500000; + pp_clks.clock[2] = 700000; + + pp_to_dc_clock_levels(&pp_clks, &dc_clks, DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 3U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[2], 700000U); +} + +/** + * dm_test_pp_to_dc_clock_levels_caps_at_max - Test count capping at max + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels caps num_levels at DM_PP_MAX_CLOCK_LEVELS + * when the input count exceeds the maximum. + */ +static void dm_test_pp_to_dc_clock_levels_caps_at_max(struct kunit *test) +{ + struct amd_pp_clocks pp_clks = {}; + struct dm_pp_clock_levels dc_clks = {}; + uint32_t i; + + pp_clks.count = DM_PP_MAX_CLOCK_LEVELS + 1; + for (i = 0; i < DM_PP_MAX_CLOCK_LEVELS; i++) + pp_clks.clock[i] = (i + 1) * 100000; + + pp_to_dc_clock_levels(&pp_clks, &dc_clks, DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS); +} + +/* ---- Tests for pp_to_dc_clock_levels_with_latency ---- */ + +/** + * dm_test_pp_to_dc_clock_levels_latency_within_limit - Test normal copy + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_latency correctly copies clock + * and latency values when count is within limits. + */ +static void dm_test_pp_to_dc_clock_levels_latency_within_limit(struct kunit *test) +{ + struct pp_clock_levels_with_latency pp_clks = {}; + struct dm_pp_clock_levels_with_latency dc_clks = {}; + + pp_clks.num_levels = 2; + pp_clks.data[0].clocks_in_khz = 400000; + pp_clks.data[0].latency_in_us = 10; + pp_clks.data[1].clocks_in_khz = 800000; + pp_clks.data[1].latency_in_us = 20; + + pp_to_dc_clock_levels_with_latency(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].clocks_in_khz, 400000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].latency_in_us, 10U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].clocks_in_khz, 800000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].latency_in_us, 20U); +} + +/** + * dm_test_pp_to_dc_clock_levels_latency_caps_at_max - Test count capping + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_latency caps num_levels at + * DM_PP_MAX_CLOCK_LEVELS when input exceeds the maximum. + */ +static void dm_test_pp_to_dc_clock_levels_latency_caps_at_max(struct kunit *test) +{ + struct pp_clock_levels_with_latency pp_clks = {}; + struct dm_pp_clock_levels_with_latency dc_clks = {}; + + pp_clks.num_levels = DM_PP_MAX_CLOCK_LEVELS + 1; + + pp_to_dc_clock_levels_with_latency(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS); +} + +/* ---- Tests for pp_to_dc_clock_levels_with_voltage ---- */ + +/** + * dm_test_pp_to_dc_clock_levels_voltage_within_limit - Test normal copy + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_voltage correctly copies clock + * and voltage values when count is within limits. + */ +static void dm_test_pp_to_dc_clock_levels_voltage_within_limit(struct kunit *test) +{ + struct pp_clock_levels_with_voltage pp_clks = {}; + struct dm_pp_clock_levels_with_voltage dc_clks = {}; + + pp_clks.num_levels = 2; + pp_clks.data[0].clocks_in_khz = 300000; + pp_clks.data[0].voltage_in_mv = 800; + pp_clks.data[1].clocks_in_khz = 600000; + pp_clks.data[1].voltage_in_mv = 950; + + pp_to_dc_clock_levels_with_voltage(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_MEMORY_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].clocks_in_khz, 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].voltage_in_mv, 800U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].clocks_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].voltage_in_mv, 950U); +} + +/** + * dm_test_pp_to_dc_clock_levels_voltage_caps_at_max - Test count capping + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_voltage caps num_levels at + * DM_PP_MAX_CLOCK_LEVELS when input exceeds the maximum. + */ +static void dm_test_pp_to_dc_clock_levels_voltage_caps_at_max(struct kunit *test) +{ + struct pp_clock_levels_with_voltage pp_clks = {}; + struct dm_pp_clock_levels_with_voltage dc_clks = {}; + + pp_clks.num_levels = DM_PP_MAX_CLOCK_LEVELS + 1; + + pp_to_dc_clock_levels_with_voltage(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_MEMORY_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS); +} + +/* ---- Tests for dm_pp_get_funcs ---- */ + +/** + * dm_test_get_funcs_rv - Test Raven PP SMU function table setup + * @test: KUnit test context + * + * Verify that DCN 1.0 initializes the Raven SMU function table and stores + * the DC context in the PP SMU handle. + */ +static void dm_test_get_funcs_rv(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_1_0; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RV); + KUNIT_EXPECT_PTR_EQ(test, funcs->rv_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_wm_ranges != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_pme_wa_enable != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_display_count != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_min_deep_sleep_dcfclk != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_hard_min_dcfclk_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_hard_min_fclk_by_freq != NULL); + KUNIT_EXPECT_FALSE(test, funcs->rv_funcs.set_hard_min_socclk_by_freq != NULL); +} + +/** + * dm_test_get_funcs_rv_101 - Test DCN 1.01 Raven PP SMU setup + * @test: KUnit test context + * + * Verify that DCN 1.01 uses the same Raven SMU function table as DCN 1.0. + */ +static void dm_test_get_funcs_rv_101(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_1_01; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RV); + KUNIT_EXPECT_PTR_EQ(test, funcs->rv_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_display_count != NULL); +} + +/** + * dm_test_get_funcs_nv - Test Navi PP SMU function table setup + * @test: KUnit test context + * + * Verify that DCN 2.0 initializes the Navi SMU function table and leaves the + * unsupported PME workaround callback unset. + */ +static void dm_test_get_funcs_nv(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_2_0; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_NV); + KUNIT_EXPECT_PTR_EQ(test, funcs->nv_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_display_count != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_hard_min_dcfclk_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_min_deep_sleep_dcfclk != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_voltage_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_wm_ranges != NULL); + KUNIT_EXPECT_FALSE(test, funcs->nv_funcs.set_pme_wa_enable != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_hard_min_uclk_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.get_maximum_sustainable_clocks != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.get_uclk_dpm_states != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_pstate_handshake_support != NULL); +} + +/** + * dm_test_get_funcs_rn - Test Renoir PP SMU function table setup + * @test: KUnit test context + * + * Verify that DCN 2.1 initializes the Renoir SMU function table. + */ +static void dm_test_get_funcs_rn(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_2_1; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RN); + KUNIT_EXPECT_PTR_EQ(test, funcs->rn_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->rn_funcs.set_wm_ranges != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rn_funcs.get_dpm_clock_table != NULL); +} + +/** + * dm_test_get_funcs_unsupported - Test unsupported DCE version handling + * @test: KUnit test context + * + * Verify that unsupported DCE versions do not initialize a PP SMU version or + * function table callbacks. + */ +static void dm_test_get_funcs_unsupported(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCE_VERSION_MAX; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_UNSUPPORTED); + KUNIT_EXPECT_FALSE(test, funcs->rv_funcs.set_wm_ranges != NULL); +} + +/* ---- Tests for amdgpu_device-backed entry points ---- */ + +/** + * dm_test_apply_display_requirements_dpm_disabled - Test DPM-disabled path + * @test: KUnit test context + * + * Verify that dm_pp_apply_display_requirements returns true without touching + * the display configuration when DPM is disabled. + */ +static void dm_test_apply_display_requirements_dpm_disabled(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_display_configuration cfg = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + adev->pm.dpm_enabled = false; + ctx->driver_context = adev; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_display_requirements(ctx, &cfg)); +} + +/** + * dm_test_apply_clock_for_voltage_invalid_type - Test invalid clock type path + * @test: KUnit test context + * + * Verify that dm_pp_apply_clock_for_voltage_request returns false for a clock + * type that does not map to a valid PP clock type, taking the early-return + * path before any SMU request is issued. + */ +static void dm_test_apply_clock_for_voltage_invalid_type(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->driver_context = adev; + req.clk_type = (enum dm_pp_clock_type)0xffff; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_FALSE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/* ---- Tests for build_pm_display_cfg ---- */ + +/** + * dm_test_build_pm_display_cfg_scalar_fields - Test scalar field translation + * @test: KUnit test context + * + * Verify that build_pm_display_cfg copies the pass-through fields and applies + * the /10 (10 kHz) scaling, and sets the fixed constants. + */ +static void dm_test_build_pm_display_cfg_scalar_fields(struct kunit *test) +{ + struct amd_pp_display_configuration *pm = + kunit_kzalloc(test, sizeof(*pm), GFP_KERNEL); + struct dm_pp_display_configuration *pp = + kunit_kzalloc(test, sizeof(*pp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pm); + KUNIT_ASSERT_NOT_NULL(test, pp); + + pp->cpu_cc6_disable = true; + pp->cpu_pstate_disable = true; + pp->cpu_pstate_separation_time = 7; + pp->nb_pstate_switch_disable = true; + pp->display_count = 2; + pp->min_engine_clock_khz = 300000; + pp->min_engine_clock_deep_sleep_khz = 50000; + pp->min_memory_clock_khz = 800000; + pp->min_dcfclock_khz = 600000; + pp->all_displays_in_sync = true; + pp->avail_mclk_switch_time_us = 11; + pp->disp_clk_khz = 400000; + pp->avail_mclk_switch_time_in_disp_active_us = 13; + pp->crtc_index = 3; + pp->line_time_in_us = 17; + pp->disp_configs[0].v_refresh = 60; + + build_pm_display_cfg(pm, pp); + + KUNIT_EXPECT_TRUE(test, pm->cpu_cc6_disable); + KUNIT_EXPECT_TRUE(test, pm->cpu_pstate_disable); + KUNIT_EXPECT_EQ(test, pm->cpu_pstate_separation_time, 7); + KUNIT_EXPECT_TRUE(test, pm->nb_pstate_switch_disable); + KUNIT_EXPECT_EQ(test, pm->num_display, 2); + KUNIT_EXPECT_EQ(test, pm->num_path_including_non_display, 2); + KUNIT_EXPECT_EQ(test, pm->min_core_set_clock, 30000); + KUNIT_EXPECT_EQ(test, pm->min_core_set_clock_in_sr, 5000); + KUNIT_EXPECT_EQ(test, pm->min_mem_set_clock, 80000); + KUNIT_EXPECT_EQ(test, pm->min_dcef_deep_sleep_set_clk, 5000); + KUNIT_EXPECT_EQ(test, pm->min_dcef_set_clk, 60000); + KUNIT_EXPECT_TRUE(test, pm->multi_monitor_in_sync); + KUNIT_EXPECT_EQ(test, pm->min_vblank_time, 11); + KUNIT_EXPECT_EQ(test, pm->display_clk, 40000); + KUNIT_EXPECT_EQ(test, pm->dce_tolerable_mclk_in_active_latency, 13); + KUNIT_EXPECT_EQ(test, pm->crtc_index, 3); + KUNIT_EXPECT_EQ(test, pm->line_time_in_us, 17); + KUNIT_EXPECT_EQ(test, pm->vrefresh, 60); + KUNIT_EXPECT_EQ(test, pm->crossfire_display_index, -1); + KUNIT_EXPECT_EQ(test, pm->min_bus_bandwidth, 0); +} + +/** + * dm_test_build_pm_display_cfg_per_display - Test per-display translation + * @test: KUnit test context + * + * Verify that build_pm_display_cfg maps each display config, applying the + * controller_id = pipe_idx + 1 offset and copying the pixel clock. + */ +static void dm_test_build_pm_display_cfg_per_display(struct kunit *test) +{ + struct amd_pp_display_configuration *pm = + kunit_kzalloc(test, sizeof(*pm), GFP_KERNEL); + struct dm_pp_display_configuration *pp = + kunit_kzalloc(test, sizeof(*pp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pm); + KUNIT_ASSERT_NOT_NULL(test, pp); + + pp->display_count = 2; + pp->disp_configs[0].pipe_idx = 0; + pp->disp_configs[0].pixel_clock = 148500; + pp->disp_configs[1].pipe_idx = 4; + pp->disp_configs[1].pixel_clock = 297000; + + build_pm_display_cfg(pm, pp); + + KUNIT_EXPECT_EQ(test, pm->displays[0].controller_id, 1); + KUNIT_EXPECT_EQ(test, pm->displays[0].pixel_clock, 148500); + KUNIT_EXPECT_EQ(test, pm->displays[1].controller_id, 5); + KUNIT_EXPECT_EQ(test, pm->displays[1].pixel_clock, 297000); +} + +/* ---- Tests for build_wm_clock_ranges_soc15 ---- */ + +/** + * dm_test_build_wm_clock_ranges_dmif - Test reader (DMIF) watermark sets + * @test: KUnit test context + * + * Verify that build_wm_clock_ranges_soc15 copies the reader set count, + * maps wm_inst to wm_set_id (clamping instances > 3 to WM_SET_A), and + * converts every clock from MHz to kHz (x1000) into the DMIF clock ranges. + */ +static void dm_test_build_wm_clock_ranges_dmif(struct kunit *test) +{ + struct pp_smu_wm_range_sets *ranges = + kunit_kzalloc(test, sizeof(*ranges), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm = + kunit_kzalloc(test, sizeof(*wm), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ranges); + KUNIT_ASSERT_NOT_NULL(test, wm); + + ranges->num_reader_wm_sets = 2; + /* set 0: wm_inst within range -> preserved */ + ranges->reader_wm_sets[0].wm_inst = 2; + ranges->reader_wm_sets[0].max_drain_clk_mhz = 600; + ranges->reader_wm_sets[0].min_drain_clk_mhz = 300; + ranges->reader_wm_sets[0].max_fill_clk_mhz = 800; + ranges->reader_wm_sets[0].min_fill_clk_mhz = 400; + /* set 1: wm_inst > 3 -> clamped to WM_SET_A */ + ranges->reader_wm_sets[1].wm_inst = 5; + ranges->reader_wm_sets[1].max_drain_clk_mhz = 700; + ranges->reader_wm_sets[1].min_drain_clk_mhz = 350; + ranges->reader_wm_sets[1].max_fill_clk_mhz = 900; + ranges->reader_wm_sets[1].min_fill_clk_mhz = 450; + + build_wm_clock_ranges_soc15(ranges, wm); + + KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 2U); + KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 0U); + + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_set_id, WM_SET_C); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_max_dcfclk_clk_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_min_dcfclk_clk_in_khz, 300000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_max_mem_clk_in_khz, 800000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_min_mem_clk_in_khz, 400000U); + + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_set_id, WM_SET_A); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_max_dcfclk_clk_in_khz, 700000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_min_dcfclk_clk_in_khz, 350000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_max_mem_clk_in_khz, 900000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_min_mem_clk_in_khz, 450000U); +} + +/** + * dm_test_build_wm_clock_ranges_mcif - Test writer (MCIF) watermark sets + * @test: KUnit test context + * + * Verify that build_wm_clock_ranges_soc15 copies the writer set count and + * maps the writer clocks into the MCIF ranges: fill clocks become socclk + * and drain clocks become mem clk, each converted from MHz to kHz. + */ +static void dm_test_build_wm_clock_ranges_mcif(struct kunit *test) +{ + struct pp_smu_wm_range_sets *ranges = + kunit_kzalloc(test, sizeof(*ranges), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm = + kunit_kzalloc(test, sizeof(*wm), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ranges); + KUNIT_ASSERT_NOT_NULL(test, wm); + + ranges->num_writer_wm_sets = 1; + ranges->writer_wm_sets[0].wm_inst = 1; + ranges->writer_wm_sets[0].max_fill_clk_mhz = 1200; + ranges->writer_wm_sets[0].min_fill_clk_mhz = 600; + ranges->writer_wm_sets[0].max_drain_clk_mhz = 1000; + ranges->writer_wm_sets[0].min_drain_clk_mhz = 500; + + build_wm_clock_ranges_soc15(ranges, wm); + + KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 0U); + KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 1U); + + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_set_id, WM_SET_B); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_socclk_clk_in_khz, 1200000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_socclk_clk_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_mem_clk_in_khz, 1000000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_mem_clk_in_khz, 500000U); +} + +/* ---- Tests for cap_clock_levels_to_validation ---- */ + +/** + * dm_test_cap_clock_levels_engine_caps - Test engine clock level capping + * @test: KUnit test context + * + * Verify that for engine clocks, num_levels is reduced to the index of the + * first level whose frequency exceeds the engine validation clock. + */ +static void dm_test_cap_clock_levels_engine_caps(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 450000, + .memory_max_clock = 800000, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 2U); +} + +/** + * dm_test_cap_clock_levels_engine_first_exceeds - Test floor of one level + * @test: KUnit test context + * + * Verify that when the very first engine clock level already exceeds the + * validation clock, num_levels is clamped to 1 rather than 0. + */ +static void dm_test_cap_clock_levels_engine_first_exceeds(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 100000, + .memory_max_clock = 800000, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 1U); +} + +/** + * dm_test_cap_clock_levels_memory_caps - Test memory clock level capping + * @test: KUnit test context + * + * Verify that for memory clocks, num_levels is reduced based on the memory + * validation clock (and is unaffected by the engine validation clock). + */ +static void dm_test_cap_clock_levels_memory_caps(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 100000, + .memory_max_clock = 700000, + }; + + clks.num_levels = 2; + clks.clocks_in_khz[0] = 333000; + clks.clocks_in_khz[1] = 800000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_MEMORY_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 1U); +} + +/** + * dm_test_cap_clock_levels_within_limit - Test no capping when within limit + * @test: KUnit test context + * + * Verify that num_levels is left unchanged when no level exceeds the + * validation clock. + */ +static void dm_test_cap_clock_levels_within_limit(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 999000, + .memory_max_clock = 999000, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 3U); +} + +/** + * dm_test_cap_clock_levels_other_type - Test non-engine/memory types ignored + * @test: KUnit test context + * + * Verify that for clock types other than engine or memory, num_levels is + * left unchanged regardless of the validation clocks. + */ +static void dm_test_cap_clock_levels_other_type(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 1, + .memory_max_clock = 1, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_DISPLAY_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 3U); +} + +/* ---- Tests for pp_smu_nv_clock_id_to_pp ---- */ + +/** + * dm_test_nv_clock_id_dispclk - Test DISPCLK id mapping + * @test: KUnit test context + * + * Verify that PP_SMU_NV_DISPCLK maps to amd_pp_disp_clock and returns true. + */ +static void dm_test_nv_clock_id_dispclk(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_mem_clock; + + KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_DISPCLK, &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_disp_clock); +} + +/** + * dm_test_nv_clock_id_phyclk - Test PHYCLK id mapping + * @test: KUnit test context + * + * Verify that PP_SMU_NV_PHYCLK maps to amd_pp_phy_clock and returns true. + */ +static void dm_test_nv_clock_id_phyclk(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_mem_clock; + + KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_PHYCLK, &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_phy_clock); +} + +/** + * dm_test_nv_clock_id_pixelclk - Test PIXELCLK id mapping + * @test: KUnit test context + * + * Verify that PP_SMU_NV_PIXELCLK maps to amd_pp_pixel_clock and returns true. + */ +static void dm_test_nv_clock_id_pixelclk(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_mem_clock; + + KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_PIXELCLK, &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_pixel_clock); +} + +/** + * dm_test_nv_clock_id_invalid - Test unknown id is rejected + * @test: KUnit test context + * + * Verify that an unknown clock id returns false and leaves the output + * clock_type untouched, guarding against the previously uninitialized path. + */ +static void dm_test_nv_clock_id_invalid(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_dcef_clock; + + KUNIT_EXPECT_FALSE(test, pp_smu_nv_clock_id_to_pp((enum pp_smu_nv_clock_id)0xff, + &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_dcef_clock); +} + static struct kunit_case dm_pp_smu_test_cases[] = { /* get_default_clock_levels */ KUNIT_CASE(dm_test_default_clock_levels_display), @@ -227,6 +928,41 @@ static struct kunit_case dm_pp_smu_test_cases[] = { KUNIT_CASE(dm_test_dc_to_pp_clock_type_phyclk), KUNIT_CASE(dm_test_dc_to_pp_clock_type_dppclk), KUNIT_CASE(dm_test_dc_to_pp_clock_type_invalid), + /* pp_to_dc_clock_levels */ + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_within_limit), + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_caps_at_max), + /* pp_to_dc_clock_levels_with_latency */ + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_latency_within_limit), + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_latency_caps_at_max), + /* pp_to_dc_clock_levels_with_voltage */ + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_voltage_within_limit), + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_voltage_caps_at_max), + /* dm_pp_get_funcs */ + KUNIT_CASE(dm_test_get_funcs_rv), + KUNIT_CASE(dm_test_get_funcs_rv_101), + KUNIT_CASE(dm_test_get_funcs_nv), + KUNIT_CASE(dm_test_get_funcs_rn), + KUNIT_CASE(dm_test_get_funcs_unsupported), + /* amdgpu_device-backed entry points */ + KUNIT_CASE(dm_test_apply_display_requirements_dpm_disabled), + KUNIT_CASE(dm_test_apply_clock_for_voltage_invalid_type), + /* build_pm_display_cfg */ + KUNIT_CASE(dm_test_build_pm_display_cfg_scalar_fields), + KUNIT_CASE(dm_test_build_pm_display_cfg_per_display), + /* build_wm_clock_ranges_soc15 */ + KUNIT_CASE(dm_test_build_wm_clock_ranges_dmif), + KUNIT_CASE(dm_test_build_wm_clock_ranges_mcif), + /* cap_clock_levels_to_validation */ + KUNIT_CASE(dm_test_cap_clock_levels_engine_caps), + KUNIT_CASE(dm_test_cap_clock_levels_engine_first_exceeds), + KUNIT_CASE(dm_test_cap_clock_levels_memory_caps), + KUNIT_CASE(dm_test_cap_clock_levels_within_limit), + KUNIT_CASE(dm_test_cap_clock_levels_other_type), + /* pp_smu_nv_clock_id_to_pp */ + KUNIT_CASE(dm_test_nv_clock_id_dispclk), + KUNIT_CASE(dm_test_nv_clock_id_phyclk), + KUNIT_CASE(dm_test_nv_clock_id_pixelclk), + KUNIT_CASE(dm_test_nv_clock_id_invalid), {} }; -- cgit v1.2.3 From 3576a045cd200688cad25a7ad321bb1708f6bb6f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 29 May 2026 17:16:58 -0600 Subject: drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types The following existing functions are also exported for the test module: - needs_dsc_aux_workaround: detect branches needing the DSC AUX workaround - dm_mst_get_pbn_divider: compute the PBN divider from link bandwidth - amdgpu_dm_mst_reset_mst_connector_setting: reset per-connector MST state - retrieve_downstream_port_device: read downstream port presence from DPCD - retrieve_branch_specific_data: read branch OUI from the upstream device Several self-contained pieces of logic are extracted from larger functions into small testable helpers. - dm_dp_aux_transfer_result: AUX return-code to errno mapping - dm_dp_aux_fill_payload_flags: AUX request bit decode - dm_mst_msg_ready_mask: MST sideband ESI mask selection - dm_mst_select_esi_dpcd: DPCD ESI address/length selection Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 143 +++++--- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 12 + .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 385 +++++++++++++++++++++ 3 files changed, 491 insertions(+), 49 deletions(-) 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 9e1916f8f99b..b6bfe56eeb68 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 @@ -34,6 +34,7 @@ #include "dm_services.h" #include "amdgpu.h" #include "amdgpu_dm.h" +#include "dmub_cmd.h" #include "amdgpu_dm_mst_types.h" #include "amdgpu_dm_hdcp.h" @@ -44,7 +45,6 @@ #include "ddc_service_types.h" #include "dpcd_defs.h" -#include "dmub_cmd.h" #if defined(CONFIG_DEBUG_FS) #include "amdgpu_dm_debugfs.h" #endif @@ -53,6 +53,49 @@ #define PEAK_FACTOR_X1000 1006 +/* + * Translate a failed AUX transaction's operation result into an errno-style + * return value. @result is returned unchanged for AUX_RET_SUCCESS. + */ +STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer_result(ssize_t result, + enum aux_return_code_type operation_result) +{ + switch (operation_result) { + case AUX_RET_SUCCESS: + break; + case AUX_RET_ERROR_HPD_DISCON: + case AUX_RET_ERROR_UNKNOWN: + case AUX_RET_ERROR_INVALID_OPERATION: + case AUX_RET_ERROR_PROTOCOL_ERROR: + result = -EIO; + break; + case AUX_RET_ERROR_INVALID_REPLY: + case AUX_RET_ERROR_ENGINE_ACQUIRE: + result = -EBUSY; + break; + case AUX_RET_ERROR_TIMEOUT: + result = -ETIMEDOUT; + break; + } + + return result; +} +EXPORT_IF_KUNIT(dm_dp_aux_transfer_result); + +/* + * Derive the AUX payload transaction flags from a DP AUX request field. + */ +STATIC_IFN_KUNIT void dm_dp_aux_fill_payload_flags(u8 request, + struct aux_payload *payload) +{ + payload->i2c_over_aux = (request & DP_AUX_NATIVE_WRITE) == 0; + payload->write = (request & DP_AUX_I2C_READ) == 0; + payload->mot = (request & DP_AUX_I2C_MOT) != 0; + payload->write_status_update = + (request & DP_AUX_I2C_WRITE_STATUS_UPDATE) != 0; +} +EXPORT_IF_KUNIT(dm_dp_aux_fill_payload_flags); + /* * This function handles both native AUX and I2C-Over-AUX transactions. */ @@ -73,11 +116,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, payload.data = msg->buffer; payload.length = msg->size; payload.reply = &msg->reply; - payload.i2c_over_aux = (msg->request & DP_AUX_NATIVE_WRITE) == 0; - payload.write = (msg->request & DP_AUX_I2C_READ) == 0; - payload.mot = (msg->request & DP_AUX_I2C_MOT) != 0; - payload.write_status_update = - (msg->request & DP_AUX_I2C_WRITE_STATUS_UPDATE) != 0; + dm_dp_aux_fill_payload_flags(msg->request, &payload); payload.defer_delay = 0; if (payload.write) { @@ -117,23 +156,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, } if (result < 0) { - switch (operation_result) { - case AUX_RET_SUCCESS: - break; - case AUX_RET_ERROR_HPD_DISCON: - case AUX_RET_ERROR_UNKNOWN: - case AUX_RET_ERROR_INVALID_OPERATION: - case AUX_RET_ERROR_PROTOCOL_ERROR: - result = -EIO; - break; - case AUX_RET_ERROR_INVALID_REPLY: - case AUX_RET_ERROR_ENGINE_ACQUIRE: - result = -EBUSY; - break; - case AUX_RET_ERROR_TIMEOUT: - result = -ETIMEDOUT; - break; - } + result = dm_dp_aux_transfer_result(result, operation_result); drm_dbg_dp(adev_to_drm(adev), "DP AUX transfer fail:%d\n", operation_result); } @@ -184,7 +207,7 @@ amdgpu_dm_mst_connector_late_register(struct drm_connector *connector) } -static inline void +STATIC_IFN_KUNIT void amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector) { aconnector->drm_edid = NULL; @@ -193,6 +216,7 @@ amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector aconnector->mst_local_bw = 0; aconnector->vc_full_pbn = 0; } +EXPORT_IF_KUNIT(amdgpu_dm_mst_reset_mst_connector_setting); static void amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector) @@ -313,7 +337,7 @@ static bool validate_dsc_caps_on_connector(struct amdgpu_dm_connector *aconnecto } #endif -static bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector) { union dp_downstream_port_present ds_port_present; @@ -331,8 +355,9 @@ static bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnect return true; } +EXPORT_IF_KUNIT(retrieve_downstream_port_device); -static bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_dp_mst_port *port = aconnector->mst_output_port; @@ -359,6 +384,7 @@ static bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector return true; } +EXPORT_IF_KUNIT(retrieve_branch_specific_data); static int dm_dp_mst_get_modes(struct drm_connector *connector) { @@ -708,6 +734,44 @@ dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, return connector; } +/* + * Select the ESI[1] mask used to filter the MST sideband ready bits for a + * given message-ready event type. + */ +STATIC_IFN_KUNIT u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type) +{ + switch (msg_rdy_type) { + case DOWN_REP_MSG_RDY_EVENT: + /* Only handle DOWN_REP_MSG_RDY case*/ + return DP_DOWN_REP_MSG_RDY; + case UP_REQ_MSG_RDY_EVENT: + /* Only handle UP_REQ_MSG_RDY case*/ + return DP_UP_REQ_MSG_RDY; + default: + /* Handle both cases*/ + return DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY; + } +} +EXPORT_IF_KUNIT(dm_mst_msg_ready_mask); + +/* + * Select the DPCD ESI address and read length based on the DPCD revision. + */ +STATIC_IFN_KUNIT void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, + u8 *dpcd_bytes_to_read) +{ + if (dpcd_rev < 0x12) { + *dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT; + /* DPCD 0x200 - 0x201 for downstream IRQ */ + *dpcd_addr = DP_SINK_COUNT; + } else { + *dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI; + /* DPCD 0x2002 - 0x2005 for downstream IRQ */ + *dpcd_addr = DP_SINK_COUNT_ESI; + } +} +EXPORT_IF_KUNIT(dm_mst_select_esi_dpcd); + void dm_handle_mst_sideband_msg_ready_event( struct drm_dp_mst_topology_mgr *mgr, enum mst_msg_ready_type msg_rdy_type) @@ -726,15 +790,8 @@ void dm_handle_mst_sideband_msg_ready_event( const struct dc_link_status *link_status = dc_link_get_status(aconnector->dc_link); - if (link_status->dpcd_caps->dpcd_rev.raw < 0x12) { - dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT; - /* DPCD 0x200 - 0x201 for downstream IRQ */ - dpcd_addr = DP_SINK_COUNT; - } else { - dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI; - /* DPCD 0x2002 - 0x2005 for downstream IRQ */ - dpcd_addr = DP_SINK_COUNT_ESI; - } + dm_mst_select_esi_dpcd(link_status->dpcd_caps->dpcd_rev.raw, &dpcd_addr, + &dpcd_bytes_to_read); mutex_lock(&aconnector->handle_mst_msg_ready); @@ -756,20 +813,7 @@ void dm_handle_mst_sideband_msg_ready_event( DRM_DEBUG_DRIVER("ESI %02x %02x %02x\n", esi[0], esi[1], esi[2]); - switch (msg_rdy_type) { - case DOWN_REP_MSG_RDY_EVENT: - /* Only handle DOWN_REP_MSG_RDY case*/ - esi[1] &= DP_DOWN_REP_MSG_RDY; - break; - case UP_REQ_MSG_RDY_EVENT: - /* Only handle UP_REQ_MSG_RDY case*/ - esi[1] &= DP_UP_REQ_MSG_RDY; - break; - default: - /* Handle both cases*/ - esi[1] &= (DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY); - break; - } + esi[1] &= dm_mst_msg_ready_mask(msg_rdy_type); if (!esi[1]) break; @@ -866,6 +910,7 @@ uint32_t dm_mst_get_pbn_divider(struct dc_link *link) return dfixed_const(pbn_div_x100) / 100; } +EXPORT_IF_KUNIT(dm_mst_get_pbn_divider); struct dsc_mst_fairness_params { struct dc_crtc_timing *timing; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index 208629ca3721..2aefab5264d0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -60,6 +60,7 @@ enum mst_msg_ready_type { struct amdgpu_device; struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct aux_payload; struct dc_state; struct dc_stream_state; struct dm_atomic_state; @@ -100,4 +101,15 @@ enum dc_status dm_dp_mst_is_port_support_mode( struct amdgpu_dm_connector *aconnector, struct dc_stream_state *stream); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector); +bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector); +bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector); +ssize_t dm_dp_aux_transfer_result(ssize_t result, + enum aux_return_code_type operation_result); +void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload); +u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type); +void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read); +#endif + #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index e21386819ea1..e3b171992be1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -7,10 +7,44 @@ #include +#include +#include +#include + #include "dc.h" #include "dpcd_defs.h" +#include "dmub_cmd.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_mst_types.h" +/* + * Minimal mock DPCD backing store and AUX transfer callback used to exercise + * the DPCD read paths without real hardware. + */ +static u8 dm_mst_test_dpcd[0x10]; + +static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + size_t i; + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_NATIVE_READ: + for (i = 0; i < msg->size; i++) + ((u8 *)msg->buffer)[i] = + dm_mst_test_dpcd[(msg->address + i) & 0xf]; + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + case DP_AUX_NATIVE_WRITE: + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + default: + return -EINVAL; + } +} + /* Tests for needs_dsc_aux_workaround */ /** @@ -103,6 +137,332 @@ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *te KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); } +/** + * dm_mst_test_needs_dsc_aux_workaround_zero_sink_count - Test workaround skipped for zero sinks + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the sink + * count is zero, even if device ID and DPCD rev match. + */ +static void dm_mst_test_needs_dsc_aux_workaround_zero_sink_count(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 0; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +/* Tests for dm_mst_get_pbn_divider */ + +/** + * dm_mst_test_pbn_divider_null_link - Test pbn_divider with NULL link + * @test: KUnit test context + * + * Verify that dm_mst_get_pbn_divider() returns 0 when passed a NULL + * link pointer without crashing. + */ +static void dm_mst_test_pbn_divider_null_link(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(NULL), 0U); +} + +/* Tests for amdgpu_dm_mst_reset_mst_connector_setting */ + +/** + * dm_mst_test_reset_connector_setting - Test MST connector setting reset + * @test: KUnit test context + * + * Verify that amdgpu_dm_mst_reset_mst_connector_setting() clears the cached + * EDID, DSC AUX, passthrough AUX, local bandwidth, and VC PBN state. + */ +static void dm_mst_test_reset_connector_setting(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_port *port; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, port); + + aconnector->drm_edid = (const struct drm_edid *)test; + aconnector->dsc_aux = (struct drm_dp_aux *)test; + aconnector->mst_output_port = port; + aconnector->mst_output_port->passthrough_aux = (struct drm_dp_aux *)test; + aconnector->mst_local_bw = 12345; + aconnector->vc_full_pbn = 678; + + amdgpu_dm_mst_reset_mst_connector_setting(aconnector); + + KUNIT_EXPECT_TRUE(test, aconnector->drm_edid == NULL); + KUNIT_EXPECT_TRUE(test, aconnector->dsc_aux == NULL); + KUNIT_EXPECT_TRUE(test, aconnector->mst_output_port->passthrough_aux == NULL); + KUNIT_EXPECT_EQ(test, aconnector->mst_local_bw, 0U); + KUNIT_EXPECT_EQ(test, aconnector->vc_full_pbn, 0U); +} + +/* Tests for retrieve_downstream_port_device */ + +/** + * dm_mst_test_retrieve_downstream_no_aux - Test retrieval bails out without AUX + * @test: KUnit test context + * + * Verify that retrieve_downstream_port_device() returns false when the + * connector has no DSC AUX channel and therefore cannot read DPCD. + */ +static void dm_mst_test_retrieve_downstream_no_aux(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->dsc_aux = NULL; + + KUNIT_EXPECT_FALSE(test, retrieve_downstream_port_device(aconnector)); +} + +/** + * dm_mst_test_retrieve_downstream_present - Test retrieval parses DPCD 0x05 + * @test: KUnit test context + * + * Verify that retrieve_downstream_port_device() reads DP_DOWNSTREAMPORT_PRESENT + * over a mock AUX channel and caches the parsed downstream port fields. + */ +static void dm_mst_test_retrieve_downstream_present(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, aux); + + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + /* PORT_PRESENT = 1, PORT_TYPE = 2 (0b101) */ + dm_mst_test_dpcd[DP_DOWNSTREAMPORT_PRESENT] = 0x05; + + aux->name = "dm_mst_test_aux"; + aux->transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(aux); + drm_dp_dpcd_set_probe(aux, false); + aconnector->dsc_aux = aux; + + KUNIT_EXPECT_TRUE(test, retrieve_downstream_port_device(aconnector)); + KUNIT_EXPECT_EQ(test, + (int)aconnector->mst_downstream_port_present.fields.PORT_PRESENT, 1); + KUNIT_EXPECT_EQ(test, + (int)aconnector->mst_downstream_port_present.fields.PORT_TYPE, 2); +} + +/* Tests for retrieve_branch_specific_data */ + +/** + * dm_mst_test_retrieve_branch_no_parent - Test branch lookup needs a parent port + * @test: KUnit test context + * + * Verify that retrieve_branch_specific_data() returns false when the MST + * output port has no parent branch device to query. + */ +static void dm_mst_test_retrieve_branch_no_parent(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_port *port; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, port); + + port->parent = NULL; + aconnector->mst_output_port = port; + + KUNIT_EXPECT_FALSE(test, retrieve_branch_specific_data(aconnector)); +} + +/** + * dm_mst_test_aux_result_success - AUX_RET_SUCCESS preserves the input result. + * @test: KUnit test context. + * + * On success the original (negative) transfer result must be returned unchanged. + */ +static void dm_mst_test_aux_result_success(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-5, AUX_RET_SUCCESS), (ssize_t)-5); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(3, AUX_RET_SUCCESS), (ssize_t)3); +} + +/** + * dm_mst_test_aux_result_eio - HPD/unknown/protocol errors map to -EIO. + * @test: KUnit test context. + * + * AUX_RET_ERROR_HPD_DISCON, AUX_RET_ERROR_UNKNOWN, + * AUX_RET_ERROR_INVALID_OPERATION and AUX_RET_ERROR_PROTOCOL_ERROR all map to -EIO. + */ +static void dm_mst_test_aux_result_eio(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_HPD_DISCON), + (ssize_t)-EIO); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_UNKNOWN), + (ssize_t)-EIO); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_INVALID_OPERATION), + (ssize_t)-EIO); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_PROTOCOL_ERROR), + (ssize_t)-EIO); +} + +/** + * dm_mst_test_aux_result_ebusy - invalid reply / engine acquire map to -EBUSY. + * @test: KUnit test context. + * + * AUX_RET_ERROR_INVALID_REPLY and AUX_RET_ERROR_ENGINE_ACQUIRE map to -EBUSY. + */ +static void dm_mst_test_aux_result_ebusy(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_INVALID_REPLY), + (ssize_t)-EBUSY); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_ENGINE_ACQUIRE), + (ssize_t)-EBUSY); +} + +/** + * dm_mst_test_aux_result_timeout - AUX_RET_ERROR_TIMEOUT maps to -ETIMEDOUT. + * @test: KUnit test context. + */ +static void dm_mst_test_aux_result_timeout(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_TIMEOUT), + (ssize_t)-ETIMEDOUT); +} + +/** + * dm_mst_test_fill_payload_flags_native_write - native write request decode. + * @test: KUnit test context. + * + * DP_AUX_NATIVE_WRITE clears i2c_over_aux and sets write; no I2C bits set. + */ +static void dm_mst_test_fill_payload_flags_native_write(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_NATIVE_WRITE, &payload); + + KUNIT_EXPECT_FALSE(test, payload.i2c_over_aux); + KUNIT_EXPECT_TRUE(test, payload.write); + KUNIT_EXPECT_FALSE(test, payload.mot); + KUNIT_EXPECT_FALSE(test, payload.write_status_update); +} + +/** + * dm_mst_test_fill_payload_flags_native_read - native read request decode. + * @test: KUnit test context. + * + * DP_AUX_NATIVE_READ keeps i2c_over_aux clear; the I2C_READ bit clears write. + */ +static void dm_mst_test_fill_payload_flags_native_read(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_NATIVE_READ, &payload); + + KUNIT_EXPECT_FALSE(test, payload.i2c_over_aux); + KUNIT_EXPECT_FALSE(test, payload.write); + KUNIT_EXPECT_FALSE(test, payload.mot); +} + +/** + * dm_mst_test_fill_payload_flags_i2c_read_mot - I2C read with MOT request decode. + * @test: KUnit test context. + * + * DP_AUX_I2C_READ sets i2c_over_aux and clears write; DP_AUX_I2C_MOT sets mot. + */ +static void dm_mst_test_fill_payload_flags_i2c_read_mot(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_I2C_READ | DP_AUX_I2C_MOT, &payload); + + KUNIT_EXPECT_TRUE(test, payload.i2c_over_aux); + KUNIT_EXPECT_FALSE(test, payload.write); + KUNIT_EXPECT_TRUE(test, payload.mot); +} + +/** + * dm_mst_test_fill_payload_flags_write_status - write status update decode. + * @test: KUnit test context. + * + * DP_AUX_I2C_WRITE_STATUS_UPDATE sets write_status_update. + */ +static void dm_mst_test_fill_payload_flags_write_status(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_I2C_WRITE | DP_AUX_I2C_WRITE_STATUS_UPDATE, + &payload); + + KUNIT_EXPECT_TRUE(test, payload.i2c_over_aux); + KUNIT_EXPECT_TRUE(test, payload.write_status_update); +} + +/** + * dm_mst_test_msg_ready_mask - ESI mask selection per message-ready type. + * @test: KUnit test context. + * + * DOWN_REP and UP_REQ each select their single bit; other types select both. + */ +static void dm_mst_test_msg_ready_mask(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(DOWN_REP_MSG_RDY_EVENT), + (u8)DP_DOWN_REP_MSG_RDY); + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(UP_REQ_MSG_RDY_EVENT), + (u8)DP_UP_REQ_MSG_RDY); + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(DOWN_OR_UP_MSG_RDY_EVENT), + (u8)(DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY)); + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(NONE_MSG_RDY_EVENT), + (u8)(DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY)); +} + +/** + * dm_mst_test_select_esi_dpcd_legacy - pre-1.2 DPCD ESI address/length. + * @test: KUnit test context. + * + * For DPCD rev < 0x12 the legacy DP_SINK_COUNT address/length pair is selected. + */ +static void dm_mst_test_select_esi_dpcd_legacy(struct kunit *test) +{ + int dpcd_addr = -1; + u8 dpcd_bytes_to_read = 0; + + dm_mst_select_esi_dpcd(0x11, &dpcd_addr, &dpcd_bytes_to_read); + + KUNIT_EXPECT_EQ(test, dpcd_addr, DP_SINK_COUNT); + KUNIT_EXPECT_EQ(test, (int)dpcd_bytes_to_read, + (int)(DP_LANE0_1_STATUS - DP_SINK_COUNT)); +} + +/** + * dm_mst_test_select_esi_dpcd_esi - 1.2+ DPCD ESI address/length. + * @test: KUnit test context. + * + * For DPCD rev >= 0x12 the ESI DP_SINK_COUNT_ESI address/length pair is selected. + */ +static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test) +{ + int dpcd_addr = -1; + u8 dpcd_bytes_to_read = 0; + + dm_mst_select_esi_dpcd(0x14, &dpcd_addr, &dpcd_bytes_to_read); + + KUNIT_EXPECT_EQ(test, dpcd_addr, DP_SINK_COUNT_ESI); + KUNIT_EXPECT_EQ(test, (int)dpcd_bytes_to_read, + (int)(DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI)); +} + static struct kunit_case dm_mst_types_test_cases[] = { /* needs_dsc_aux_workaround tests */ KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match), @@ -110,6 +470,31 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id), KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_rev), KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_low_sink_count), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_zero_sink_count), + /* dm_mst_get_pbn_divider tests */ + KUNIT_CASE(dm_mst_test_pbn_divider_null_link), + /* amdgpu_dm_mst_reset_mst_connector_setting tests */ + KUNIT_CASE(dm_mst_test_reset_connector_setting), + /* retrieve_downstream_port_device tests */ + KUNIT_CASE(dm_mst_test_retrieve_downstream_no_aux), + KUNIT_CASE(dm_mst_test_retrieve_downstream_present), + /* retrieve_branch_specific_data tests */ + KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent), + /* dm_dp_aux_transfer_result tests */ + KUNIT_CASE(dm_mst_test_aux_result_success), + KUNIT_CASE(dm_mst_test_aux_result_eio), + KUNIT_CASE(dm_mst_test_aux_result_ebusy), + KUNIT_CASE(dm_mst_test_aux_result_timeout), + /* dm_dp_aux_fill_payload_flags tests */ + KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write), + KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read), + KUNIT_CASE(dm_mst_test_fill_payload_flags_i2c_read_mot), + KUNIT_CASE(dm_mst_test_fill_payload_flags_write_status), + /* dm_mst_msg_ready_mask tests */ + KUNIT_CASE(dm_mst_test_msg_ready_mask), + /* dm_mst_select_esi_dpcd tests */ + KUNIT_CASE(dm_mst_test_select_esi_dpcd_legacy), + KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi), {} }; -- cgit v1.2.3 From e3d0810f50add1e63883a993bac9e01603a81b53 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 4 Jun 2026 09:38:12 -0500 Subject: drm/amd/display: Set default backlight without ACPI support [Why] If BIOS doesn't include ATIF method it will not specify default AC or DC levels. This means that backlight will always start at 0%, which isn't expected behavior. [How] Set default AC and DC level when no valid caps found. Also reduce code duplication for ACPI and non-ACPI cases. Reported-by: Edson Juliano Drosdeck Closes: https://lore.kernel.org/dri-devel/20260526210048.1162477-1-edson.drosdeck@gmail.com/ Reviewed-by: Alex Hung Signed-off-by: Mario Limonciello Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 f101aed75bb3..0a861d846677 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 @@ -78,20 +78,16 @@ void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, caps->caps_valid = false; } } - +#else + if (caps->aux_support) + return; +#endif if (!caps->caps_valid) { caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->ac_level = caps->dc_level = 50; caps->caps_valid = true; } -#else - if (caps->aux_support) - return; - - caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; - caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - caps->caps_valid = true; -#endif } EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps); -- cgit v1.2.3 From 75ac474209514d2aa1b9bf43d391bcafa50384d0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 5 Jun 2026 10:37:38 -0600 Subject: drm/amd/display: Move backlight macros to backlight header [WHAT] Move AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT, AMDGPU_DM_MIN_SPREAD, and AUX_BL_DEFAULT_TRANSITION_TIME_MS from amdgpu_dm_backlight.c to amdgpu_dm_backlight.h so they can be reused by KUnit tests. Update the test file to use these macros instead of hardcoded literal values. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 5 -- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 5 ++ .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 62 +++++++++++----------- 3 files changed, 36 insertions(+), 36 deletions(-) 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 0a861d846677..f19092a3237e 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 @@ -49,11 +49,6 @@ #include "amd_shared.h" #include "amdgpu_dm_kunit_helpers.h" -#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 -#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 -#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) -#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 - 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_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 5234da6ae484..a6c01b7ccab3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -29,6 +29,11 @@ struct amdgpu_dm_connector; struct drm_connector; struct attribute_group; +#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 +#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 +#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) +#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 + void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx); void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, 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 2f4293cfd478..8763cd635ae1 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 @@ -110,8 +110,8 @@ static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test) amdgpu_dm_update_backlight_caps(dm, 0); KUNIT_EXPECT_TRUE(test, caps->caps_valid); - KUNIT_EXPECT_EQ(test, caps->min_input_signal, 12); - KUNIT_EXPECT_EQ(test, caps->max_input_signal, 255); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, AMDGPU_DM_DEFAULT_MIN_BACKLIGHT); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT); } #endif @@ -141,13 +141,13 @@ static void dm_test_brightness_range_pwm(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); - /* 0x101 * 12 = 3084, 0x101 * 255 = 65535 */ - KUNIT_EXPECT_EQ(test, min, 0x101U * 12); - KUNIT_EXPECT_EQ(test, max, 0x101U * 255); + /* 0x101 * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT */ + KUNIT_EXPECT_EQ(test, min, 0x101U * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT); + KUNIT_EXPECT_EQ(test, max, 0x101U * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT); } /** @@ -195,10 +195,10 @@ static void dm_test_brightness_to_user_below_min(struct kunit *test) struct amdgpu_dm_backlight_caps caps = {}; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - /* brightness < min (0x101*12 = 3084), should return 0 */ + /* brightness < min (0x101*AMDGPU_DM_DEFAULT_MIN_BACKLIGHT), should return 0 */ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U); } @@ -212,8 +212,8 @@ static void dm_test_brightness_to_user_at_max(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -231,8 +231,8 @@ static void dm_test_brightness_to_user_at_min(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -251,8 +251,8 @@ static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test) u64 expected; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -286,8 +286,8 @@ static void dm_test_brightness_from_user_zero(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; /* no custom curve */ caps.data_points = 0; @@ -307,8 +307,8 @@ static void dm_test_brightness_from_user_max(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 0; get_brightness_range(&caps, &min, &max); @@ -403,7 +403,7 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 3; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; @@ -453,7 +453,7 @@ static void dm_test_custom_brightness_below_first(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 100; caps.luminance_data[0].luminance = 40; @@ -498,7 +498,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; @@ -539,7 +539,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; @@ -580,7 +580,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 1; caps.luminance_data[0].input_signal = 128; caps.luminance_data[0].luminance = 50; @@ -616,7 +616,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 0; /* zero lower luminance */ @@ -650,8 +650,8 @@ static void dm_test_brightness_to_user_above_max(struct kunit *test) unsigned int min, max, result; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -672,8 +672,8 @@ static void dm_test_brightness_from_user_midrange(struct kunit *test) u32 result; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 0; get_brightness_range(&caps, &min, &max); @@ -700,7 +700,7 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; -- cgit v1.2.3 From 79d21b50956d89a86565014574e6798f2a5a6cc9 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Thu, 11 Jun 2026 23:25:11 +0800 Subject: Revert "drm/amd/display: Use handle_hpd_irq_helper for HPD RX" This reverts commit 60597d2cb21990face4ac60bb0f9a642c00ff6d2. Reason for revert: This change is found to cause hang on DP2 link layer compliance 4.2.2.8. Signed-off-by: Chenyu Chen Reviewed-by: Jerry Zuo Tested-by: Mark Broadworth Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 41 +++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) 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 0759c1d92b61..57dd176e4cc1 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 @@ -1425,12 +1425,14 @@ static void handle_hpd_rx_irq(void *param) struct dc_link *dc_link = aconnector->dc_link; bool is_mst_root_connector = aconnector->mst_mgr.mst_state; bool result = false; + enum dc_connection_type new_connection_type = dc_connection_none; struct amdgpu_device *adev = drm_to_adev(dev); union hpd_irq_data hpd_irq_data; bool link_loss = false; bool has_left_work = false; int idx = dc_link->link_index; struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; + struct dc *dc = aconnector->dc_link->ctx->dc; memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); @@ -1499,7 +1501,44 @@ static void handle_hpd_rx_irq(void *param) out: if (result && !is_mst_root_connector) { /* Downstream Port status changed. */ - handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); + if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) + drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); + + if (aconnector->base.force && new_connection_type == dc_connection_none) { + amdgpu_dm_emulated_link_detect(dc_link); + + if (aconnector->fake_enable) + aconnector->fake_enable = false; + + amdgpu_dm_update_connector_after_detect(aconnector); + + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + drm_kms_helper_connector_hotplug_event(connector); + } else { + bool ret = false; + + mutex_lock(&adev->dm.dc_lock); + dc_exit_ips_for_hw_access(dc); + ret = dc_link_detect(dc_link, DETECT_REASON_HPDRX); + mutex_unlock(&adev->dm.dc_lock); + + if (ret) { + if (aconnector->fake_enable) + aconnector->fake_enable = false; + + amdgpu_dm_update_connector_after_detect(aconnector); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + drm_kms_helper_connector_hotplug_event(connector); + } + } } if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { if (adev->dm.hdcp_workqueue) -- cgit v1.2.3 From 9a591ae691b50b73b106ef78b0c2c56f90fec439 Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Fri, 5 Jun 2026 17:09:34 -0400 Subject: drm/amd/display: [FW Promotion] Release 0.1.63.0 [Why & How] Add some CACP command and remove some unused struct and enum. Signed-off-by: Taimur Hassan Signed-off-by: Chenyu Chen Acked-by: Tom Chung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 178 +++++------------------- 1 file changed, 35 insertions(+), 143 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h index 6f6a59a23495..57f30be6bc9c 100644 --- a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h +++ b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h @@ -1800,28 +1800,13 @@ enum dmub_inbox0_command { * * Command IDs should be treated as stable ABI. * Do not reuse or modify IDs. + * Note that command IDs 1-4 have been deprecated. */ enum dmub_cmd_type { /** * Invalid command. */ DMUB_CMD__NULL = 0, - /** - * Read modify write register sequence offload. - */ - DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE = 1, - /** - * Field update register sequence offload. - */ - DMUB_CMD__REG_SEQ_FIELD_UPDATE_SEQ = 2, - /** - * Burst write sequence offload. - */ - DMUB_CMD__REG_SEQ_BURST_WRITE = 3, - /** - * Reg wait sequence offload. - */ - DMUB_CMD__REG_REG_WAIT = 4, /** * Workaround to avoid HUBP underflow during NV12 playback. */ @@ -2041,98 +2026,6 @@ struct dmub_cmd_header { unsigned int reserved1 : 2; /**< reserved bits */ }; -/* - * struct dmub_cmd_read_modify_write_sequence - Read modify write - * - * 60 payload bytes can hold up to 5 sets of read modify writes, - * each take 3 dwords. - * - * number of sequences = header.payload_bytes / sizeof(struct dmub_cmd_read_modify_write_sequence) - * - * modify_mask = 0xffff'ffff means all fields are going to be updated. in this case - * command parser will skip the read and we can use modify_mask = 0xffff'ffff as reg write - */ -struct dmub_cmd_read_modify_write_sequence { - uint32_t addr; /**< register address */ - uint32_t modify_mask; /**< modify mask */ - uint32_t modify_value; /**< modify value */ -}; - -/** - * Maximum number of ops in read modify write sequence. - */ -#define DMUB_READ_MODIFY_WRITE_SEQ__MAX 5 - -/** - * struct dmub_cmd_read_modify_write_sequence - Read modify write command. - */ -struct dmub_rb_cmd_read_modify_write { - struct dmub_cmd_header header; /**< command header */ - /** - * Read modify write sequence. - */ - struct dmub_cmd_read_modify_write_sequence seq[DMUB_READ_MODIFY_WRITE_SEQ__MAX]; -}; - -/* - * Update a register with specified masks and values sequeunce - * - * 60 payload bytes can hold address + up to 7 sets of mask/value combo, each take 2 dword - * - * number of field update sequence = (header.payload_bytes - sizeof(addr)) / sizeof(struct read_modify_write_sequence) - * - * - * USE CASE: - * 1. auto-increment register where additional read would update pointer and produce wrong result - * 2. toggle a bit without read in the middle - */ - -struct dmub_cmd_reg_field_update_sequence { - uint32_t modify_mask; /**< 0xffff'ffff to skip initial read */ - uint32_t modify_value; /**< value to update with */ -}; - -/** - * Maximum number of ops in field update sequence. - */ -#define DMUB_REG_FIELD_UPDATE_SEQ__MAX 7 - -/** - * struct dmub_rb_cmd_reg_field_update_sequence - Field update command. - */ -struct dmub_rb_cmd_reg_field_update_sequence { - struct dmub_cmd_header header; /**< command header */ - uint32_t addr; /**< register address */ - /** - * Field update sequence. - */ - struct dmub_cmd_reg_field_update_sequence seq[DMUB_REG_FIELD_UPDATE_SEQ__MAX]; -}; - - -/** - * Maximum number of burst write values. - */ -#define DMUB_BURST_WRITE_VALUES__MAX 14 - -/* - * struct dmub_rb_cmd_burst_write - Burst write - * - * support use case such as writing out LUTs. - * - * 60 payload bytes can hold up to 14 values to write to given address - * - * number of payload = header.payload_bytes / sizeof(struct read_modify_write_sequence) - */ -struct dmub_rb_cmd_burst_write { - struct dmub_cmd_header header; /**< command header */ - uint32_t addr; /**< register start address */ - /** - * Burst write register values. - */ - uint32_t write_values[DMUB_BURST_WRITE_VALUES__MAX]; -}; - /** * struct dmub_rb_cmd_common - Common command header */ @@ -2144,24 +2037,6 @@ struct dmub_rb_cmd_common { uint8_t cmd_buffer[DMUB_RB_CMD_SIZE - sizeof(struct dmub_cmd_header)]; }; -/** - * struct dmub_cmd_reg_wait_data - Register wait data - */ -struct dmub_cmd_reg_wait_data { - uint32_t addr; /**< Register address */ - uint32_t mask; /**< Mask for register bits */ - uint32_t condition_field_value; /**< Value to wait for */ - uint32_t time_out_us; /**< Time out for reg wait in microseconds */ -}; - -/** - * struct dmub_rb_cmd_reg_wait - Register wait command - */ -struct dmub_rb_cmd_reg_wait { - struct dmub_cmd_header header; /**< Command header */ - struct dmub_cmd_reg_wait_data reg_wait; /**< Register wait data */ -}; - /** * struct dmub_cmd_PLAT_54186_wa - Underflow workaround * @@ -6439,10 +6314,43 @@ struct dmub_cmd_cacp_set_backlight_data { */ uint8_t panel_mask; + /** + * AUX HW Instance. + */ + uint8_t aux_inst; + /** * Explicit padding to 4 byte boundary. */ - uint8_t pad[2]; + uint8_t pad[1]; + + /** + * Backlight control type. + * Value 0 is PWM backlight control. + * Value 1 is VAUX backlight control. + * Value 2 is AMD DPCD AUX backlight control. + */ + enum dmub_backlight_control_type backlight_control_type; + + /** + * Minimum luminance in nits. + */ + uint32_t min_luminance; + + /** + * Maximum luminance in nits. + */ + uint32_t max_luminance; + + /** + * Minimum backlight in pwm. + */ + uint32_t min_backlight_pwm; + + /** + * Maximum backlight in pwm. + */ + uint32_t max_backlight_pwm; }; /** @@ -7362,22 +7270,6 @@ union dmub_rb_cmd { * Elements shared with all commands. */ struct dmub_rb_cmd_common cmd_common; - /** - * Definition of a DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE command. - */ - struct dmub_rb_cmd_read_modify_write read_modify_write; - /** - * Definition of a DMUB_CMD__REG_SEQ_FIELD_UPDATE_SEQ command. - */ - struct dmub_rb_cmd_reg_field_update_sequence reg_field_update_seq; - /** - * Definition of a DMUB_CMD__REG_SEQ_BURST_WRITE command. - */ - struct dmub_rb_cmd_burst_write burst_write; - /** - * Definition of a DMUB_CMD__REG_REG_WAIT command. - */ - struct dmub_rb_cmd_reg_wait reg_wait; /** * Definition of a DMUB_CMD__VBIOS_DIGX_ENCODER_CONTROL command. */ -- cgit v1.2.3 From 00b91f3f66adec5080f47dbf09271b13b283df1e Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Fri, 5 Jun 2026 19:05:22 -0500 Subject: drm/amd/display: Promote DC to 3.2.386 This version brings along the following updates: - Increase dcn42b uclk value. - Add a new interface to set idle opts in clock manager. - Revert dmub_cmd updates for HDMI. - Add utm_qos_model pointer to clk_bw_params. - Remove get_utm_qos_model from soc_and_ip_translator. - Rename hdmi_frl_borrow_mode. - Remove unused project_id from DML2 core instance. - Drop HDMI2_1 guards. - Introduce dc_plane_cm and migrate surface update color path. - Extract backlight code to amdgpu_dm_backlight. - Extract audio code to amdgpu_dm_audio. - Extract DMUB code to amdgpu_dm_dmub. - Move HPD and IRQ handler code to amdgpu_dm_irq. - Extract connector and encoder code to amdgpu_dm_connector. - Fix conflicting types for dc_plane_cm functions. - Add PSR Active VTotal Control capability. - Enable pstate for DCN4 non-emulation builds. - Refactor surface_update_flags to flat struct with helpers. - Add support for HDMI Compliance Automation. - Add KUnit tests for amdgpu_dm and its components. - Set default backlight without ACPI support. - Move backlight macros to backlight header. - Revert use of handle_hpd_irq_helper for HPD RX. - FW Promotion Release 0.1.63.0. Signed-off-by: Taimur Hassan Signed-off-by: Chenyu Chen Acked-by: Tom Chung Co-authored-by: Cursor Tested-by: Daniel 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 2202c8669bf8..2de0f9cf8264 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -65,7 +65,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.385" +#define DC_VER "3.2.386" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC -- cgit v1.2.3 From 8e2d7bbd6b184c0c1b0fe7cb404c9b5214d89931 Mon Sep 17 00:00:00 2001 From: James Lin Date: Fri, 12 Jun 2026 10:05:29 -0400 Subject: drm/amd/display: Add IN_FORMATS_ASYNC support for planes [Why] The DRM core exposes an IN_FORMATS_ASYNC plane property describing the set of format/modifier pairs that are valid for asynchronous (immediate) page flips. amdgpu already advertises async page flip support via mode_config.async_page_flip = true, but never implemented the .format_mod_supported_async plane callback, so the IN_FORMATS_ASYNC property was not created. This inconsistency (advertising async flips while exposing IN_FORMATS but no IN_FORMATS_ASYNC) causes userspace, such as igt-gpu-tools, to emit a repeated warning during plane initialization, which in turn demotes many otherwise passing KMS subtests to a WARN result. [How] Wire up .format_mod_supported_async to the existing amdgpu_dm_plane_format_mod_supported callback so the async format list is populated. amdgpu does not restrict async flips at the format/modifier level: the async flip constraints are enforced at atomic check and commit time and only require a fast update (no change to FB pitch, DCC state, rotation or memory type) between the old and new buffers. Therefore the set of formats/modifiers valid for async flips is identical to the regular IN_FORMATS set, and the same callback can be reused. Reviewed-by: Aurabindo Pillai Signed-off-by: James Lin Signed-off-by: Ivan Lipski Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 1 + 1 file changed, 1 insertion(+) 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 e957657b06c7..c7f8e08feaf4 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 @@ -1859,6 +1859,7 @@ static const struct drm_plane_funcs dm_plane_funcs = { .atomic_duplicate_state = amdgpu_dm_plane_drm_plane_duplicate_state, .atomic_destroy_state = amdgpu_dm_plane_drm_plane_destroy_state, .format_mod_supported = amdgpu_dm_plane_format_mod_supported, + .format_mod_supported_async = amdgpu_dm_plane_format_mod_supported, #ifdef AMD_PRIVATE_COLOR .atomic_set_property = dm_atomic_plane_set_property, .atomic_get_property = dm_atomic_plane_get_property, -- cgit v1.2.3 From 4d7c624a0af1fd54a270172bb9c59c0bc8269ab6 Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Thu, 11 Jun 2026 15:41:37 -0400 Subject: drm/amdkfd: remove obsolete events page mmap support The mmap of the events (signal) page from /dev/kfd via KFD_MMAP_TYPE_EVENTS was only needed on APUs using IOMMUv2, which is no longer supported by the kernel mode driver. For dGPUs (and modern APUs) the events page is allocated in user mode and mapped to the kernel through the event_page_offset of the create event IOCTL (kfd_kmap_event_page), so the KFD_MMAP_TYPE_EVENTS mmap path is no longer functional. Remove kfd_event_mmap() and reject KFD_MMAP_TYPE_EVENTS in kfd_mmap, similar to the recent removal of KFD_MMAP_TYPE_RESERVED_MEM. This also removes a way for user space to abuse KFD_MMAP_TYPE_EVENTS of kfd_mmap. Signed-off-by: Yongqiang Sun Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 3 ++- drivers/gpu/drm/amd/amdkfd/kfd_events.c | 45 -------------------------------- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 1 - 3 files changed, 2 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index a2b100d14425..734a5a2a251f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -3762,7 +3762,8 @@ static int kfd_mmap(struct file *filep, struct vm_area_struct *vma) return kfd_doorbell_mmap(dev, process, vma); case KFD_MMAP_TYPE_EVENTS: - return kfd_event_mmap(process, vma); + pr_warn("KFD_MMAP_TYPE_EVENTS is no longer supported\n"); + return -EINVAL; case KFD_MMAP_TYPE_RESERVED_MEM: pr_warn("KFD_MMAP_TYPE_RESERVED_MEM is no longer supported\n"); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index 71e8f9a23215..6088870b8c30 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -1070,51 +1070,6 @@ out: return ret; } -int kfd_event_mmap(struct kfd_process *p, struct vm_area_struct *vma) -{ - unsigned long pfn; - struct kfd_signal_page *page; - int ret; - - /* check required size doesn't exceed the allocated size */ - if (get_order(KFD_SIGNAL_EVENT_LIMIT * 8) < - get_order(vma->vm_end - vma->vm_start)) { - pr_err("Event page mmap requested illegal size\n"); - return -EINVAL; - } - - page = p->signal_page; - if (!page) { - /* Probably KFD bug, but mmap is user-accessible. */ - pr_debug("Signal page could not be found\n"); - return -EINVAL; - } - - pfn = __pa(page->kernel_address); - pfn >>= PAGE_SHIFT; - - vm_flags_set(vma, VM_IO | VM_DONTCOPY | VM_DONTEXPAND | VM_NORESERVE - | VM_DONTDUMP | VM_PFNMAP); - - pr_debug("Mapping signal page\n"); - pr_debug(" start user address == 0x%08lx\n", vma->vm_start); - pr_debug(" end user address == 0x%08lx\n", vma->vm_end); - pr_debug(" pfn == 0x%016lX\n", pfn); - pr_debug(" vm_flags == 0x%08lX\n", vma->vm_flags); - pr_debug(" size == 0x%08lX\n", - vma->vm_end - vma->vm_start); - - page->user_address = (uint64_t __user *)vma->vm_start; - - /* mapping the page to user process */ - ret = remap_pfn_range(vma, vma->vm_start, pfn, - vma->vm_end - vma->vm_start, vma->vm_page_prot); - if (!ret) - p->signal_mapped_size = vma->vm_end - vma->vm_start; - - return ret; -} - /* * Assumes that p is not going away. */ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index 73bf7120d622..babc1116baec 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -1540,7 +1540,6 @@ extern const struct kfd_device_global_init_class device_global_init_class_cik; int kfd_event_init_process(struct kfd_process *p); void kfd_event_free_process(struct kfd_process *p); -int kfd_event_mmap(struct kfd_process *process, struct vm_area_struct *vma); int kfd_wait_on_events(struct kfd_process *p, uint32_t num_events, void __user *data, bool all, uint32_t *user_timeout_ms, -- cgit v1.2.3 From 13158e5dbd896281f3e9982b5437cffa5fd621b2 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 11 Jun 2026 08:44:38 -0700 Subject: drm/amd/display: Fix mem_type change detection for async flips [Why] amdgpu_dm_crtc_mem_type_changed() fetches the "old" and "new" plane state with two drm_atomic_get_plane_state() calls, which both return the new state. It compares a state against itself, so it never detects a mem_type change and never rejects the async flip. On DCN 3.0.1, this shows up as intermittent corruption when a single DCC plane is scanned out with immediate flips under gamescope and its buffer moves between the VRAM carveout and GTT. [How] Use drm_atomic_get_old_plane_state() and drm_atomic_get_new_plane_state() to compare the actual old and new states. These return NULL rather than an error pointer for a plane that is not part of the commit, so the IS_ERR() check becomes a NULL check that skips those planes, such as an unmodified cursor still in the CRTC's plane_mask. Fixes: 4caacd1671b7 ("drm/amd/display: Do not elevate mem_type change to full update") Reviewed-by: Harry Wentland Reviewed-by: Melissa Wen Signed-off-by: Matthew Schwartz Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 ++++------ 1 file changed, 4 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 d23d9d85e567..2e74ff94dcac 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6684,13 +6684,11 @@ static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev, struct drm_plane_state *new_plane_state, *old_plane_state; drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { - new_plane_state = drm_atomic_get_plane_state(state, plane); - old_plane_state = drm_atomic_get_plane_state(state, plane); + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + old_plane_state = drm_atomic_get_old_plane_state(state, plane); - if (IS_ERR(new_plane_state) || IS_ERR(old_plane_state)) { - drm_err(dev, "Failed to get plane state for plane %s\n", plane->name); - return false; - } + if (!old_plane_state || !new_plane_state) + continue; if (old_plane_state->fb && new_plane_state->fb && get_mem_type(old_plane_state->fb) != get_mem_type(new_plane_state->fb)) -- cgit v1.2.3 From 0cfc1e9fafcd0b974ed4b35a6090d2ee772881df Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Fri, 12 Jun 2026 15:02:12 +0800 Subject: drm/amd/ras: Sync bad page count on EEPROM update The rascore EEPROM runtime append path updates the saved bad page count in memory and EEPROM. Keep the SMU bad page count in sync when the EEPROM header is updated so firmware sees the latest count from the runtime threshold path. Notify UPDATE_BAD_PAGE_NUM after computing the rascore UMC bad page count. Signed-off-by: Xiang Liu Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c index 3a0ea036c9be..62d1a319c08c 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c @@ -746,6 +746,9 @@ static int ras_eeprom_update_header(struct ras_eeprom_control *control) int res; bad_page_count = ras_umc_get_badpage_count(ras_core); + ras_core_event_notify(ras_core, RAS_EVENT_ID__UPDATE_BAD_PAGE_NUM, + &bad_page_count); + /* Modify the header if it exceeds. */ if (threshold_config != 0 && -- cgit v1.2.3 From b4f5837f746adc9f407cf887fca610d4638fad24 Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Thu, 11 Jun 2026 15:42:34 -0400 Subject: drm/amdkfd: remove dead kernel-allocated signal page code With the KFD_MMAP_TYPE_EVENTS mmap path gone, a kernel-allocated signal page can no longer be exposed to user space, so allocate_signal_page() and the related bookkeeping are dead code. The only remaining way to set up a signal page is kfd_kmap_event_page()/kfd_event_page_set(), where user space allocates the events page as a BO and passes it via the event_page_offset of the create event IOCTL. Remove allocate_signal_page() and require the signal page to be provided by user space. Drop the now unused kfd_signal_page user mapping bookkeeping (user_address/need_to_free_pages) and kfd_event::user_signal_address. Signed-off-by: Yongqiang Sun Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_events.c | 59 ++++++--------------------------- drivers/gpu/drm/amd/amdkfd/kfd_events.h | 3 -- 2 files changed, 10 insertions(+), 52 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index 6088870b8c30..cf10e0902f18 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -55,8 +55,6 @@ struct kfd_event_waiter { */ struct kfd_signal_page { uint64_t *kernel_address; - uint64_t __user *user_address; - bool need_to_free_pages; }; static uint64_t *page_slots(struct kfd_signal_page *page) @@ -64,49 +62,19 @@ static uint64_t *page_slots(struct kfd_signal_page *page) return page->kernel_address; } -static struct kfd_signal_page *allocate_signal_page(struct kfd_process *p) -{ - void *backing_store; - struct kfd_signal_page *page; - - page = kzalloc_obj(*page); - if (!page) - return NULL; - - backing_store = (void *) __get_free_pages(GFP_KERNEL, - get_order(KFD_SIGNAL_EVENT_LIMIT * 8)); - if (!backing_store) - goto fail_alloc_signal_store; - - /* Initialize all events to unsignaled */ - memset(backing_store, (uint8_t) UNSIGNALED_EVENT_SLOT, - KFD_SIGNAL_EVENT_LIMIT * 8); - - page->kernel_address = backing_store; - page->need_to_free_pages = true; - pr_debug("Allocated new event signal page at %p, for process %p\n", - page, p); - - return page; - -fail_alloc_signal_store: - kfree(page); - return NULL; -} - static int allocate_event_notification_slot(struct kfd_process *p, struct kfd_event *ev, const int *restore_id) { int id; - if (!p->signal_page) { - p->signal_page = allocate_signal_page(p); - if (!p->signal_page) - return -ENOMEM; - /* Oldest user mode expects 256 event slots */ - p->signal_mapped_size = 256*8; - } + /* + * The signal page is allocated in user mode and mapped to the kernel + * via the event_page_offset of the create event IOCTL. Without it no + * signal events can be created. + */ + if (!p->signal_page) + return -ENOMEM; if (restore_id) { id = idr_alloc(&p->event_idr, ev, *restore_id, *restore_id + 1, @@ -212,10 +180,8 @@ static int create_signal_event(struct file *devkfd, struct kfd_process *p, p->signal_event_count++; - ev->user_signal_address = &p->signal_page->user_address[ev->event_id]; - pr_debug("Signal event number %zu created with id %d, address %p\n", - p->signal_event_count, ev->event_id, - ev->user_signal_address); + pr_debug("Signal event number %zu created with id %d\n", + p->signal_event_count, ev->event_id); return 0; } @@ -303,12 +269,7 @@ static void shutdown_signal_page(struct kfd_process *p) { struct kfd_signal_page *page = p->signal_page; - if (page) { - if (page->need_to_free_pages) - free_pages((unsigned long)page->kernel_address, - get_order(KFD_SIGNAL_EVENT_LIMIT * 8)); - kfree(page); - } + kfree(page); } void kfd_event_free_process(struct kfd_process *p) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.h b/drivers/gpu/drm/amd/amdkfd/kfd_events.h index 1dc21c13833b..88e3797bfc42 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.h @@ -63,9 +63,6 @@ struct kfd_event { spinlock_t lock; wait_queue_head_t wq; /* List of event waiters. */ - /* Only for signal events. */ - uint64_t __user *user_signal_address; - /* type specific data */ union { struct kfd_hsa_memory_exception_data memory_exception_data; -- cgit v1.2.3 From b24b9f5f2002828b9ad185068eb1150b97c6533b Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Fri, 12 Jun 2026 22:52:12 -0400 Subject: Revert "drm/amdkfd: Add gfx11 queue/pipe reset support to topology" This reverts commit d04560b5f9c29ff4c1787dad3b491fa115fd07cb. Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 1 - drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 3 --- 2 files changed, 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index babc1116baec..7b623e3f5efd 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -210,7 +210,6 @@ enum cache_policy { }; #define KFD_GC_VERSION(dev) (amdgpu_ip_version((dev)->adev, GC_HWIP, 0)) -#define KFD_GC_VERSION_MAJ(dev) ((KFD_GC_VERSION(dev) >> 24)) #define KFD_IS_SOC15(dev) ((KFD_GC_VERSION(dev)) >= (IP_VERSION(9, 0, 1))) #define KFD_SUPPORT_XNACK_PER_PROCESS(dev)\ ((KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 2)) || \ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 4af9b567e499..00517c3d0e6a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2024,9 +2024,6 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_ALU_OPERATIONS_SUPPORTED; - if (KFD_GC_VERSION_MAJ(dev->gpu) == 11) - dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; - if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 1, 0)) { dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_MEMORY_OPERATIONS_SUPPORTED; -- cgit v1.2.3 From b492d22bc93508ed140b93a510da522f26d50d9e Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 11 Jun 2026 17:42:48 +0800 Subject: drm/amdgpu/ras: Estimate RAS reservation when report capacity Add estimate of how much vram we need to reserve for RAS when caculating the total available vram Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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 ee48adb30731..5b389a92118a 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 @@ -95,11 +95,35 @@ static int amdgpu_ras_mgr_init_aca_config(struct amdgpu_device *adev, return 0; } +static uint64_t amdgpu_ras_mgr_reserved_vram_size(struct amdgpu_device *adev) +{ + struct amdgpu_ras *con = amdgpu_ras_get_context(adev); + uint64_t reserved_pages_in_bytes = 0; + + if (!con || (adev->flags & AMD_IS_APU)) + return 0; + + switch (amdgpu_ip_version(adev, MP0_HWIP, 0)) { + case IP_VERSION(13, 0, 6): + case IP_VERSION(13, 0, 12): + reserved_pages_in_bytes = RAS_RESERVED_VRAM_SIZE_DEFAULT; + break; + case IP_VERSION(13, 0, 14): + reserved_pages_in_bytes = (RAS_RESERVED_VRAM_SIZE_DEFAULT << 1); + break; + default: + break; + } + return reserved_pages_in_bytes; +} + static int amdgpu_ras_mgr_init_eeprom_config(struct amdgpu_device *adev, struct ras_core_config *config) { struct ras_eeprom_config *eeprom_cfg = &config->eeprom_cfg; + uint64_t ras_reserved_vram_size; + ras_reserved_vram_size = amdgpu_ras_mgr_reserved_vram_size(adev); eeprom_cfg->eeprom_sys_fn = &amdgpu_ras_eeprom_i2c_sys_func; eeprom_cfg->eeprom_i2c_adapter = adev->pm.ras_eeprom_i2c_bus; if (eeprom_cfg->eeprom_i2c_adapter) { @@ -133,7 +157,7 @@ static int amdgpu_ras_mgr_init_eeprom_config(struct amdgpu_device *adev, div64_u64(adev->gmc.mc_vram_size, TYPICAL_ECC_BAD_PAGE_RATE); else if (amdgpu_bad_page_threshold == WARN_NONSTOP_OVER_THRESHOLD) eeprom_cfg->eeprom_record_threshold_count = - COUNT_BAD_PAGE_THRESHOLD(RAS_RESERVED_VRAM_SIZE_DEFAULT); + COUNT_BAD_PAGE_THRESHOLD(ras_reserved_vram_size); else eeprom_cfg->eeprom_record_threshold_count = amdgpu_bad_page_threshold; -- cgit v1.2.3 From 9d748a8ac1ece966a712a3b3d81a39b6ec1cdd5c Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Fri, 12 Jun 2026 22:58:00 -0400 Subject: drm/amdkfd: Add queue reset support on gfx11 dGPU This patch enables queue reset support to KFD topology for gfx11 dGPUs Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 00517c3d0e6a..44c648907198 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2019,6 +2019,11 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) } else { dev->node_props.debug_prop |= HSA_DBG_WATCH_ADDR_MASK_LO_BIT_GFX10 | HSA_DBG_WATCH_ADDR_MASK_HI_BIT; + /* gfx11 dGPU */ + if (KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 0) || + KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 2) || + KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 3)) + dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) dev->node_props.capability |= -- cgit v1.2.3 From 97bcaf15ad25b14bd272fdff3616f9af5a8820c5 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Fri, 12 Jun 2026 14:02:49 +0800 Subject: drm/amdgpu: implement per-process MES context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MES process context is a process-level page where process specific context is saved for MES scheduler. However, current user-queue code path assigns fw_obj of a queue to MES process_context_addr when adding the queue to MES. This means every new queue from the same process would replace the previous process context address with that queue's fw_obj address. What's worse is, when user space frees a queue, its fw_obj will be freed as well, causing MES working on a NULL page pointer. This issue leads to inconsistency and crash in the scheduler. This commit allocates a process-level page for MES process contexts for a process other than queue-level Signed-off-by: Zhu Lingshan Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 6 ++++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 2 ++ drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 51 +++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 3bcde67aa092..3644e9193f58 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1165,6 +1165,7 @@ int amdgpu_userq_mgr_init(struct amdgpu_userq_mgr *userq_mgr, struct drm_file *f xa_init_flags(&userq_mgr->userq_xa, XA_FLAGS_ALLOC); userq_mgr->adev = adev; userq_mgr->file = file_priv; + mutex_init(&userq_mgr->proc_ctx_lock); INIT_DELAYED_WORK(&userq_mgr->resume_work, amdgpu_userq_restore_worker); INIT_WORK(&userq_mgr->reset_work, amdgpu_userq_mgr_reset_work); @@ -1218,6 +1219,11 @@ void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr) */ cancel_work_sync(&userq_mgr->reset_work); + amdgpu_bo_free_kernel(&userq_mgr->proc_ctx_obj.obj, + &userq_mgr->proc_ctx_obj.gpu_addr, + &userq_mgr->proc_ctx_obj.cpu_ptr); + + mutex_destroy(&userq_mgr->proc_ctx_lock); mutex_destroy(&userq_mgr->userq_mutex); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 9df1b78407f5..7a5f8ed794b8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -126,6 +126,8 @@ struct amdgpu_userq_mgr { struct amdgpu_device *adev; struct delayed_work resume_work; struct drm_file *file; + struct mutex proc_ctx_lock; + struct amdgpu_userq_obj proc_ctx_obj; /** * @reset_work: diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index e9bd5ad98265..dba3707c2659 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -133,8 +133,8 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) queue_input.gang_quantum = 10000; queue_input.paging = false; - queue_input.process_context_addr = ctx->gpu_addr; - queue_input.gang_context_addr = ctx->gpu_addr + AMDGPU_USERQ_PROC_CTX_SZ; + queue_input.process_context_addr = uq_mgr->proc_ctx_obj.gpu_addr; + queue_input.gang_context_addr = ctx->gpu_addr; queue_input.inprocess_gang_priority = AMDGPU_MES_PRIORITY_LEVEL_NORMAL; queue_input.gang_global_priority_level = convert_to_mes_priority(queue->priority); @@ -169,7 +169,7 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); queue_input.doorbell_offset = queue->doorbell_index; - queue_input.gang_context_addr = ctx->gpu_addr + AMDGPU_USERQ_PROC_CTX_SZ; + queue_input.gang_context_addr = ctx->gpu_addr; amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->remove_hw_queue(&adev->mes, &queue_input); @@ -243,12 +243,8 @@ static int mes_userq_create_ctx_space(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_userq_obj *ctx = &queue->fw_obj; int r, size; - /* - * The FW expects at least one page space allocated for - * process ctx and gang ctx each. Create an object - * for the same. - */ - size = AMDGPU_USERQ_PROC_CTX_SZ + AMDGPU_USERQ_GANG_CTX_SZ; + /* The FW expects at least one page space allocated for gang ctx. */ + size = AMDGPU_USERQ_GANG_CTX_SZ; r = amdgpu_bo_create_kernel(uq_mgr->adev, size, 0, AMDGPU_GEM_DOMAIN_GTT, &ctx->obj, &ctx->gpu_addr, @@ -262,6 +258,30 @@ static int mes_userq_create_ctx_space(struct amdgpu_userq_mgr *uq_mgr, return 0; } +static int mes_userq_create_proc_ctx_space(struct amdgpu_userq_mgr *uq_mgr) +{ + int r = 0; + + mutex_lock(&uq_mgr->proc_ctx_lock); + /* This check is a necessary because amdgpu_bo_create_kernel() + * calls helpers like amdgpu_bo_pin() and memset() unconditionally + */ + if (!uq_mgr->proc_ctx_obj.obj) { + r = amdgpu_bo_create_kernel(uq_mgr->adev, AMDGPU_USERQ_PROC_CTX_SZ, + 0, AMDGPU_GEM_DOMAIN_GTT, + &uq_mgr->proc_ctx_obj.obj, + &uq_mgr->proc_ctx_obj.gpu_addr, + &uq_mgr->proc_ctx_obj.cpu_ptr); + + if (!r) + memset(uq_mgr->proc_ctx_obj.cpu_ptr, 0, AMDGPU_USERQ_PROC_CTX_SZ); + } + + mutex_unlock(&uq_mgr->proc_ctx_lock); + + return r; +} + static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, struct drm_amdgpu_userq_in *args_in) { @@ -434,7 +454,14 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, goto free_mqd; } - /* Create BO for FW operations */ + /* Create per-process MES process context BO */ + r = mes_userq_create_proc_ctx_space(uq_mgr); + if (r) { + DRM_ERROR("Failed to allocate MES process context space bo, error: %d\n", r); + goto free_mqd; + } + + /* Create BO of a gang for FW operations */ r = mes_userq_create_ctx_space(uq_mgr, queue, mqd_user); if (r) { DRM_ERROR("Failed to allocate BO for userqueue (%d)", r); @@ -502,7 +529,7 @@ static int mes_userq_preempt(struct amdgpu_usermode_queue *queue) *fence_ptr = 0; memset(&queue_input, 0x0, sizeof(struct mes_suspend_gang_input)); - queue_input.gang_context_addr = ctx->gpu_addr + AMDGPU_USERQ_PROC_CTX_SZ; + queue_input.gang_context_addr = ctx->gpu_addr; queue_input.suspend_fence_addr = fence_gpu_addr; queue_input.suspend_fence_value = 1; amdgpu_mes_lock(&adev->mes); @@ -539,7 +566,7 @@ static int mes_userq_restore(struct amdgpu_usermode_queue *queue) return 0; memset(&queue_input, 0x0, sizeof(struct mes_resume_gang_input)); - queue_input.gang_context_addr = ctx->gpu_addr + AMDGPU_USERQ_PROC_CTX_SZ; + queue_input.gang_context_addr = ctx->gpu_addr; amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->resume_gang(&adev->mes, &queue_input); -- cgit v1.2.3 From ae16ca815dfc917929ca10a2c83ee64fa7e0c433 Mon Sep 17 00:00:00 2001 From: Samuel Zhang Date: Thu, 11 Jun 2026 11:18:07 +0800 Subject: drm/amd: add AMDGPU_DEBUG_HIBERNATION_THAW_RESUME_GPU debug mask Kernel parameter `no_console_suspend` is required to capture all hibernation kernel log via serial console. But when the parameter is set, GPU will be resumed in thaw stage. This causes many issues on alinux3 kernel. Fix: add new debug mask `AMDGPU_DEBUG_HIBERNATION_THAW_RESUME_GPU` to replace the check of `console_suspend_enabled` in thaw() callback. User can enable it using `amdgpu.debug_mask=0x800`. Signed-off-by: Samuel Zhang Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 5f775c6e9240..45bf05306c90 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -1136,6 +1136,7 @@ struct amdgpu_device { bool debug_vm_userptr; bool debug_disable_ce_logs; bool debug_enable_ce_cs; + bool debug_hibernation_thaw_resume_gpu; /* Protection for the following isolation structure */ struct mutex enforce_isolation_mutex; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 503bb64c1e55..b4120207bfa0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -146,7 +145,8 @@ enum AMDGPU_DEBUG_MASK { AMDGPU_DEBUG_SMU_POOL = BIT(7), AMDGPU_DEBUG_VM_USERPTR = BIT(8), AMDGPU_DEBUG_DISABLE_RAS_CE_LOG = BIT(9), - AMDGPU_DEBUG_ENABLE_CE_CS = BIT(10) + AMDGPU_DEBUG_ENABLE_CE_CS = BIT(10), + AMDGPU_DEBUG_HIBERNATION_THAW_RESUME_GPU = BIT(11), }; unsigned int amdgpu_vram_limit = UINT_MAX; @@ -2291,6 +2291,11 @@ static void amdgpu_init_debug_options(struct amdgpu_device *adev) pr_info("debug: allowing command submission to CE engine\n"); adev->debug_enable_ce_cs = true; } + + if (amdgpu_debug_mask & AMDGPU_DEBUG_HIBERNATION_THAW_RESUME_GPU) { + pr_info("debug: resume gpu in thaw() of hibernation\n"); + adev->debug_hibernation_thaw_resume_gpu = true; + } } static unsigned long amdgpu_fix_asic_type(struct pci_dev *pdev, unsigned long flags) @@ -2705,9 +2710,10 @@ static int amdgpu_pmops_freeze(struct device *dev) static int amdgpu_pmops_thaw(struct device *dev) { struct drm_device *drm_dev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(drm_dev); /* do not resume device if it's normal hibernation */ - if (console_suspend_enabled && + if (!adev->debug_hibernation_thaw_resume_gpu && !pm_hibernate_is_recovering() && !pm_hibernation_mode_is_suspend()) return 0; -- cgit v1.2.3 From d0a8f98166ff3dc1047b93b0d5c45dd0b6696838 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Tue, 19 May 2026 16:05:36 +0530 Subject: drm/amdxcp: Add more checks to amdxcp Add NULL check to ddev argument and guard pdev_num against underflow. Signed-off-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdxcp/amdgpu_xcp_drv.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdxcp/amdgpu_xcp_drv.c b/drivers/gpu/drm/amd/amdxcp/amdgpu_xcp_drv.c index 995cae6be144..84e13537a7ba 100644 --- a/drivers/gpu/drm/amd/amdxcp/amdgpu_xcp_drv.c +++ b/drivers/gpu/drm/amd/amdxcp/amdgpu_xcp_drv.c @@ -45,7 +45,7 @@ static const struct drm_driver amdgpu_xcp_driver = { .minor = 0, }; -static int8_t pdev_num; +static u8 pdev_num; static struct xcp_device *xcp_dev[MAX_XCP_PLATFORM_DEVICE]; static DEFINE_MUTEX(xcp_mutex); @@ -56,6 +56,11 @@ int amdgpu_xcp_drm_dev_alloc(struct drm_device **ddev) char *dev_name; int ret, i; + if (!ddev) + return -EINVAL; + + BUILD_BUG_ON(MAX_XCP_PLATFORM_DEVICE >= U8_MAX); + guard(mutex)(&xcp_mutex); if (pdev_num >= MAX_XCP_PLATFORM_DEVICE) @@ -105,7 +110,7 @@ out_unregister: } EXPORT_SYMBOL(amdgpu_xcp_drm_dev_alloc); -static void free_xcp_dev(int8_t index) +static void free_xcp_dev(uint8_t index) { if ((index < MAX_XCP_PLATFORM_DEVICE) && (xcp_dev[index])) { struct platform_device *pdev = xcp_dev[index]->pdev; @@ -114,17 +119,18 @@ static void free_xcp_dev(int8_t index) platform_device_unregister(pdev); xcp_dev[index] = NULL; - pdev_num--; + if (pdev_num > 0) + pdev_num--; } } void amdgpu_xcp_drm_dev_free(struct drm_device *ddev) { - int8_t i; + uint8_t i; guard(mutex)(&xcp_mutex); - for (i = 0; i < MAX_XCP_PLATFORM_DEVICE; i++) { + for (i = 0; pdev_num && i < MAX_XCP_PLATFORM_DEVICE; i++) { if ((xcp_dev[i]) && (&xcp_dev[i]->drm == ddev)) { free_xcp_dev(i); break; @@ -135,7 +141,7 @@ EXPORT_SYMBOL(amdgpu_xcp_drm_dev_free); void amdgpu_xcp_drv_release(void) { - int8_t i; + uint8_t i; guard(mutex)(&xcp_mutex); -- cgit v1.2.3 From b54bf09ee083bdcf323321971d8d76d45701517c Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Sun, 14 Jun 2026 12:15:12 -0400 Subject: drm/amdgpu: Add gfx12.0.1 adev to queue reset support This patch adds the inclusion of gfx12.0.1 by checking GC's major number and minor number equal to 12.0.* with the same mes_sched version. Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 020d9c512306..6c0dde3786e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -864,12 +864,13 @@ bool amdgpu_mes_suspend_resume_all_supported(struct amdgpu_device *adev) bool amdgpu_mes_queue_reset_by_mes_supported(struct amdgpu_device *adev) { - return (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(12, 1, 0) && - (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x73) || - (IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)) == 11 && - (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x8c) || - (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(12, 0, 0) && - (adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x8d); + u32 ip_maj = IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)); + u32 ip_min = IP_VERSION_MIN(amdgpu_ip_version(adev, GC_HWIP, 0)); + u32 mes_sched = adev->mes.sched_version & AMDGPU_MES_VERSION_MASK; + + return (ip_maj == 11 && mes_sched >= 0x8c) || + ((ip_maj == 12 && ip_min == 0) && mes_sched >= 0x8d) || + ((ip_maj == 12 && ip_min == 1) && mes_sched >= 0x73); } /* Fix me -- node_id is used to identify the correct MES instances in the future */ -- cgit v1.2.3 From 96d745011842e906774aa8523abb78775b008a4e Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Sun, 14 Jun 2026 15:50:26 -0400 Subject: drm/amdkfd: Add queue reset support to gfx12.0 This adds gfx 12.0 queue reset support to KFD topology. Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 44c648907198..3c67066e6657 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2025,14 +2025,15 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 3)) dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; - if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) + if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) { dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_ALU_OPERATIONS_SUPPORTED; + dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; + } if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 1, 0)) { dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_MEMORY_OPERATIONS_SUPPORTED; - dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; dev->node_props.capability2 |= HSA_CAP2_TRAP_DEBUG_LDS_OUT_OF_ADDR_RANGE_SUPPORTED; } -- cgit v1.2.3 From 5c372a64b174bd144acb68177cf1f9a03402b5d2 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Tue, 26 May 2026 09:47:25 +0800 Subject: drm/edid: extract base section header processing into helper Extract the DisplayID base section header logging and non_desktop detection from update_displayid_info() into a dedicated helper, drm_displayid_process_base_section_header(). Remove the break so the iterator walks through all data blocks, preparing for future patches that will parse additional block types within the loop. The helper is called only once for the base section via a base_section_header_processed flag. Since version and primary_use are only captured from the base section, and extension sections carry a primary use of zero per spec, the non_desktop logic is unaffected. No functional change. Assisted-by: Copilot:Claude-Opus-4.6 Signed-off-by: Chenyu Chen Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/drm_edid.c | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 404208bf23a6..aebbff8ac992 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -6715,30 +6715,35 @@ static void drm_reset_display_info(struct drm_connector *connector) memset(&info->amd_vsdb, 0, sizeof(info->amd_vsdb)); } +static void drm_displayid_process_base_section_header(struct drm_connector *connector, + const struct displayid_iter *iter) +{ + struct drm_display_info *info = &connector->display_info; + + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", + connector->base.id, connector->name, + displayid_version(iter), + displayid_primary_use(iter)); + if (displayid_version(iter) == DISPLAY_ID_STRUCTURE_VER_20 && + (displayid_primary_use(iter) == PRIMARY_USE_HEAD_MOUNTED_VR || + displayid_primary_use(iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) + info->non_desktop = true; +} + static void update_displayid_info(struct drm_connector *connector, const struct drm_edid *drm_edid) { - struct drm_display_info *info = &connector->display_info; const struct displayid_block *block; struct displayid_iter iter; + bool base_section_header_processed = false; displayid_iter_edid_begin(drm_edid, &iter); displayid_iter_for_each(block, &iter) { - drm_dbg_kms(connector->dev, - "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", - connector->base.id, connector->name, - displayid_version(&iter), - displayid_primary_use(&iter)); - if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && - (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || - displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) - info->non_desktop = true; - - /* - * We're only interested in the base section here, no need to - * iterate further. - */ - break; + if (!base_section_header_processed) { + drm_displayid_process_base_section_header(connector, &iter); + base_section_header_processed = true; + } } displayid_iter_end(&iter); } -- cgit v1.2.3 From e239d3e3cbb7b27522727371fa66523fc769f454 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Tue, 26 May 2026 09:52:28 +0800 Subject: drm/edid: parse panel type from DisplayID 2.x Display Parameters Parse the Display Parameters Data Block (tag 0x21) defined in DisplayID v2.1a Section 4.2.6. Extract the Display Device Technology field from the color depth and device technology byte, which indicates whether the panel uses LCD or OLED technology. Add a panel_type field to struct drm_display_info and populate it during DisplayID iteration so downstream drivers can use it for panel-type-dependent behavior. Add DRM_MODE_PANEL_TYPE_LCD to the UAPI panel type property alongside the existing OLED value. Assisted-by: Copilot:Claude-Opus-4.6 Signed-off-by: Chenyu Chen Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/drm_connector.c | 3 ++- drivers/gpu/drm/drm_displayid_internal.h | 24 +++++++++++++++++ drivers/gpu/drm/drm_edid.c | 45 ++++++++++++++++++++++++++++++++ include/drm/drm_connector.h | 6 +++++ include/uapi/drm/drm_mode.h | 1 + 5 files changed, 78 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 3fa4d2082cd7..9d820a2a87ce 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -1176,6 +1176,7 @@ static const struct drm_prop_enum_list drm_link_status_enum_list[] = { static const struct drm_prop_enum_list drm_panel_type_enum_list[] = { { DRM_MODE_PANEL_TYPE_UNKNOWN, "unknown" }, { DRM_MODE_PANEL_TYPE_OLED, "OLED" }, + { DRM_MODE_PANEL_TYPE_LCD, "LCD" }, }; /** @@ -1508,7 +1509,7 @@ EXPORT_SYMBOL(drm_hdmi_connector_get_output_format_name); * never read back the value of "DPMS" because it can be incorrect. * panel_type: * Immutable enum property to indicate the type of connected panel. - * Possible values are "unknown" (default) and "OLED". + * Possible values are "unknown" (default), "OLED", and "LCD". * PATH: * Connector path property to identify how this sink is physically * connected. Used by DP MST. This should be set by calling diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h index 5b1b32f73516..6f431aafafcf 100644 --- a/drivers/gpu/drm/drm_displayid_internal.h +++ b/drivers/gpu/drm/drm_displayid_internal.h @@ -142,6 +142,30 @@ struct displayid_formula_timing_block { struct displayid_formula_timings_9 timings[]; } __packed; +#define DISPLAYID_DEVICE_TECH_UNSPECIFIED 0 +#define DISPLAYID_DEVICE_TECH_LCD 1 +#define DISPLAYID_DEVICE_TECH_OLED 2 + +#define DISPLAYID_DISPLAY_PARAMS_DEVICE_TECH GENMASK(6, 4) + +struct displayid_display_params_block { + struct displayid_block base; + __le16 horiz_image_size; + __le16 vert_image_size; + __le16 horiz_pixel_count; + __le16 vert_pixel_count; + u8 features; + u8 primary_color1[3]; + u8 primary_color2[3]; + u8 primary_color3[3]; + u8 white_point[3]; + __le16 max_luminance_full; + __le16 max_luminance_10; + __le16 min_luminance; + u8 color_depth_and_tech; /* [2:0] depth, [6:4] device tech, [7] theme */ + u8 gamma_eotf; +} __packed; + #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) #define DISPLAYID_VESA_MSO_MODE GENMASK(6, 5) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index aebbff8ac992..ae26618a9a57 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -6713,6 +6713,8 @@ static void drm_reset_display_info(struct drm_connector *connector) info->source_physical_address = CEC_PHYS_ADDR_INVALID; memset(&info->amd_vsdb, 0, sizeof(info->amd_vsdb)); + + info->panel_type = DRM_MODE_PANEL_TYPE_UNKNOWN; } static void drm_displayid_process_base_section_header(struct drm_connector *connector, @@ -6731,6 +6733,45 @@ static void drm_displayid_process_base_section_header(struct drm_connector *conn info->non_desktop = true; } +static void +drm_displayid_parse_display_params(struct drm_connector *connector, + const struct displayid_block *block) +{ + struct drm_display_info *info = &connector->display_info; + const struct displayid_display_params_block *params = + (const struct displayid_display_params_block *)block; + u8 tech; + + if (block->num_bytes < sizeof(*params) - sizeof(params->base)) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] DisplayID Display Parameters block too short (%u < %zu)\n", + connector->base.id, connector->name, + block->num_bytes, + sizeof(*params) - sizeof(params->base)); + return; + } + + tech = FIELD_GET(DISPLAYID_DISPLAY_PARAMS_DEVICE_TECH, + params->color_depth_and_tech); + + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] DisplayID Display Parameters: device technology %s\n", + connector->base.id, connector->name, + tech == DISPLAYID_DEVICE_TECH_LCD ? "LCD" : + tech == DISPLAYID_DEVICE_TECH_OLED ? "OLED" : "unspecified"); + + switch (tech) { + case DISPLAYID_DEVICE_TECH_LCD: + info->panel_type = DRM_MODE_PANEL_TYPE_LCD; + break; + case DISPLAYID_DEVICE_TECH_OLED: + info->panel_type = DRM_MODE_PANEL_TYPE_OLED; + break; + default: + break; + } +} + static void update_displayid_info(struct drm_connector *connector, const struct drm_edid *drm_edid) { @@ -6744,6 +6785,10 @@ static void update_displayid_info(struct drm_connector *connector, drm_displayid_process_base_section_header(connector, &iter); base_section_header_processed = true; } + + if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && + block->tag == DATA_BLOCK_2_DISPLAY_PARAMETERS) + drm_displayid_parse_display_params(connector, block); } displayid_iter_end(&iter); } diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 5ad62c207d00..cd06a3b914a0 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -921,6 +921,12 @@ struct drm_display_info { * @amd_vsdb: AMD-specific VSDB information. */ struct drm_amd_vsdb_info amd_vsdb; + + /** + * @panel_type: Panel type from DisplayID Display Parameters + * Data Block (tag 0x21). Uses DRM_MODE_PANEL_TYPE_* constants. + */ + u8 panel_type; }; int drm_display_info_set_bus_formats(struct drm_display_info *info, diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index 381a3e857d4e..bd435effdcee 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -155,6 +155,7 @@ extern "C" { /* Panel type property */ #define DRM_MODE_PANEL_TYPE_UNKNOWN 0 #define DRM_MODE_PANEL_TYPE_OLED 1 +#define DRM_MODE_PANEL_TYPE_LCD 2 /* * DRM_MODE_ROTATE_ -- cgit v1.2.3 From 168f51adecd7c71e59a50ebcd0d24b010f981746 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Tue, 26 May 2026 09:53:15 +0800 Subject: drm/amd/display: use DisplayID panel type in dm_set_panel_type Wire up the newly parsed panel_type from drm_display_info into amdgpu_dm's panel type detection path. When neither the AMD VSDB nor DPCD determines the panel type, fall back to the DisplayID Display Device Technology field to set PANEL_TYPE_LCD or PANEL_TYPE_OLED accordingly. Also expose LCD to userspace via the panel_type connector property. Assisted-by: Copilot:Claude-Opus-4.6 Signed-off-by: Chenyu Chen Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 16 +++++++++++----- 1 file changed, 11 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 27f8fb2e8c12..300ee26f26ff 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 @@ -420,11 +420,13 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->panel_type = PANEL_TYPE_OLED; } - /* - * TODO: get panel type from DID2 that has device technology field - * to specify if it's OLED or not. But we need to wait for DID2 - * support in DC and EDID parser to be able to use it here. - */ + /* If VSDB and DPCD didn't determine panel type, check DID */ + if (link->panel_type == PANEL_TYPE_NONE) { + if (display_info->panel_type == DRM_MODE_PANEL_TYPE_LCD) + link->panel_type = PANEL_TYPE_LCD; + else if (display_info->panel_type == DRM_MODE_PANEL_TYPE_OLED) + link->panel_type = PANEL_TYPE_OLED; + } if (link->panel_type == PANEL_TYPE_NONE) { struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; @@ -442,6 +444,10 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, DRM_MODE_PANEL_TYPE_OLED); + else if (link->panel_type == PANEL_TYPE_LCD) + drm_object_property_set_value(&connector->base, + adev_to_drm(adev)->mode_config.panel_type_property, + DRM_MODE_PANEL_TYPE_LCD); else drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, -- cgit v1.2.3 From 87be26aee76239c6da03e599f238a426897f78ad Mon Sep 17 00:00:00 2001 From: Pavel Ondračka Date: Wed, 10 Jun 2026 10:32:45 +0200 Subject: drm/radeon: fix r100_copy_blit for large BOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit r100_copy_blit() copies BOs as 1024-pixel-wide ARGB8888 blits, so one GPU page becomes one blit row. Large copies are split into chunks of at most 8191 rows. The kernel register header names the packet coordinate dwords SRC_Y_X and DST_Y_X. In the BITBLT_MULTI description in R5xx_Acceleration_v1.5.pdf docs, these correspond to [SRC_X1 | SRC_Y1] and [DST_X1 | DST_Y1], which are signed 13-bit coordinates in the -8192..8191 range. The old code kept SRC/DST_PITCH_OFFSET at the BO base and used SRC_Y_X/DST_Y_X as the chunk address, so large BO moves could exceed that coordinate range. Compute per-chunk SRC/DST_PITCH_OFFSET bases and emit zero source and destination coordinates. r100_copy_blit() already packs SRC/DST_PITCH_OFFSET as pitch plus base offset, so large chunk addresses belong there rather than in the coordinate fields. This fixes Prison Architect corruption with 4096x4096 mipped textures after they are evicted to GTT under memory pressure on RV530. Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/6716 Acked-by: Christian König Signed-off-by: Pavel Ondračka Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/r100.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/radeon/r100.c b/drivers/gpu/drm/radeon/r100.c index 3ac1a79b6f13..533215d6e9cb 100644 --- a/drivers/gpu/drm/radeon/r100.c +++ b/drivers/gpu/drm/radeon/r100.c @@ -906,6 +906,7 @@ struct radeon_fence *r100_copy_blit(struct radeon_device *rdev, { struct radeon_ring *ring = &rdev->ring[RADEON_RING_TYPE_GFX_INDEX]; struct radeon_fence *fence; + uint64_t cur_src_offset, cur_dst_offset; uint32_t cur_pages; uint32_t stride_bytes = RADEON_GPU_PAGE_SIZE; uint32_t pitch; @@ -934,6 +935,10 @@ struct radeon_fence *r100_copy_blit(struct radeon_device *rdev, cur_pages = 8191; } num_gpu_pages -= cur_pages; + cur_src_offset = src_offset + + (uint64_t)num_gpu_pages * RADEON_GPU_PAGE_SIZE; + cur_dst_offset = dst_offset + + (uint64_t)num_gpu_pages * RADEON_GPU_PAGE_SIZE; /* pages are in Y direction - height page width in X direction - width */ @@ -950,13 +955,13 @@ struct radeon_fence *r100_copy_blit(struct radeon_device *rdev, RADEON_DP_SRC_SOURCE_MEMORY | RADEON_GMC_CLR_CMP_CNTL_DIS | RADEON_GMC_WR_MSK_DIS); - radeon_ring_write(ring, (pitch << 22) | (src_offset >> 10)); - radeon_ring_write(ring, (pitch << 22) | (dst_offset >> 10)); + radeon_ring_write(ring, (pitch << 22) | (cur_src_offset >> 10)); + radeon_ring_write(ring, (pitch << 22) | (cur_dst_offset >> 10)); radeon_ring_write(ring, (0x1fff) | (0x1fff << 16)); radeon_ring_write(ring, 0); radeon_ring_write(ring, (0x1fff) | (0x1fff << 16)); - radeon_ring_write(ring, num_gpu_pages); - radeon_ring_write(ring, num_gpu_pages); + radeon_ring_write(ring, 0); + radeon_ring_write(ring, 0); radeon_ring_write(ring, cur_pages | (stride_pixels << 16)); } radeon_ring_write(ring, PACKET0(RADEON_DSTCACHE_CTLSTAT, 0)); -- cgit v1.2.3 From d3f30034f861a585f8e487ae1555d9f288a96f87 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Mon, 8 Jun 2026 14:36:38 +0800 Subject: drm/radeon: avoid double free in r600 DPM cleanup r600_parse_extended_power_table() uses manual kfree() calls for some early allocation failures, but the freed pointers are left in the dynamic power-management state. If device teardown later calls r600_free_extended_power_table(), those stale pointers can be freed again. Use the common extended power table cleanup helper for those early failure paths as well, and clear each pointer after freeing it so repeated cleanup stays safe. Signed-off-by: Ruoyu Wang Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/r600_dpm.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/radeon/r600_dpm.c b/drivers/gpu/drm/radeon/r600_dpm.c index 83f1ae31cbdb..9755e717ca8b 100644 --- a/drivers/gpu/drm/radeon/r600_dpm.c +++ b/drivers/gpu/drm/radeon/r600_dpm.c @@ -932,7 +932,7 @@ int r600_parse_extended_power_table(struct radeon_device *rdev) ret = r600_parse_clk_voltage_dep_table(&rdev->pm.dpm.dyn_state.vddci_dependency_on_mclk, dep_table); if (ret) { - kfree(rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries); + r600_free_extended_power_table(rdev); return ret; } } @@ -943,8 +943,7 @@ int r600_parse_extended_power_table(struct radeon_device *rdev) ret = r600_parse_clk_voltage_dep_table(&rdev->pm.dpm.dyn_state.vddc_dependency_on_mclk, dep_table); if (ret) { - kfree(rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries); - kfree(rdev->pm.dpm.dyn_state.vddci_dependency_on_mclk.entries); + r600_free_extended_power_table(rdev); return ret; } } @@ -955,9 +954,7 @@ int r600_parse_extended_power_table(struct radeon_device *rdev) ret = r600_parse_clk_voltage_dep_table(&rdev->pm.dpm.dyn_state.mvdd_dependency_on_mclk, dep_table); if (ret) { - kfree(rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries); - kfree(rdev->pm.dpm.dyn_state.vddci_dependency_on_mclk.entries); - kfree(rdev->pm.dpm.dyn_state.vddc_dependency_on_mclk.entries); + r600_free_extended_power_table(rdev); return ret; } } @@ -1296,17 +1293,29 @@ void r600_free_extended_power_table(struct radeon_device *rdev) struct radeon_dpm_dynamic_state *dyn_state = &rdev->pm.dpm.dyn_state; kfree(dyn_state->vddc_dependency_on_sclk.entries); + dyn_state->vddc_dependency_on_sclk.entries = NULL; kfree(dyn_state->vddci_dependency_on_mclk.entries); + dyn_state->vddci_dependency_on_mclk.entries = NULL; kfree(dyn_state->vddc_dependency_on_mclk.entries); + dyn_state->vddc_dependency_on_mclk.entries = NULL; kfree(dyn_state->mvdd_dependency_on_mclk.entries); + dyn_state->mvdd_dependency_on_mclk.entries = NULL; kfree(dyn_state->cac_leakage_table.entries); + dyn_state->cac_leakage_table.entries = NULL; kfree(dyn_state->phase_shedding_limits_table.entries); + dyn_state->phase_shedding_limits_table.entries = NULL; kfree(dyn_state->ppm_table); + dyn_state->ppm_table = NULL; kfree(dyn_state->cac_tdp_table); + dyn_state->cac_tdp_table = NULL; kfree(dyn_state->vce_clock_voltage_dependency_table.entries); + dyn_state->vce_clock_voltage_dependency_table.entries = NULL; kfree(dyn_state->uvd_clock_voltage_dependency_table.entries); + dyn_state->uvd_clock_voltage_dependency_table.entries = NULL; kfree(dyn_state->samu_clock_voltage_dependency_table.entries); + dyn_state->samu_clock_voltage_dependency_table.entries = NULL; kfree(dyn_state->acp_clock_voltage_dependency_table.entries); + dyn_state->acp_clock_voltage_dependency_table.entries = NULL; } enum radeon_pcie_gen r600_get_pcie_gen_support(struct radeon_device *rdev, -- cgit v1.2.3 From 402e04f11ff75fe4580a6e5f00622b58f4c544b9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 12 Jun 2026 11:44:43 -0500 Subject: drm/amdgpu: Export ip_discovery sysfs on probe failure When driver probe fails (missing firmware, unsupported hardware, etc.), the entire device is torn down including the ip_discovery sysfs folder, preventing users from identifying what hardware is present. Export ip_discovery sysfs even when probe fails by creating it early in the probe flow and tying its lifetime to the PCI device rather than the driver. The sysfs folder persists across probe failures and module reloads, but is cleaned up on driver unbind. Acked-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 285 ++++++++++++++++++++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h | 5 + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 2 + 3 files changed, 257 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index be5069642a90..b844f4a9f0c6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -22,6 +22,7 @@ */ #include +#include #include "amdgpu.h" #include "amdgpu_discovery.h" @@ -148,6 +149,26 @@ MODULE_FIRMWARE("amdgpu/aldebaran_ip_discovery.bin"); #define mmDRIVER_SCRATCH_1 0x95 #define mmDRIVER_SCRATCH_2 0x96 +struct ip_discovery_top { + struct kobject kobj; + struct kset die_kset; + struct pci_dev *pdev; + struct amdgpu_device *adev; + uint8_t *discovery_bin; + uint32_t bin_size; + bool standalone_mode; +}; + +/* List to track early-initialized ip_discovery_top entries */ +struct early_ip_discovery { + struct list_head list; + struct pci_dev *pdev; + struct ip_discovery_top *ip_top; +}; + +static LIST_HEAD(early_ip_discovery_list); +static DEFINE_MUTEX(early_ip_discovery_mutex); + static const char *hw_id_names[HW_ID_MAX] = { [MP1_HWID] = "MP1", [MP2_HWID] = "MP2", @@ -542,25 +563,37 @@ static const char *amdgpu_discovery_get_fw_name(struct amdgpu_device *adev) } } -static int amdgpu_discovery_get_table_info(struct amdgpu_device *adev, - struct table_info **info, - uint16_t table_id) +static struct table_info * +amdgpu_discovery_get_table_info_from_bin(uint8_t *discovery_bin, + uint16_t table_id) { - struct binary_header *bhdr = - (struct binary_header *)adev->discovery.bin; + struct binary_header *bhdr = (struct binary_header *)discovery_bin; struct binary_header_v2 *bhdrv2; switch (bhdr->version_major) { case 2: - bhdrv2 = (struct binary_header_v2 *)adev->discovery.bin; - *info = &bhdrv2->table_list[table_id]; - break; + bhdrv2 = (struct binary_header_v2 *)discovery_bin; + return &bhdrv2->table_list[table_id]; case 1: case 0: - *info = &bhdr->table_list[table_id]; - break; + return &bhdr->table_list[table_id]; default: - dev_err(adev->dev, "Invalid ip discovery table version %d\n",bhdr->version_major); + return NULL; + } +} + +static int amdgpu_discovery_get_table_info(struct amdgpu_device *adev, + struct table_info **info, + uint16_t table_id) +{ + struct binary_header *bhdr = + (struct binary_header *)adev->discovery.bin; + + *info = amdgpu_discovery_get_table_info_from_bin(adev->discovery.bin, + table_id); + if (!*info) { + dev_err(adev->dev, "Invalid ip discovery table version %d\n", + bhdr->version_major); return -EINVAL; } @@ -728,7 +761,9 @@ static void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev); void amdgpu_discovery_fini(struct amdgpu_device *adev) { - amdgpu_discovery_sysfs_fini(adev); + if (adev->discovery.ip_top && !adev->discovery.ip_top->standalone_mode) + amdgpu_discovery_sysfs_fini(adev); + kfree(adev->discovery.bin); adev->discovery.bin = NULL; } @@ -737,15 +772,17 @@ static int amdgpu_discovery_validate_ip(struct amdgpu_device *adev, uint8_t instance, uint16_t hw_id) { if (instance >= HWIP_MAX_INSTANCE) { - dev_err(adev->dev, - "Unexpected instance_number (%d) from ip discovery blob\n", - instance); + if (adev) + dev_err(adev->dev, + "Unexpected instance_number (%d) from ip discovery blob\n", + instance); return -EINVAL; } if (hw_id >= HW_ID_MAX) { - dev_err(adev->dev, - "Unexpected hw_id (%d) from ip discovery blob\n", - hw_id); + if (adev) + dev_err(adev->dev, + "Unexpected hw_id (%d) from ip discovery blob\n", + hw_id); return -EINVAL; } @@ -1111,12 +1148,6 @@ static const struct kobj_type ip_discovery_ktype = { .sysfs_ops = &kobj_sysfs_ops, }; -struct ip_discovery_top { - struct kobject kobj; /* ip_discovery/ */ - struct kset die_kset; /* ip_discovery/die/, contains ip_die_entry */ - struct amdgpu_device *adev; -}; - static void die_kobj_release(struct kobject *kobj) { struct ip_discovery_top *ip_top = container_of(to_kset(kobj), @@ -1132,8 +1163,14 @@ static void ip_disc_release(struct kobject *kobj) kobj); struct amdgpu_device *adev = ip_top->adev; + /* In standalone mode, discovery_bin is managed by devm and will be + * freed automatically when the PCI device is removed. Do not manually + * free it here to avoid double-free. + */ + kfree(ip_top); - adev->discovery.ip_top = NULL; + if (adev) + adev->discovery.ip_top = NULL; } static uint8_t amdgpu_discovery_get_harvest_info(struct amdgpu_device *adev, @@ -1141,6 +1178,10 @@ static uint8_t amdgpu_discovery_get_harvest_info(struct amdgpu_device *adev, { uint8_t harvest = 0; + /* In early init mode (adev == NULL), harvest info is not available */ + if (!adev) + return 0; + /* Until a uniform way is figured, get mask based on hwid */ switch (hw_id) { case VCN_HWID: @@ -1169,11 +1210,14 @@ static uint8_t amdgpu_discovery_get_harvest_info(struct amdgpu_device *adev, } static int amdgpu_discovery_sysfs_ips(struct amdgpu_device *adev, + struct ip_discovery_top *ip_top, struct ip_die_entry *ip_die_entry, const size_t _ip_offset, const int num_ips, bool reg_base_64) { - uint8_t *discovery_bin = adev->discovery.bin; + uint8_t *discovery_bin = ip_top->standalone_mode ? + ip_top->discovery_bin : + adev->discovery.bin; int ii, jj, kk, res; uint16_t hw_id; uint8_t inst; @@ -1270,10 +1314,12 @@ next_ip: return 0; } -static int amdgpu_discovery_sysfs_recurse(struct amdgpu_device *adev) +static int amdgpu_discovery_sysfs_recurse(struct amdgpu_device *adev, + struct ip_discovery_top *ip_top) { - struct ip_discovery_top *ip_top = adev->discovery.ip_top; - uint8_t *discovery_bin = adev->discovery.bin; + uint8_t *discovery_bin = ip_top->standalone_mode ? + ip_top->discovery_bin : + adev->discovery.bin; struct table_info *info; struct ip_discovery_header *ihdr; struct die_header *dhdr; @@ -1282,9 +1328,10 @@ static int amdgpu_discovery_sysfs_recurse(struct amdgpu_device *adev) size_t ip_offset; int ii, res; - res = amdgpu_discovery_get_table_info(adev, &info, IP_DISCOVERY); - if (res) - return res; + info = amdgpu_discovery_get_table_info_from_bin(discovery_bin, + IP_DISCOVERY); + if (!info) + return -EINVAL; ihdr = (struct ip_discovery_header *)(discovery_bin + le16_to_cpu(info->offset)); @@ -1322,7 +1369,8 @@ static int amdgpu_discovery_sysfs_recurse(struct amdgpu_device *adev) return res; } - amdgpu_discovery_sysfs_ips(adev, ip_die_entry, ip_offset, num_ips, !!ihdr->base_addr_64_bit); + amdgpu_discovery_sysfs_ips(adev, ip_top, ip_die_entry, ip_offset, + num_ips, !!ihdr->base_addr_64_bit); } return 0; @@ -1338,12 +1386,30 @@ static int amdgpu_discovery_sysfs_init(struct amdgpu_device *adev) if (!discovery_bin) return -EINVAL; + /* If early init already created sysfs in standalone mode, skip normal init */ + if (adev->discovery.ip_top && adev->discovery.ip_top->standalone_mode) + return 0; + ip_top = kzalloc_obj(*ip_top); if (!ip_top) return -ENOMEM; ip_top->adev = adev; - adev->discovery.ip_top = ip_top; + + /* Check if ip_discovery already exists before creating. + * This shouldn't normally happen but handle it gracefully. + */ + if (adev->dev->kobj.sd) { + struct kernfs_node *existing; + + existing = kernfs_find_and_get(adev->dev->kobj.sd, "ip_discovery"); + if (existing) { + kernfs_put(existing); + kfree(ip_top); + return 0; + } + } + res = kobject_init_and_add(&ip_top->kobj, &ip_discovery_ktype, &adev->dev->kobj, "ip_discovery"); if (res) { @@ -1351,6 +1417,8 @@ static int amdgpu_discovery_sysfs_init(struct amdgpu_device *adev) goto Err; } + adev->discovery.ip_top = ip_top; + die_kset = &ip_top->die_kset; kobject_set_name(&die_kset->kobj, "%s", "die"); die_kset->kobj.parent = &ip_top->kobj; @@ -1365,7 +1433,7 @@ static int amdgpu_discovery_sysfs_init(struct amdgpu_device *adev) ip_hw_instance_attrs[ii] = &ip_hw_attr[ii].attr; ip_hw_instance_attrs[ii] = NULL; - res = amdgpu_discovery_sysfs_recurse(adev); + res = amdgpu_discovery_sysfs_recurse(adev, ip_top); return res; Err: @@ -1479,6 +1547,150 @@ void amdgpu_discovery_dump(struct amdgpu_device *adev, struct drm_printer *p) spin_unlock(&die_kset->list_lock); } +int amdgpu_discovery_sysfs_early_init(struct amdgpu_device *adev, struct pci_dev *pdev) +{ + struct ip_discovery_top *ip_top; + struct early_ip_discovery *early_entry, *tmp; + struct kset *die_kset; + uint8_t *discovery_bin; + int res, ii; + + if (!adev || !adev->discovery.bin) + return -EINVAL; + + if (adev->discovery.ip_top) + return 0; + + mutex_lock(&early_ip_discovery_mutex); + list_for_each_entry_safe(early_entry, tmp, &early_ip_discovery_list, list) { + if (early_entry->pdev == pdev) { + adev->discovery.ip_top = early_entry->ip_top; + early_entry->ip_top->adev = adev; + mutex_unlock(&early_ip_discovery_mutex); + return 0; + } + } + mutex_unlock(&early_ip_discovery_mutex); + + discovery_bin = adev->discovery.bin; + + early_entry = kzalloc(sizeof(*early_entry), GFP_KERNEL); + if (!early_entry) + return -ENOMEM; + + ip_top = kzalloc(sizeof(*ip_top), GFP_KERNEL); + if (!ip_top) { + kfree(early_entry); + return -ENOMEM; + } + + ip_top->discovery_bin = devm_kmemdup(&pdev->dev, discovery_bin, + DISCOVERY_TMR_SIZE, GFP_KERNEL); + if (!ip_top->discovery_bin) { + kfree(ip_top); + kfree(early_entry); + return -ENOMEM; + } + + ip_top->bin_size = DISCOVERY_TMR_SIZE; + ip_top->pdev = pdev; + ip_top->adev = adev; + ip_top->standalone_mode = true; + + /* Check if ip_discovery already exists (from previous probe attempt). + * This can happen if the module was unloaded and reloaded but the + * sysfs persisted (tied to PCI device lifetime). + */ + if (pdev->dev.kobj.sd) { + struct kernfs_node *existing; + + existing = kernfs_find_and_get(pdev->dev.kobj.sd, "ip_discovery"); + if (existing) { + kernfs_put(existing); + kfree(ip_top); + kfree(early_entry); + return 0; + } + } + + res = kobject_init_and_add(&ip_top->kobj, &ip_discovery_ktype, + &pdev->dev.kobj, "ip_discovery"); + if (res) + goto err_put_kobj; + + adev->discovery.ip_top = ip_top; + + die_kset = &ip_top->die_kset; + kobject_set_name(&die_kset->kobj, "%s", "die"); + die_kset->kobj.parent = &ip_top->kobj; + die_kset->kobj.ktype = &die_kobj_ktype; + res = kset_register(&ip_top->die_kset); + if (res) + goto err_put_die_kset; + + for (ii = 0; ii < ARRAY_SIZE(ip_hw_attr); ii++) + ip_hw_instance_attrs[ii] = &ip_hw_attr[ii].attr; + ip_hw_instance_attrs[ii] = NULL; + + res = amdgpu_discovery_sysfs_recurse(NULL, ip_top); + if (res) + goto err_put_die_kset; + + early_entry->pdev = pdev; + early_entry->ip_top = ip_top; + mutex_lock(&early_ip_discovery_mutex); + list_add(&early_entry->list, &early_ip_discovery_list); + mutex_unlock(&early_ip_discovery_mutex); + + return 0; + +err_put_die_kset: + kobject_put(&ip_top->die_kset.kobj); +err_put_kobj: + kobject_put(&ip_top->kobj); + kfree(early_entry); + adev->discovery.ip_top = NULL; + return res; +} + +void amdgpu_discovery_sysfs_early_fini(struct pci_dev *pdev) +{ + struct early_ip_discovery *entry, *tmp_entry; + struct ip_discovery_top *ip_top = NULL; + struct list_head *el, *tmp; + struct kset *die_kset; + + /* Find the entry in our tracking list */ + mutex_lock(&early_ip_discovery_mutex); + list_for_each_entry_safe(entry, tmp_entry, &early_ip_discovery_list, list) { + if (entry->pdev == pdev) { + ip_top = entry->ip_top; + list_del(&entry->list); + kfree(entry); + break; + } + } + mutex_unlock(&early_ip_discovery_mutex); + + if (!ip_top) + return; + + /* Clean up sysfs hierarchy */ + die_kset = &ip_top->die_kset; + + spin_lock(&die_kset->list_lock); + list_for_each_prev_safe(el, tmp, &die_kset->list) { + list_del_init(el); + spin_unlock(&die_kset->list_lock); + amdgpu_discovery_sysfs_die_free(to_ip_die_entry(list_to_kobj(el))); + spin_lock(&die_kset->list_lock); + } + spin_unlock(&die_kset->list_lock); + + kobject_put(&ip_top->die_kset.kobj); + kobject_put(&ip_top->kobj); + /* ip_top itself will be freed by kobject_put via ip_disc_release */ +} /* ================================================== */ @@ -1504,6 +1716,9 @@ static int amdgpu_discovery_reg_base_init(struct amdgpu_device *adev) r = amdgpu_discovery_init(adev); if (r) return r; + + amdgpu_discovery_sysfs_early_init(adev, adev->pdev); + discovery_bin = adev->discovery.bin; wafl_ver = 0; adev->gfx.xcc_mask = 0; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h index e0010f6a3eda..edc78184e0f3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h @@ -53,4 +53,9 @@ int amdgpu_discovery_get_gc_major_minor_version(struct amdgpu_device *adev, void amdgpu_discovery_dump(struct amdgpu_device *adev, struct drm_printer *p); +/* Early sysfs functions for persistent ip_discovery export */ +int amdgpu_discovery_sysfs_early_init(struct amdgpu_device *adev, + struct pci_dev *pdev); +void amdgpu_discovery_sysfs_early_fini(struct pci_dev *pdev); + #endif /* __AMDGPU_DISCOVERY__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index b4120207bfa0..65f2de86fdd2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2557,6 +2557,8 @@ amdgpu_pci_remove(struct pci_dev *pdev) amdgpu_driver_unload_kms(dev); + amdgpu_discovery_sysfs_early_fini(pdev); + /* * Flush any in flight DMA operations from device. * Clear the Bus Master Enable bit and then wait on the PCIe Device -- cgit v1.2.3 From 2ad08b9c798d4d255f5218428e55f81217dbc5a4 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Fri, 5 Jun 2026 12:40:31 +0200 Subject: drm/amd/display: Simplify data output in ips_status_show() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the specification for a line break from a seq_puts() call to a previous seq_printf() call. This issue was detected by using the Coccinelle software. Reviewed-by: Timur Kristóf Signed-off-by: Markus Elfring Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 2d455359fdb4..133f3af0e4e3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -2710,11 +2710,10 @@ static int ips_status_show(struct seq_file *m, void *unused) rcg_count = ips_fw->rcg_exit_count; ips1_count = ips_fw->ips1_exit_count; ips2_count = ips_fw->ips2_exit_count; - seq_printf(m, "exit counts: rcg=%u ips1=%u ips2=%u", + seq_printf(m, "exit counts: rcg=%u ips1=%u ips2=%u\n", rcg_count, ips1_count, ips2_count); - seq_puts(m, "\n"); } return 0; } -- cgit v1.2.3 From 99e37b3ba8c8284b2b6a9c5a5fe6a511e486352a Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Fri, 5 Jun 2026 12:44:01 +0200 Subject: drm/amd/display: Use seq_putc() in three functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single characters should occasionally be put into a sequence. Thus use the corresponding function “seq_putc”. The source code was transformed by using the Coccinelle software. Reviewed-by: Timur Kristóf Signed-off-by: Markus Elfring Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 133f3af0e4e3..830cf8da06b4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -606,7 +606,7 @@ static int dp_lttpr_status_show(struct seq_file *m, void *unused) break; } - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } @@ -1081,7 +1081,7 @@ static int psr_capability_show(struct seq_file *m, void *data) seq_printf(m, "Driver support: %s", str_yes_no(link->psr_settings.psr_feature_enabled)); if (link->psr_settings.psr_version) seq_printf(m, " [0x%02x]", link->psr_settings.psr_version); - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } @@ -1266,7 +1266,7 @@ static int hdcp_sink_capability_show(struct seq_file *m, void *data) if (!hdcp_cap && !hdcp2_cap) seq_printf(m, "%s ", "None"); - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } -- cgit v1.2.3 From 6322d278a298e2c1430b9d2697743d3a04b788b1 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 12 Jun 2026 21:22:04 -0500 Subject: drm/amdkfd: fix list_del corruption in kfd_criu_resume_svm The cleanup tail of kfd_criu_resume_svm() walks svms->criu_svm_metadata_list and kfree()s each struct criu_svm_metadata without removing it from the list. The list head is left pointing at freed kmalloc-96 objects. A second AMDKFD_IOC_CRIU_OP from the same process re-enters: list_empty() reads the dangling ->next (use-after-free), the loop walks freed entries, and each is kfree()'d again (double-free). This is reachable by an unprivileged render-group user via /dev/kfd with no capabilities required. Add list_del() before the kfree() so the list is properly emptied. The list_for_each_entry_safe() iterator already caches the next pointer, so unlinking during the walk is safe. Fixes: 2a909ae71871 ("drm/amdkfd: CRIU resume shared virtual memory ranges") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index d64d104783d4..5a56d86b3ecf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -4115,6 +4115,7 @@ exit: list_for_each_entry_safe(criu_svm_md, next, &svms->criu_svm_metadata_list, list) { pr_debug("freeing criu_svm_md[]\n\tstart: 0x%llx\n", criu_svm_md->data.start_addr); + list_del(&criu_svm_md->list); kfree(criu_svm_md); } -- cgit v1.2.3 From c18cd8d6e008ab7d5bda784f583772bed8dbf5ca Mon Sep 17 00:00:00 2001 From: Ruijing Dong Date: Fri, 12 Jun 2026 14:39:31 -0400 Subject: drm/amdgpu: enumerate UMSCH HW IP This part enumerates a UMSCH block under hardware id (22) at version 2.2.0 rather than under VCN. Add the UMSCH hardware id, an IP enum slot, and the discovery name/map entries so it is recognized. No IP block is wired up yet; this only makes the IP discoverable. The multimedia IP setup assumed VCN/VCE/UVD was always present; handle the case where it is absent so init does not fail with -EINVAL. Acked-by: Alex Deucher Reviewed-by: Boyuan Zhang Signed-off-by: Ruijing Dong Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 11 +++++++++-- drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h | 1 + drivers/gpu/drm/amd/include/soc15_hw_ip.h | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 322c55aaf15f..ba2f15d12751 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -63,6 +63,7 @@ const char *hw_ip_names[MAX_HWIP] = { [VCN1_HWIP] = "VCN1", [VCE_HWIP] = "VCE", [VPE_HWIP] = "VPE", + [UMSCH_HWIP] = "UMSCH", [DF_HWIP] = "DF", [DCE_HWIP] = "DCE", [OSSSYS_HWIP] = "OSSSYS", diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index b844f4a9f0c6..5b67941ecc47 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -247,6 +247,7 @@ static const char *hw_id_names[HW_ID_MAX] = { [XGBE_HWID] = "XGBE", [MP0_HWID] = "MP0", [VPE_HWID] = "VPE", + [UMSCH_HWID] = "UMSCH", [ATU_HWID] = "ATU", [AIGC_HWID] = "AIGC", }; @@ -279,6 +280,7 @@ static int hw_id_map[MAX_HWIP] = { [DCI_HWIP] = DCI_HWID, [PCIE_HWIP] = PCIE_HWID, [VPE_HWIP] = VPE_HWID, + [UMSCH_HWIP] = UMSCH_HWID, [ISP_HWIP] = ISP_HWID, [ATU_HWIP] = ATU_HWID, }; @@ -2845,7 +2847,12 @@ static int amdgpu_discovery_set_mm_ip_blocks(struct amdgpu_device *adev) return -EINVAL; } } else { - switch (amdgpu_ip_version(adev, UVD_HWIP, 0)) { + uint32_t vcn_version = amdgpu_ip_version(adev, UVD_HWIP, 0); + + /* no VCN discovered; nothing to add */ + if (!vcn_version) + return 0; + switch (vcn_version) { case IP_VERSION(1, 0, 0): case IP_VERSION(1, 0, 1): amdgpu_device_ip_block_add(adev, &vcn_v1_0_ip_block); @@ -2913,7 +2920,7 @@ static int amdgpu_discovery_set_mm_ip_blocks(struct amdgpu_device *adev) default: dev_err(adev->dev, "Failed to add vcn/jpeg ip block(UVD_HWIP:0x%x)\n", - amdgpu_ip_version(adev, UVD_HWIP, 0)); + vcn_version); return -EINVAL; } } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h index 1d0df6d93957..590ad82f115e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h @@ -68,6 +68,7 @@ enum amd_hw_ip_block_type { ISP_HWIP, ATU_HWIP, AIGC_HWIP, + UMSCH_HWIP, MAX_HWIP }; diff --git a/drivers/gpu/drm/amd/include/soc15_hw_ip.h b/drivers/gpu/drm/amd/include/soc15_hw_ip.h index a20e59584dde..60f588dd0130 100644 --- a/drivers/gpu/drm/amd/include/soc15_hw_ip.h +++ b/drivers/gpu/drm/amd/include/soc15_hw_ip.h @@ -44,6 +44,7 @@ #define SDPMUX_HWID 19 #define NTB_HWID 20 #define VPE_HWID 21 +#define UMSCH_HWID 22 #define IOHC_HWID 24 #define L2IMU_HWID 28 #define VCE_HWID 32 -- cgit v1.2.3 From 7dba3e10ecdeec85208e255853fcd3890880b10e Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 8 Jun 2026 16:22:35 -0300 Subject: drm/amdgpu: initialize irq.lock spinlock earlier If there is an early failure during amdgpu probe, like missing firmware, it will end up calling amdgpu_irq_disable_all, which takes irq.lock spinlock without it being initialized. Initializing irq.lock earlier at amdgpu_device_init fixes the issue. [ 79.334079] INFO: trying to register non-static key. [ 79.334081] The code is fine but needs lockdep annotation, or maybe [ 79.334083] you didn't initialize this object before use? [ 79.334084] turning off the locking correctness validator. [ 79.334088] CPU: 2 UID: 0 PID: 1819 Comm: bash Not tainted 7.1.0-rc5-gfd06300b2348 #96 PREEMPT 8e8f461221633dae3c832d6689eaf0546c0ed4cd [ 79.334092] Hardware name: Valve Jupiter/Jupiter, BIOS F7A0133 08/05/2024 [ 79.334094] Call Trace: [ 79.334095] [ 79.334097] dump_stack_lvl+0x5d/0x80 [ 79.334103] register_lock_class+0x7af/0x7c0 [ 79.334109] __lock_acquire+0x416/0x2610 [ 79.334114] lock_acquire+0xcf/0x310 [ 79.334117] ? amdgpu_irq_disable_all+0x3b/0xf0 [amdgpu c88bab43d391d519ad0d5c8e5a099b4aceefa180] [ 79.334503] ? _raw_spin_lock_irqsave+0x53/0x60 [ 79.334508] _raw_spin_lock_irqsave+0x3f/0x60 [ 79.334510] ? amdgpu_irq_disable_all+0x3b/0xf0 [amdgpu c88bab43d391d519ad0d5c8e5a099b4aceefa180] [ 79.334881] amdgpu_irq_disable_all+0x3b/0xf0 [amdgpu c88bab43d391d519ad0d5c8e5a099b4aceefa180] [ 79.335240] amdgpu_device_fini_hw+0x90/0x32c [amdgpu c88bab43d391d519ad0d5c8e5a099b4aceefa180] [ 79.335704] amdgpu_driver_load_kms.cold+0x22/0x44 [amdgpu c88bab43d391d519ad0d5c8e5a099b4aceefa180] [ 79.336159] amdgpu_pci_probe+0x204/0x440 [amdgpu c88bab43d391d519ad0d5c8e5a099b4aceefa180] [ 79.336494] local_pci_probe+0x3c/0x80 [ 79.336500] pci_call_probe+0x55/0x2e0 [ 79.336505] ? _raw_spin_unlock+0x2d/0x50 [ 79.336508] ? pci_match_device+0x157/0x180 [ 79.336512] pci_device_probe+0x9b/0x170 [ 79.336516] really_probe+0xd5/0x370 [ 79.336521] __driver_probe_device+0x84/0x150 [ 79.336525] device_driver_attach+0x47/0xb0 [ 79.336528] bind_store+0x73/0xc0 [ 79.336531] kernfs_fop_write_iter+0x176/0x250 [ 79.336536] vfs_write+0x24d/0x560 [ 79.336542] ksys_write+0x71/0xe0 [ 79.336546] do_syscall_64+0x122/0x710 [ 79.336550] ? do_syscall_64+0xd1/0x710 [ 79.336553] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 79.336557] RIP: 0033:0x7f92fd675006 [ 79.336561] Code: 5d e8 41 8b 93 08 03 00 00 59 5e 48 83 f8 fc 75 19 83 e2 39 83 fa 08 75 11 e8 26 ff ff ff 66 0f 1f 44 00 00 48 8b 45 10 0f 05 <48> 8b 5d f8 c9 c3 0f 1f 40 00 f3 0f 1e fa 55 48 89 e5 48 83 ec 08 [ 79.336562] RSP: 002b:00007ffe4fa867a0 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 [ 79.336565] RAX: ffffffffffffffda RBX: 000000000000000d RCX: 00007f92fd675006 [ 79.336567] RDX: 000000000000000d RSI: 000055b2dfce59b0 RDI: 0000000000000001 [ 79.336568] RBP: 00007ffe4fa867c0 R08: 0000000000000000 R09: 0000000000000000 [ 79.336569] R10: 0000000000000000 R11: 0000000000000202 R12: 000000000000000d [ 79.336570] R13: 000055b2dfce59b0 R14: 00007f92fd7ca5c0 R15: 000055b2dfdbaf70 [ 79.336574] Fixes: 9950cda2a018 ("drm/amdgpu: drop the drm irq pre/post/un install callbacks") Reviewed-by: Tvrtko Ursulin Signed-off-by: Thadeu Lima de Souza Cascardo Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 2 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 0fa2ce36c2ea..211d30f03d25 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3771,6 +3771,8 @@ int amdgpu_device_init(struct amdgpu_device *adev, mutex_init(&adev->gfx.workload_profile_mutex); mutex_init(&adev->vcn.workload_profile_mutex); + spin_lock_init(&adev->irq.lock); + amdgpu_device_init_apu_flags(adev); r = amdgpu_device_check_arguments(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c index 254a4e983f40..40b8506ac66f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -309,8 +309,6 @@ int amdgpu_irq_init(struct amdgpu_device *adev) unsigned int irq, flags; int r; - spin_lock_init(&adev->irq.lock); - /* Enable MSI if not disabled by module parameter */ adev->irq.msi_enabled = false; -- cgit v1.2.3 From ce8b04960aebd898223caef775b43aad1816043e Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Mon, 15 Jun 2026 12:06:42 -0400 Subject: drm/amdkfd: Limit queue reset support on gfx9 For gfx9, queue reset is supported on gfx 9.4.3 and above. Signed-off-by: Amber Lin Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 3c67066e6657..01bae6e27423 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2013,7 +2013,8 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_MEMORY_OPERATIONS_SUPPORTED; - if (!amdgpu_sriov_vf(dev->gpu->adev)) + if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(9, 4, 3) && + !amdgpu_sriov_vf(dev->gpu->adev)) dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; } else { -- cgit v1.2.3 From 528b19377affc1cc7362a70a254c1dda793595f9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 12 Jun 2026 21:11:53 -0500 Subject: drm/amdgpu: check amdgpu_vm_bo_find() result in GET_MAPPING_INFO The AMDGPU_GEM_OP_GET_MAPPING_INFO path of amdgpu_gem_op_ioctl() looks up the bo_va for the buffer object in the caller's VM via amdgpu_vm_bo_find(), but uses the returned pointer without checking it. amdgpu_vm_bo_find() returns NULL when the BO has no bo_va in that VM, which is the normal case for a BO that has never been mapped. The result is fed straight into amdgpu_vm_bo_va_for_each_valid_mapping(), which expands to list_for_each_entry(mapping, &(bo_va)->valids, list) and dereferences bo_va, causing a NULL pointer dereference. This is reachable by any process able to issue the ioctl (render group) simply by requesting mapping info for an unmapped BO. Return -ENOENT when no bo_va is found, jumping to out_exec so the drm_exec context and GEM object reference are released. Fixes: 4d82724f7f2b ("drm/amdgpu: Add mapping info option for GEM_OP ioctl") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 212c14d99f6b..76da3f932f24 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -1094,6 +1094,11 @@ int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data, * If that number is larger than the size of the array, the ioctl must * be retried. */ + if (!bo_va) { + r = -ENOENT; + goto out_exec; + } + if (args->num_entries > INT_MAX / sizeof(*vm_entries)) { r = -EINVAL; goto out_exec; -- cgit v1.2.3 From 7f61b2eef7415eccdb40850aca0de94211948657 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 12 Jun 2026 21:07:24 -0500 Subject: drm/amdgpu: validate CP_GFX_SHADOW chunk size in CS pass1 Add a minimum-length check for the AMDGPU_CHUNK_ID_CP_GFX_SHADOW chunk in amdgpu_cs_pass1(), matching the gate already present for the IB, FENCE and BO_HANDLES chunk types. The CP_GFX_SHADOW case previously shared a bare break with the dependency and syncobj chunk types, which do not dereference a fixed-size struct. When userspace submits this chunk with length_dw == 0, vmemdup_array_user() is called with size 0 and returns ZERO_SIZE_PTR, which passes the IS_ERR() check. amdgpu_cs_p2_shadow() then dereferences chunk->kdata as a struct drm_amdgpu_cs_chunk_cp_gfx_shadow (reading shadow->flags), faulting on the ZERO_SIZE_PTR and causing a NULL-pointer dereference. This is reachable by an unprivileged process in the render group. Reject undersized chunks with -EINVAL during pass1 so the bad submission is rejected before pass2 ever dereferences the data. Fixes: ac9287055ff1 ("drm/amdgpu: add gfx shadow CS IOCTL support") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 115b134b4cd1..c2e6495a28bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -247,13 +247,17 @@ static int amdgpu_cs_pass1(struct amdgpu_cs_parser *p, goto free_partial_kdata; break; + case AMDGPU_CHUNK_ID_CP_GFX_SHADOW: + if (size < sizeof(struct drm_amdgpu_cs_chunk_cp_gfx_shadow)) + goto free_partial_kdata; + break; + case AMDGPU_CHUNK_ID_DEPENDENCIES: case AMDGPU_CHUNK_ID_SYNCOBJ_IN: case AMDGPU_CHUNK_ID_SYNCOBJ_OUT: case AMDGPU_CHUNK_ID_SCHEDULED_DEPENDENCIES: case AMDGPU_CHUNK_ID_SYNCOBJ_TIMELINE_WAIT: case AMDGPU_CHUNK_ID_SYNCOBJ_TIMELINE_SIGNAL: - case AMDGPU_CHUNK_ID_CP_GFX_SHADOW: break; default: -- cgit v1.2.3 From 674c692702341fed321720b4b92036c5934fb485 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 1 Jun 2026 23:55:53 +1000 Subject: drm/amdkfd: Fix NULL deref during sysfs teardown Move kfd_process_remove_sysfs() earlier in kfd_process_wq_release() so that all sysfs/procfs entries are removed before tearing down PDDs and dropping lead_thread. The per-process sysfs attributes are backed by struct kfd_process_device, and their show/store callbacks dereference PDD fields. Since sysfs removal waits for active callbacks to complete, removing these entries first closes a race where userspace reads sdma_* and stats_* files after PDD teardown. Previously this cleanup ran after kfd_process_destroy_pdds(), which resets p->n_pdds to 0. This meant kfd_process_remove_sysfs() could no longer walk the PDD array, so the per-PDD sysfs cleanup did not run as intended. This race caused NULL pointer dereferences observed in kfd_sdma_activity_worker and kfd_procfs_stats_show. Also harden kfd_process_remove_sysfs() against partially initialized or already-freed objects: - Check kobj_queues before removing PASID and deleting it - Guard kobj_stats and kobj_counters before use These checks prevent invalid dereferences during cleanup. Cc: Felix Kuehling Cc: Alex Deucher Signed-off-by: Geoffrey McRae Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 40 +++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index e58327c08549..9b7b00154c69 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -1214,10 +1214,12 @@ static void kfd_process_remove_sysfs(struct kfd_process *p) if (!p->kobj) return; - sysfs_remove_file(p->kobj, &p->attr_pasid); - kobject_del(p->kobj_queues); - kobject_put(p->kobj_queues); - p->kobj_queues = NULL; + if (p->kobj_queues) { + sysfs_remove_file(p->kobj, &p->attr_pasid); + kobject_del(p->kobj_queues); + kobject_put(p->kobj_queues); + p->kobj_queues = NULL; + } for (i = 0; i < p->n_pdds; i++) { pdd = p->pdds[i]; @@ -1225,17 +1227,21 @@ static void kfd_process_remove_sysfs(struct kfd_process *p) sysfs_remove_file(p->kobj, &pdd->attr_vram); sysfs_remove_file(p->kobj, &pdd->attr_sdma); - sysfs_remove_file(pdd->kobj_stats, &pdd->attr_evict); - if (pdd->dev->kfd2kgd->get_cu_occupancy) - sysfs_remove_file(pdd->kobj_stats, - &pdd->attr_cu_occupancy); - kobject_del(pdd->kobj_stats); - kobject_put(pdd->kobj_stats); - pdd->kobj_stats = NULL; + if (pdd->kobj_stats) { + sysfs_remove_file(pdd->kobj_stats, &pdd->attr_evict); + if (pdd->dev->kfd2kgd->get_cu_occupancy) + sysfs_remove_file(pdd->kobj_stats, + &pdd->attr_cu_occupancy); + kobject_del(pdd->kobj_stats); + kobject_put(pdd->kobj_stats); + pdd->kobj_stats = NULL; + } } for_each_set_bit(i, p->svms.bitmap_supported, p->n_pdds) { pdd = p->pdds[i]; + if (!pdd->kobj_counters) + continue; sysfs_remove_file(pdd->kobj_counters, &pdd->attr_faults); sysfs_remove_file(pdd->kobj_counters, &pdd->attr_page_in); @@ -1293,6 +1299,13 @@ static void kfd_process_wq_release(struct work_struct *work) kfd_debugfs_remove_process(p); + /* + * Remove the proc/sysfs entries before destroying PDDs. The removal path + * walks the PDD array and sysfs callbacks dereference PDD fields, so the + * backing data must remain valid until sysfs removal has completed. + */ + kfd_process_remove_sysfs(p); + kfd_process_kunmap_signal_bo(p); kfd_process_free_outstanding_kfd_bos(p); svm_range_list_fini(p); @@ -1306,11 +1319,6 @@ static void kfd_process_wq_release(struct work_struct *work) put_task_struct(p->lead_thread); - /* the last step is removing process entries under /sys - * to indicate the process has been terminated. - */ - kfd_process_remove_sysfs(p); - kfree(p); } -- cgit v1.2.3 From 6cfa412680fe3bfd8ff14c65f0a98924ab37f691 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Mon, 15 Jun 2026 22:36:41 +0800 Subject: drm/amdkfd: Disable queue reset on gfx11 SR-IOV VF Queue reset is not supported when running as an SR-IOV virtual function on gfx11 dGPUs. Guard HSA_CAP_PER_QUEUE_RESET_SUPPORTED with !amdgpu_sriov_vf(). so the capability is not reported to user space under SR-IOV, matching the gfx9/gfx10 path. Fixes: 9d748a8ac1ec ("drm/amdkfd: Add queue reset support on gfx11 dGPU") Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 01bae6e27423..af1249165bdb 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2021,9 +2021,10 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) dev->node_props.debug_prop |= HSA_DBG_WATCH_ADDR_MASK_LO_BIT_GFX10 | HSA_DBG_WATCH_ADDR_MASK_HI_BIT; /* gfx11 dGPU */ - if (KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 0) || - KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 2) || - KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 3)) + if ((KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 0) || + KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 2) || + KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 3)) && + !amdgpu_sriov_vf(dev->gpu->adev)) dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) { -- cgit v1.2.3 From 296ebc46de22f412e6bcacae99cc5cbf516cb461 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 15 Jun 2026 18:58:09 +0800 Subject: drm/amdkfd: fix SDMA queue counter read on non-gfx9.4.3 ASICs The SDMA queue counter read was dispatched by GC version: anything newer than gfx 9.4.2 was routed to the kfd2kgd->hqd_sdma_get_counter hook. However that hook is only implemented for gfx 9.4.3, so gfx 10.3, gfx 11 and gfx 12 fell into the else branch with a NULL hook and got -EOPNOTSUPP. This spammed "Failed to read SDMA queue counter" on every SDMA queue teardown and left sdma_val at 0, so the per-process SDMA activity accounting stopped working on those ASICs. Dispatch based on whether the hook is implemented instead of the GC version, so ASICs without the hook keep using read_sdma_queue_counter() as before. Fixes: 8f09c0ec21cf ("drm/amdkfd: add sdma queue counter for gfxv9.4.3") Reviewed-by: Eric Huang Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 28 ++++++++++------------ drivers/gpu/drm/amd/amdkfd/kfd_process.c | 12 ++++------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 4ae7f4c6365e..5c9dfb0c424f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -1027,17 +1027,15 @@ static int destroy_queue_nocpsch(struct device_queue_manager *dqm, /* Get the SDMA queue stats */ if ((q->properties.type == KFD_QUEUE_TYPE_SDMA) || (q->properties.type == KFD_QUEUE_TYPE_SDMA_XGMI)) { - if (KFD_GC_VERSION(dqm->dev) <= IP_VERSION(9, 4, 2)) + if (dqm->dev->kfd2kgd->hqd_sdma_get_counter) + retval = dqm->dev->kfd2kgd->hqd_sdma_get_counter( + dqm->dev->adev, q->mqd, + dqm->dev->kfd->device_info.num_sdma_queues_per_engine, + &sdma_val); + else retval = read_sdma_queue_counter( (uint64_t __user *)q->properties.read_ptr, &sdma_val); - else - retval = dqm->dev->kfd2kgd->hqd_sdma_get_counter ? - dqm->dev->kfd2kgd->hqd_sdma_get_counter( - dqm->dev->adev, q->mqd, - dqm->dev->kfd->device_info.num_sdma_queues_per_engine, - &sdma_val) : - -EOPNOTSUPP; if (retval) dev_err(dev, "Failed to read SDMA queue counter for queue: %d\n", q->properties.queue_id); @@ -2675,17 +2673,15 @@ static int destroy_queue_cpsch(struct device_queue_manager *dqm, /* Get the SDMA queue stats */ if ((q->properties.type == KFD_QUEUE_TYPE_SDMA) || (q->properties.type == KFD_QUEUE_TYPE_SDMA_XGMI)) { - if (KFD_GC_VERSION(dqm->dev) <= IP_VERSION(9, 4, 2)) + if (dqm->dev->kfd2kgd->hqd_sdma_get_counter) + retval = dqm->dev->kfd2kgd->hqd_sdma_get_counter( + dqm->dev->adev, q->mqd, + dqm->dev->kfd->device_info.num_sdma_queues_per_engine, + &sdma_val); + else retval = read_sdma_queue_counter( (uint64_t __user *)q->properties.read_ptr, &sdma_val); - else - retval = dqm->dev->kfd2kgd->hqd_sdma_get_counter ? - dqm->dev->kfd2kgd->hqd_sdma_get_counter( - dqm->dev->adev, q->mqd, - dqm->dev->kfd->device_info.num_sdma_queues_per_engine, - &sdma_val) : - -EOPNOTSUPP; if (retval) dev_err(dev, "Failed to read SDMA queue counter for queue: %d\n", diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index 9b7b00154c69..303b2b26f1cc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -192,15 +192,13 @@ static void kfd_sdma_activity_worker(struct work_struct *work) list_for_each_entry(sdma_q, &sdma_q_list.list, list) { val = 0; - if (KFD_GC_VERSION(dqm->dev) <= IP_VERSION(9, 4, 2)) - ret = read_sdma_queue_counter(sdma_q->rptr, &val); - else - ret = dqm->dev->kfd2kgd->hqd_sdma_get_counter ? - dqm->dev->kfd2kgd->hqd_sdma_get_counter( + if (dqm->dev->kfd2kgd->hqd_sdma_get_counter) + ret = dqm->dev->kfd2kgd->hqd_sdma_get_counter( dqm->dev->adev, sdma_q->mqd, dqm->dev->kfd->device_info.num_sdma_queues_per_engine, - &val) : - -EOPNOTSUPP; + &val); + else + ret = read_sdma_queue_counter(sdma_q->rptr, &val); if (ret) { pr_debug("Failed to read SDMA queue active counter for queue id: %d", -- cgit v1.2.3 From 473f99e4a262776141366f7fb10f101d0f22b019 Mon Sep 17 00:00:00 2001 From: Gangliang Xie Date: Tue, 16 Jun 2026 17:04:47 +0800 Subject: drm/amdgpu: add buf length check add buf length check before using it to access data Signed-off-by: Gangliang Xie Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp_ta.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp_ta.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp_ta.c index 0d3c18f04ac3..8ae72c862d11 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp_ta.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp_ta.c @@ -166,7 +166,8 @@ static ssize_t ta_if_load_debugfs_write(struct file *fp, const char *buf, size_t if (ret) return -EFAULT; - if (ta_bin_len > PSP_1_MEG) + if (ta_bin_len < sizeof(struct common_firmware_header) || + ta_bin_len > PSP_1_MEG) return -EINVAL; copy_pos += sizeof(uint32_t); @@ -321,6 +322,8 @@ static ssize_t ta_if_invoke_debugfs_write(struct file *fp, const char *buf, size ret = copy_from_user((void *)&shared_buf_len, &buf[copy_pos], sizeof(uint32_t)); if (ret) return -EFAULT; + if (!shared_buf_len || shared_buf_len > PSP_1_MEG) + return -EINVAL; copy_pos += sizeof(uint32_t); shared_buf = memdup_user(&buf[copy_pos], shared_buf_len); -- cgit v1.2.3 From 14682de8ad377bf13ea66e47c26dcfea0b19a21d Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Fri, 29 May 2026 11:47:38 +0500 Subject: drm/amdgpu: convert amdgpu_vm_lock_by_pasid() to drm_exec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_vm_lock_by_pasid() looks up a VM by PASID and reserves its root PD with a bare amdgpu_bo_reserve(), returning the still-reserved root to the caller. A caller that then needs to reserve further BOs (for example the devcoredump IB dump) ends up nesting reservation_ww_class_mutex acquires without a ww_acquire_ctx, which lockdep flags as recursive locking. Convert the helper to take a drm_exec context and lock the root PD with drm_exec_lock_obj(). Callers now run it inside a drm_exec_until_all_locked() loop and can lock additional BOs in the same ww ticket, so there is no nested ww_mutex acquire. The drm_exec context holds its own reference on the locked root BO, so the helper no longer hands a root reference back to the caller: the root output parameter is dropped, and the transient reference taken across the PASID lookup is released before returning. The only existing caller, amdgpu_vm_handle_fault(), is updated accordingly. Its is_compute_context path, which previously dropped the root reservation around svm_range_restore_pages() and re-took it, now finalises the drm_exec context and re-initialises a fresh one; behaviour is otherwise unchanged. No functional change intended for the page-fault path. Reviewed-by: Christian König Signed-off-by: Mikhail Gavrilov Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 91 +++++++++++++++++++++------------- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h | 2 +- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 7d51880b4860..fee4c94c2585 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2920,47 +2920,56 @@ int amdgpu_vm_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) } /** - * amdgpu_vm_lock_by_pasid - return an amdgpu_vm and its root bo from a pasid, if possible. + * amdgpu_vm_lock_by_pasid - look up a VM by PASID and lock its root PD * @adev: amdgpu device pointer - * @root: root BO of the VM * @pasid: PASID of the VM - * The caller needs to unreserve and unref the root bo on success. + * @exec: drm_exec context to lock the root PD in + * + * Must be called from within a drm_exec_until_all_locked() loop; the caller + * runs drm_exec_retry_on_contention() afterwards. The drm_exec context holds + * a reference on the root BO until it is finalised. + * + * Return: the VM on success, or NULL if the PASID has no VM, the VM is being + * torn down, or locking the root PD failed. */ struct amdgpu_vm *amdgpu_vm_lock_by_pasid(struct amdgpu_device *adev, - struct amdgpu_bo **root, u32 pasid) + u32 pasid, struct drm_exec *exec) { unsigned long irqflags; + 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; + root = vm ? amdgpu_bo_ref(vm->root.bo) : NULL; xa_unlock_irqrestore(&adev->vm_manager.pasids, irqflags); - if (!*root) + if (!root) return NULL; - r = amdgpu_bo_reserve(*root, true); - if (r) - goto error_unref; + r = drm_exec_lock_obj(exec, &root->tbo.base); + if (r) { + amdgpu_bo_unref(&root); + return NULL; + } /* 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) + if (vm && vm->root.bo != root) vm = NULL; xa_unlock_irqrestore(&adev->vm_manager.pasids, irqflags); - if (!vm) - goto error_unlock; + if (!vm) { + drm_exec_unlock_obj(exec, &root->tbo.base); + amdgpu_bo_unref(&root); + return NULL; + } - return vm; -error_unlock: - amdgpu_bo_unreserve(*root); + /* The drm_exec context holds its own reference on the root BO. */ + amdgpu_bo_unref(&root); -error_unref: - amdgpu_bo_unref(root); - return NULL; + return vm; } /** @@ -2982,33 +2991,49 @@ bool amdgpu_vm_handle_fault(struct amdgpu_device *adev, u32 pasid, uint64_t ts, bool write_fault) { bool is_compute_context = false; - struct amdgpu_bo *root; + struct drm_exec exec; uint64_t value, flags; struct amdgpu_vm *vm; int r; - vm = amdgpu_vm_lock_by_pasid(adev, &root, pasid); - if (!vm) + drm_exec_init(&exec, 0, 1); + drm_exec_until_all_locked(&exec) { + vm = amdgpu_vm_lock_by_pasid(adev, pasid, &exec); + drm_exec_retry_on_contention(&exec); + if (!vm) + break; + } + if (!vm) { + drm_exec_fini(&exec); return false; + } is_compute_context = vm->is_compute_context; if (is_compute_context) { - /* Unreserve root since svm_range_restore_pages might try to reserve it. */ - /* TODO: rework svm_range_restore_pages so that this isn't necessary. */ - amdgpu_bo_unreserve(root); + /* Release the root PD lock since svm_range_restore_pages + * might try to take it. + * TODO: rework svm_range_restore_pages so that this isn't + * necessary. + */ + drm_exec_fini(&exec); if (!svm_range_restore_pages(adev, pasid, vmid, - node_id, addr >> PAGE_SHIFT, ts, write_fault)) { - amdgpu_bo_unref(&root); + node_id, addr >> PAGE_SHIFT, ts, write_fault)) return true; - } - amdgpu_bo_unref(&root); /* Re-acquire the VM lock, could be that the VM was freed in between. */ - vm = amdgpu_vm_lock_by_pasid(adev, &root, pasid); - if (!vm) + drm_exec_init(&exec, 0, 1); + drm_exec_until_all_locked(&exec) { + vm = amdgpu_vm_lock_by_pasid(adev, pasid, &exec); + drm_exec_retry_on_contention(&exec); + if (!vm) + break; + } + if (!vm) { + drm_exec_fini(&exec); return false; + } } addr /= AMDGPU_GPU_PAGE_SIZE; @@ -3032,7 +3057,7 @@ bool amdgpu_vm_handle_fault(struct amdgpu_device *adev, u32 pasid, value = 0; } - r = dma_resv_reserve_fences(root->tbo.base.resv, 1); + r = dma_resv_reserve_fences(vm->root.bo->tbo.base.resv, 1); if (r) { pr_debug("failed %d to reserve fence slot\n", r); goto error_unlock; @@ -3046,12 +3071,10 @@ bool amdgpu_vm_handle_fault(struct amdgpu_device *adev, u32 pasid, r = amdgpu_vm_update_pdes(adev, vm, true); error_unlock: - amdgpu_bo_unreserve(root); + drm_exec_fini(&exec); if (r < 0) dev_err(adev->dev, "Can't handle page fault (%d)\n", r); - amdgpu_bo_unref(&root); - return false; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h index 3695299f1a03..b32f51a78cd8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h @@ -592,7 +592,7 @@ bool amdgpu_vm_handle_fault(struct amdgpu_device *adev, u32 pasid, bool write_fault); struct amdgpu_vm *amdgpu_vm_lock_by_pasid(struct amdgpu_device *adev, - struct amdgpu_bo **root, u32 pasid); + u32 pasid, struct drm_exec *exec); void amdgpu_vm_set_task_info(struct amdgpu_vm *vm); -- cgit v1.2.3 From d6bf4242731219ee08ce54c365631e395486651e Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Fri, 29 May 2026 11:47:39 +0500 Subject: drm/amdgpu: fix recursive ww_mutex acquire in amdgpu_devcoredump_format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When dumping IB contents from a hung job, amdgpu_devcoredump_format() acquired the VM root PD's reservation via amdgpu_vm_lock_by_pasid() and then, for each IB, called amdgpu_bo_reserve() on the BO backing the IB. Both reservations are reservation_ww_class_mutex objects and neither used a ww_acquire_ctx, which trips lockdep: WARNING: possible recursive locking detected -------------------------------------------- kworker/u128:0 is trying to acquire lock: ffff88838b16e1f0 (reservation_ww_class_mutex){+.+.}-{4:4}, at: amdgpu_devcoredump_format+0x1594/0x23f0 [amdgpu] but task is already holding lock: ffff8882f82681f0 (reservation_ww_class_mutex){+.+.}-{4:4}, at: amdgpu_devcoredump_format+0x1594/0x23f0 [amdgpu] Possible unsafe locking scenario: CPU0 ---- lock(reservation_ww_class_mutex); lock(reservation_ww_class_mutex); *** DEADLOCK *** May be due to missing lock nesting notation Workqueue: events_unbound amdgpu_devcoredump_deferred_work [amdgpu] Call Trace: __ww_mutex_lock.constprop.0 ww_mutex_lock amdgpu_bo_reserve amdgpu_devcoredump_format+0x1594 [amdgpu] amdgpu_devcoredump_deferred_work+0xea [amdgpu] The two reservations are on different BOs in the captured trace, so the splat is a lockdep-correctness warning, not an observed deadlock. It becomes a real self-deadlock whenever the IB BO shares its dma_resv with the root PD (the always-valid case, see amdgpu_vm_is_bo_always_valid()): amdgpu_bo_reserve(abo) re-acquires the same ww_mutex without a ticket and blocks forever. With amdgpu.gpu_recovery=0 the timeout handler refires every ~2 s and each invocation produces this splat, drowning the kernel ring buffer. Now that amdgpu_vm_lock_by_pasid() takes a drm_exec context, move the IB dumping into a separate helper that locks the root PD and every IB BO together in a single drm_exec ticket. DRM_EXEC_IGNORE_DUPLICATES handles IB BOs that share a dma_resv (e.g. always-valid BOs, or two IBs backed by the same BO). Every lock is now a top-level acquire under one ww_acquire_ctx, so the recursive ww_mutex condition is gone, and the per-IB amdgpu_bo_reserve()/amdgpu_bo_unref() dance -- including a BO refcount leak on the amdgpu_bo_reserve() failure path -- is removed. Fixes: 7b15fc2d1f1a ("drm/amdgpu: dump job ibs in the devcoredump") Suggested-by: Christian König Signed-off-by: Mikhail Gavrilov Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 215 +++++++++++++---------- 1 file changed, 126 insertions(+), 89 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index ba2f15d12751..4fd0df3aa70d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -24,6 +24,7 @@ #include #include +#include #include "amdgpu_dev_coredump.h" #include "atom.h" @@ -208,23 +209,137 @@ static void amdgpu_devcoredump_fw_info(struct amdgpu_device *adev, } } +static void +amdgpu_devcoredump_print_ibs(struct drm_printer *p, + struct amdgpu_coredump_info *coredump, + bool sizing_pass) +{ + struct amdgpu_device *adev = coredump->adev; + struct amdgpu_bo_va_mapping *mapping; + struct amdgpu_bo *abo; + struct drm_exec exec; + struct amdgpu_vm *vm; + u32 *ib_content; + u64 va_start, offset; + u8 *kptr; + u32 off; + int r; + + /* + * On the sizing pass there is no VM to look up and no BO to lock; the + * size estimate doesn't depend on whether the IB BOs are reachable. + * Just emit the per-IB headers (the content is not written anywhere). + */ + if (sizing_pass) { + for (int i = 0; i < coredump->num_ibs; i++) { + drm_printf(p, "\nIB #%d 0x%llx %d dw\n", i, + coredump->ibs[i].gpu_addr, + coredump->ibs[i].ib_size_dw); + } + return; + } + + /* + * Lock the VM root PD and every IB BO together in a single drm_exec + * ticket. Reserving the IB BOs one by one while the root PD is held + * would be a recursive reservation_ww_class_mutex acquire without a + * ww_acquire_ctx, which trips lockdep and self-deadlocks for IB BOs + * that share their dma_resv with the root PD (always-valid BOs). + */ + drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 1 + coredump->num_ibs); + drm_exec_until_all_locked(&exec) { + vm = amdgpu_vm_lock_by_pasid(adev, coredump->pasid, &exec); + if (!vm) + goto unlock; + + for (int i = 0; i < coredump->num_ibs; i++) { + u64 pfn = (coredump->ibs[i].gpu_addr & + AMDGPU_GMC_HOLE_MASK) / AMDGPU_GPU_PAGE_SIZE; + + mapping = amdgpu_vm_bo_lookup_mapping(vm, pfn); + if (!mapping) + continue; + + abo = mapping->bo_va->base.bo; + r = drm_exec_lock_obj(&exec, &abo->tbo.base); + drm_exec_retry_on_contention(&exec); + if (r) + goto unlock; + } + } + + for (int i = 0; i < coredump->num_ibs; i++) { + bool emit_content = false; + + ib_content = kvmalloc_array(coredump->ibs[i].ib_size_dw, 4, + GFP_KERNEL); + if (!ib_content) + continue; + + va_start = coredump->ibs[i].gpu_addr & AMDGPU_GMC_HOLE_MASK; + mapping = amdgpu_vm_bo_lookup_mapping(vm, + va_start / AMDGPU_GPU_PAGE_SIZE); + if (!mapping) + goto output_ib_content; + + abo = mapping->bo_va->base.bo; + offset = va_start - mapping->start * AMDGPU_GPU_PAGE_SIZE; + + if (abo->flags & AMDGPU_GEM_CREATE_NO_CPU_ACCESS) { + struct amdgpu_res_cursor cursor; + + off = 0; + + if (abo->tbo.resource->mem_type != TTM_PL_VRAM) + goto output_ib_content; + + amdgpu_res_first(abo->tbo.resource, offset, + coredump->ibs[i].ib_size_dw * 4, &cursor); + while (cursor.remaining) { + amdgpu_device_mm_access(adev, cursor.start / 4, + &ib_content[off], cursor.size / 4, + false); + off += cursor.size; + amdgpu_res_next(&cursor, cursor.size); + } + emit_content = true; + } else { + r = ttm_bo_kmap(&abo->tbo, 0, PFN_UP(abo->tbo.base.size), + &abo->kmap); + if (r) + goto output_ib_content; + + kptr = amdgpu_bo_kptr(abo); + kptr += offset; + memcpy(ib_content, kptr, coredump->ibs[i].ib_size_dw * 4); + + amdgpu_bo_kunmap(abo); + emit_content = true; + } + +output_ib_content: + drm_printf(p, "\nIB #%d 0x%llx %d dw\n", i, + coredump->ibs[i].gpu_addr, coredump->ibs[i].ib_size_dw); + if (emit_content) { + for (int j = 0; j < coredump->ibs[i].ib_size_dw; j++) + drm_printf(p, "0x%08x\n", ib_content[j]); + } + kvfree(ib_content); + } + +unlock: + drm_exec_fini(&exec); +} + static ssize_t amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_info *coredump) { - struct amdgpu_device *adev = coredump->adev; struct drm_printer p; struct drm_print_iterator iter; struct amdgpu_vm_fault_info *fault_info; - struct amdgpu_bo_va_mapping *mapping; struct amdgpu_ip_block *ip_block; - struct amdgpu_res_cursor cursor; - struct amdgpu_bo *abo, *root; - uint64_t va_start, offset; struct amdgpu_ring *ring; - struct amdgpu_vm *vm; - u32 *ib_content; - uint8_t *kptr; - int ver, i, j, r; + int ver, i, j; u32 ring_idx, off; bool sizing_pass; @@ -344,86 +459,8 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf else if (coredump->reset_vram_lost) drm_printf(&p, "VRAM is lost due to GPU reset!\n"); - if (coredump->num_ibs) { - /* Don't try to lookup the VM or map the BOs when calculating the - * size required to store the devcoredump. - */ - if (sizing_pass) - vm = NULL; - else - vm = amdgpu_vm_lock_by_pasid(adev, &root, coredump->pasid); - - for (int i = 0; i < coredump->num_ibs && (sizing_pass || vm); i++) { - ib_content = kvmalloc_array(coredump->ibs[i].ib_size_dw, 4, - GFP_KERNEL); - if (!ib_content) - continue; - - /* vm=NULL can only happen when 'sizing_pass' is true. Skip to the - * drm_printf() calls (ib_content doesn't need to be initialized - * as its content won't be written anywhere). - */ - if (!vm) - goto output_ib_content; - - va_start = coredump->ibs[i].gpu_addr & AMDGPU_GMC_HOLE_MASK; - mapping = amdgpu_vm_bo_lookup_mapping(vm, va_start / AMDGPU_GPU_PAGE_SIZE); - if (!mapping) - goto free_ib_content; - - offset = va_start - (mapping->start * AMDGPU_GPU_PAGE_SIZE); - abo = amdgpu_bo_ref(mapping->bo_va->base.bo); - r = amdgpu_bo_reserve(abo, false); - if (r) - goto free_ib_content; - - if (abo->flags & AMDGPU_GEM_CREATE_NO_CPU_ACCESS) { - off = 0; - - if (abo->tbo.resource->mem_type != TTM_PL_VRAM) - goto unreserve_abo; - - amdgpu_res_first(abo->tbo.resource, offset, - coredump->ibs[i].ib_size_dw * 4, - &cursor); - while (cursor.remaining) { - amdgpu_device_mm_access(adev, cursor.start / 4, - &ib_content[off], cursor.size / 4, - false); - off += cursor.size; - amdgpu_res_next(&cursor, cursor.size); - } - } else { - r = ttm_bo_kmap(&abo->tbo, 0, - PFN_UP(abo->tbo.base.size), - &abo->kmap); - if (r) - goto unreserve_abo; - - kptr = amdgpu_bo_kptr(abo); - kptr += offset; - memcpy(ib_content, kptr, - coredump->ibs[i].ib_size_dw * 4); - - amdgpu_bo_kunmap(abo); - } - -output_ib_content: - drm_printf(&p, "\nIB #%d 0x%llx %d dw\n", - i, coredump->ibs[i].gpu_addr, coredump->ibs[i].ib_size_dw); - for (int j = 0; j < coredump->ibs[i].ib_size_dw; j++) - drm_printf(&p, "0x%08x\n", ib_content[j]); -unreserve_abo: - if (vm) - amdgpu_bo_unreserve(abo); -free_ib_content: - kvfree(ib_content); - } - if (vm) { - amdgpu_bo_unreserve(root); - amdgpu_bo_unref(&root); - } - } + if (coredump->num_ibs) + amdgpu_devcoredump_print_ibs(&p, coredump, sizing_pass); return count - iter.remain; } -- cgit v1.2.3 From 7de02fe95312583e461c985cd8621ba46f1b1016 Mon Sep 17 00:00:00 2001 From: geomcrae_amdeng Date: Mon, 1 Jun 2026 14:50:14 +1000 Subject: drm/amdgpu: clean up discovery and preempt sysfs entries on shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a sysfs duplication error when reinitializing the device: sysfs: cannot create duplicate filename '.../ip_discovery' kobject_add_internal failed for ip_discovery with -EEXIST ... Failed to create device file mem_info_preempt_used (-17) The failure is caused by stale sysfs entries not being removed during device teardown, leading to -EEXIST when the driver is reprobed. In particular: - amdgpu_discovery sysfs kobjects were not fully torn down early enough, and ip_top remained non-NULL after cleanup - the preempt manager sysfs attribute was removed only conditionally and not during the common hw fini path Fix this by: - making amdgpu_discovery_sysfs_fini() externally visible and clearing adev->discovery.ip_top to prevent reuse - calling amdgpu_discovery_sysfs_fini() and amdgpu_preempt_mgr_sysfs_fini() from amdgpu_device_sys_interface_fini() This ensures sysfs state is fully cleaned up before reprobe and avoids duplicate kobject/file creation. Cc: Christian König Cc: Alex Deucher Signed-off-by: Geoffrey McRae Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 5 +++++ drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 5 ++--- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c | 14 +++++++++++--- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h | 1 + 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 211d30f03d25..b29b60acd8f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3677,6 +3677,10 @@ static void amdgpu_device_sys_interface_fini(struct amdgpu_device *adev) amdgpu_pm_sysfs_fini(adev); if (adev->ucode_sysfs_en) amdgpu_ucode_sysfs_fini(adev); + + amdgpu_discovery_sysfs_fini(adev); + amdgpu_preempt_mgr_sysfs_fini(adev); + amdgpu_device_attr_sysfs_fini(adev); amdgpu_fru_sysfs_fini(adev); @@ -4210,6 +4214,7 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) if (adev->mman.initialized) drain_workqueue(adev->mman.bdev.wq); + adev->shutdown = true; unregister_pm_notifier(&adev->pm_nb); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 5b67941ecc47..e0cf6848ab7c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -759,8 +759,6 @@ out: return r; } -static void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev); - void amdgpu_discovery_fini(struct amdgpu_device *adev) { if (adev->discovery.ip_top && !adev->discovery.ip_top->standalone_mode) @@ -1482,7 +1480,7 @@ static void amdgpu_discovery_sysfs_die_free(struct ip_die_entry *ip_die_entry) kobject_put(&ip_die_entry->ip_kset.kobj); } -static void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev) +void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev) { struct ip_discovery_top *ip_top = adev->discovery.ip_top; struct list_head *el, *tmp; @@ -1491,6 +1489,7 @@ static void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev) if (!ip_top) return; + adev->discovery.ip_top = NULL; die_kset = &ip_top->die_kset; spin_lock(&die_kset->list_lock); list_for_each_prev_safe(el, tmp, &die_kset->list) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h index edc78184e0f3..5b2b16f68576 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h @@ -41,6 +41,7 @@ struct amdgpu_discovery_info { bool reserve_tmr; }; +void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev); void amdgpu_discovery_fini(struct amdgpu_device *adev); int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c index b1dc33301d83..e8592970aaab 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c @@ -46,6 +46,17 @@ static ssize_t mem_info_preempt_used_show(struct device *dev, static DEVICE_ATTR_RO(mem_info_preempt_used); +/** + * amdgpu_preempt_mgr_sysfs_fini - remove PREEMPT manager sysfs attributes + * + * @adev: amdgpu_device pointer + */ +void amdgpu_preempt_mgr_sysfs_fini(struct amdgpu_device *adev) +{ + if (adev->dev->kobj.sd) + device_remove_file(adev->dev, &dev_attr_mem_info_preempt_used); +} + /** * amdgpu_preempt_mgr_new - allocate a new node * @@ -137,9 +148,6 @@ void amdgpu_preempt_mgr_fini(struct amdgpu_device *adev) if (ret) return; - if (adev->dev->kobj.sd) - device_remove_file(adev->dev, &dev_attr_mem_info_preempt_used); - ttm_resource_manager_cleanup(man); ttm_set_driver_manager(&adev->mman.bdev, AMDGPU_PL_PREEMPT, NULL); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h index 2d72fa217274..00acec7226f5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h @@ -140,6 +140,7 @@ int amdgpu_gtt_mgr_init(struct amdgpu_device *adev, uint64_t gtt_size); void amdgpu_gtt_mgr_fini(struct amdgpu_device *adev); int amdgpu_preempt_mgr_init(struct amdgpu_device *adev); void amdgpu_preempt_mgr_fini(struct amdgpu_device *adev); +void amdgpu_preempt_mgr_sysfs_fini(struct amdgpu_device *adev); int amdgpu_vram_mgr_init(struct amdgpu_device *adev); void amdgpu_vram_mgr_fini(struct amdgpu_device *adev); -- cgit v1.2.3 From f54ce9e8cbd3abe0eda3a285f54dc4f572fe589a Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Tue, 26 May 2026 22:50:02 -0500 Subject: drm/amdkfd: Let driver decide buffer size at AMDKFD_IOC_GET_DMABUF_INFO ioctl amdkfd driver needs allocate buffer to return bo metadata to user space. The buffer size is controlled by user currently. It is a potential security issue that hostile value (e.g. 2 GiB) lets any render-group user trigger order-MAX allocation/OOM in kernel context. This patch first finds bo metadata size. If the size is smaller than user provided value drive can safely allocate buffer in kernel space and copy to user space buffer. If not, driver will let user know, not allocate and copy. User will redo with new buffer in user space. This patch lets driver decide buffer allocation size to avoid potential hostile size from user space. Signed-off-by: Xiaogang Chen Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 23 +++++++++++++++++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 10 ++-------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index f25759962e0c..c693c508df1a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -558,7 +558,7 @@ uint32_t amdgpu_amdkfd_get_max_engine_clock_in_mhz(struct amdgpu_device *adev) int amdgpu_amdkfd_get_dmabuf_info(struct amdgpu_device *adev, int dma_buf_fd, struct amdgpu_device **dmabuf_adev, - uint64_t *bo_size, void *metadata_buffer, + uint64_t *bo_size, void **metadata_buffer, size_t buffer_size, uint32_t *metadata_size, uint32_t *flags, int8_t *xcp_id) { @@ -593,9 +593,24 @@ int amdgpu_amdkfd_get_dmabuf_info(struct amdgpu_device *adev, int dma_buf_fd, *dmabuf_adev = adev; if (bo_size) *bo_size = amdgpu_bo_size(bo); - if (metadata_buffer) - r = amdgpu_bo_get_metadata(bo, metadata_buffer, buffer_size, - metadata_size, &metadata_flags); + if (metadata_buffer) { + /* first get metadata_size by buffer = NULL */ + r = amdgpu_bo_get_metadata(bo, NULL, 0, + metadata_size, NULL); + + /* user buf_size is bigger than bo metadata_size + * allocate a buf at kernel space and copy */ + if (*metadata_size <= buffer_size) { + *metadata_buffer = kzalloc(*metadata_size, GFP_KERNEL); + + if (!*metadata_buffer) + return -ENOMEM; + + r = amdgpu_bo_get_metadata(bo, *metadata_buffer, *metadata_size, + NULL, &metadata_flags); + } else + r = -EINVAL; + } if (flags) { *flags = (bo->preferred_domains & AMDGPU_GEM_DOMAIN_VRAM) ? KFD_IOC_ALLOC_MEM_FLAGS_VRAM diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index 32132be6e683..5b49fa50a47d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -268,7 +268,7 @@ uint64_t amdgpu_amdkfd_get_gpu_clock_counter(struct amdgpu_device *adev); uint32_t amdgpu_amdkfd_get_max_engine_clock_in_mhz(struct amdgpu_device *adev); int amdgpu_amdkfd_get_dmabuf_info(struct amdgpu_device *adev, int dma_buf_fd, struct amdgpu_device **dmabuf_adev, - uint64_t *bo_size, void *metadata_buffer, + uint64_t *bo_size, void **metadata_buffer, size_t buffer_size, uint32_t *metadata_size, uint32_t *flags, int8_t *xcp_id); int amdgpu_amdkfd_get_pcie_bandwidth_mbytes(struct amdgpu_device *adev, bool is_min); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 734a5a2a251f..d41773aeb49c 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1562,16 +1562,10 @@ static int kfd_ioctl_get_dmabuf_info(struct file *filep, if (!dev) return -EINVAL; - if (args->metadata_ptr) { - metadata_buffer = kzalloc(args->metadata_size, GFP_KERNEL); - if (!metadata_buffer) - return -ENOMEM; - } - /* Get dmabuf info from KGD */ r = amdgpu_amdkfd_get_dmabuf_info(dev->adev, args->dmabuf_fd, &dmabuf_adev, &args->size, - metadata_buffer, args->metadata_size, + &metadata_buffer, args->metadata_size, &args->metadata_size, &flags, &xcp_id); if (r) goto exit; @@ -1583,7 +1577,7 @@ static int kfd_ioctl_get_dmabuf_info(struct file *filep, args->flags = flags; /* Copy metadata buffer to user mode */ - if (metadata_buffer) { + if (metadata_buffer && args->metadata_ptr) { r = copy_to_user((void __user *)args->metadata_ptr, metadata_buffer, args->metadata_size); if (r != 0) -- cgit v1.2.3 From 2664ce9143d174651a793d96a6a2326050c4f45a Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Tue, 16 Jun 2026 12:54:49 -0500 Subject: drm/amdkfd: check find_first_zero_bit before __set_bit on kfd->doorbell_bitmap If inx from find_first_zero_bit is beyond range not need set doorbell_bitmap. Signed-off-by: Xiaogang Chen Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c b/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c index 05c74887fd6f..fdcf7f2d1b5b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c @@ -153,14 +153,16 @@ void __iomem *kfd_get_kernel_doorbell(struct kfd_dev *kfd, u32 inx; mutex_lock(&kfd->doorbell_mutex); + inx = find_first_zero_bit(kfd->doorbell_bitmap, PAGE_SIZE / sizeof(u32)); + if (inx >= KFD_MAX_NUM_OF_QUEUES_PER_PROCESS) { + mutex_unlock(&kfd->doorbell_mutex); + return NULL; + } __set_bit(inx, kfd->doorbell_bitmap); mutex_unlock(&kfd->doorbell_mutex); - if (inx >= KFD_MAX_NUM_OF_QUEUES_PER_PROCESS) - return NULL; - *doorbell_off = amdgpu_doorbell_index_on_bar(kfd->adev, kfd->doorbells, inx, -- cgit v1.2.3 From 4eca4742eb215951f9739ffe0122d179d545a7a4 Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Tue, 16 Jun 2026 13:25:56 -0500 Subject: drm/amdkfd: Use memdup_array_user to copy data from/to user space at kfd ioctls Several kfd ioctls need transfer array data from/to user space. Kfd driver uses kmalloc_array with user provided size. That can oversize alloc or 32-bit wrap with hostile value. Replace it by memdup_array_user that does overflow checking and allocates through dedicated slab caches, also physical continuous as kmalloc. Signed-off-by: Xiaogang Chen Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 46 +++++++++----------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index d41773aeb49c..fcdb4e222167 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1299,18 +1299,11 @@ static int kfd_ioctl_map_memory_to_gpu(struct file *filep, return -EINVAL; } - devices_arr = kmalloc_array(args->n_devices, sizeof(*devices_arr), - GFP_KERNEL); - if (!devices_arr) - return -ENOMEM; + devices_arr = memdup_array_user((void *)args->device_ids_array_ptr, + args->n_devices, sizeof(*devices_arr)); - err = copy_from_user(devices_arr, - (void __user *)args->device_ids_array_ptr, - args->n_devices * sizeof(*devices_arr)); - if (err != 0) { - err = -EFAULT; - goto copy_from_user_failed; - } + if (IS_ERR(devices_arr)) + return PTR_ERR(devices_arr); mutex_lock(&p->mutex); pdd = kfd_process_device_data_by_id(p, GET_GPU_ID(args->handle)); @@ -1391,7 +1384,6 @@ get_mem_obj_from_handle_failed: map_memory_to_gpu_failed: sync_memory_failed: mutex_unlock(&p->mutex); -copy_from_user_failed: kfree(devices_arr); return err; @@ -1416,18 +1408,11 @@ static int kfd_ioctl_unmap_memory_from_gpu(struct file *filep, return -EINVAL; } - devices_arr = kmalloc_array(args->n_devices, sizeof(*devices_arr), - GFP_KERNEL); - if (!devices_arr) - return -ENOMEM; + devices_arr = memdup_array_user((void *)args->device_ids_array_ptr, + args->n_devices, sizeof(*devices_arr)); - err = copy_from_user(devices_arr, - (void __user *)args->device_ids_array_ptr, - args->n_devices * sizeof(*devices_arr)); - if (err != 0) { - err = -EFAULT; - goto copy_from_user_failed; - } + if (IS_ERR(devices_arr)) + return PTR_ERR(devices_arr); mutex_lock(&p->mutex); pdd = kfd_process_device_data_by_id(p, GET_GPU_ID(args->handle)); @@ -1493,7 +1478,6 @@ get_mem_obj_from_handle_failed: unmap_memory_from_gpu_failed: sync_memory_failed: mutex_unlock(&p->mutex); -copy_from_user_failed: kfree(devices_arr); return err; } @@ -2353,17 +2337,11 @@ static int criu_restore_devices(struct kfd_process *p, if (*priv_offset + (args->num_devices * sizeof(*device_privs)) > max_priv_data_size) return -EINVAL; - device_buckets = kmalloc_objs(*device_buckets, args->num_devices); - if (!device_buckets) - return -ENOMEM; + device_buckets = memdup_array_user((void *)args->devices, + args->num_devices, sizeof(*device_buckets)); - ret = copy_from_user(device_buckets, (void __user *)args->devices, - args->num_devices * sizeof(*device_buckets)); - if (ret) { - pr_err("Failed to copy devices buckets from user\n"); - ret = -EFAULT; - goto exit; - } + if (IS_ERR(device_buckets)) + return PTR_ERR(device_buckets); for (i = 0; i < args->num_devices; i++) { struct kfd_node *dev; -- cgit v1.2.3 From 1b5e413713c0a93bc1818394d0ce49aaad21bd27 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:22 +0100 Subject: drm/amdgpu: Fix context pstate override handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are several problems in the context pstate handling code. The most serious ones are potential use-after-free and NULL pointer dereferences at context initialization time. Both are due amdgpu_ctx_init() not holding the adev->pm.stable_pstate_ctx_lock, which is otherwise used from both sysfs and the context code itself for modifying and clearing the stored context pointer. Second issue is that context fini can trample over the pstate configuration set via sysfs. This is due the restore state (ctx->stable_pstate) being saved at context init time, and not if, or when the context actually changes the pstate. As the context exits it will therefore incorrectly restore to what was set before the sysfs override was requested. The simplest fix is to drastically simplify how the state is tracked, by clearly defining the points at which pstate ownership is taken and released, and to handle all transitions under the correct lock. Instead of at context init time, the previous state is saved only at the point the context overrides the current state, and is restored on context exit only if the context is still the owner of the current override state. Signed-off-by: Tvrtko Ursulin Fixes: 79610d304133 ("drm/amdgpu: fix pstate setting issue") Cc: Chengming Gui Cc: Alex Deucher Cc: "Christian König" Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 71 +++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index 0d7f6cd74f79..ce35b415093d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -326,7 +326,6 @@ static int amdgpu_ctx_init(struct amdgpu_ctx_mgr *mgr, int32_t priority, struct drm_file *filp, struct amdgpu_ctx *ctx) { struct amdgpu_fpriv *fpriv = filp->driver_priv; - u32 current_stable_pstate; int r; r = amdgpu_ctx_priority_permit(filp, priority); @@ -344,36 +343,21 @@ static int amdgpu_ctx_init(struct amdgpu_ctx_mgr *mgr, int32_t priority, ctx->generation = amdgpu_vm_generation(mgr->adev, &fpriv->vm); ctx->init_priority = priority; ctx->override_priority = AMDGPU_CTX_PRIORITY_UNSET; - - r = amdgpu_ctx_get_stable_pstate(ctx, ¤t_stable_pstate); - if (r) - return r; - - if (mgr->adev->pm.stable_pstate_ctx) - ctx->stable_pstate = mgr->adev->pm.stable_pstate_ctx->stable_pstate; - else - ctx->stable_pstate = current_stable_pstate; + ctx->stable_pstate = AMDGPU_CTX_STABLE_PSTATE_NONE; return 0; } -static int amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, - u32 stable_pstate) +static int __amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, + u32 stable_pstate) { struct amdgpu_device *adev = ctx->mgr->adev; enum amd_dpm_forced_level level; + struct amdgpu_ctx *current_ctx; u32 current_stable_pstate; - int r; + int r = 0; - mutex_lock(&adev->pm.stable_pstate_ctx_lock); - if (adev->pm.stable_pstate_ctx && adev->pm.stable_pstate_ctx != ctx) { - r = -EBUSY; - goto done; - } - - r = amdgpu_ctx_get_stable_pstate(ctx, ¤t_stable_pstate); - if (r || (stable_pstate == current_stable_pstate)) - goto done; + lockdep_assert_held(&adev->pm.stable_pstate_ctx_lock); switch (stable_pstate) { case AMDGPU_CTX_STABLE_PSTATE_NONE: @@ -392,17 +376,41 @@ static int amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, level = AMD_DPM_FORCED_LEVEL_PROFILE_PEAK; break; default: - r = -EINVAL; - goto done; + return -EINVAL; } + current_ctx = adev->pm.stable_pstate_ctx; + if (current_ctx && current_ctx != ctx) + return -EBUSY; + + r = amdgpu_ctx_get_stable_pstate(ctx, ¤t_stable_pstate); + if (r || current_stable_pstate == stable_pstate) + return r; + r = amdgpu_dpm_force_performance_level(adev, level); + if (r) + return r; - if (level == AMD_DPM_FORCED_LEVEL_AUTO) - adev->pm.stable_pstate_ctx = NULL; - else + if (!current_ctx) { adev->pm.stable_pstate_ctx = ctx; -done: + /* + * Serialized by context taking ownership for the first time + * while holding adev->pm.stable_pstate_ctx_lock). + */ + WRITE_ONCE(ctx->stable_pstate, current_stable_pstate); + } + + return 0; +} + +static int amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, + u32 stable_pstate) +{ + struct amdgpu_device *adev = ctx->mgr->adev; + int r; + + mutex_lock(&adev->pm.stable_pstate_ctx_lock); + r = __amdgpu_ctx_set_stable_pstate(ctx, stable_pstate); mutex_unlock(&adev->pm.stable_pstate_ctx_lock); return r; @@ -428,7 +436,12 @@ static void amdgpu_ctx_fini(struct kref *ref) } if (drm_dev_enter(adev_to_drm(adev), &idx)) { - amdgpu_ctx_set_stable_pstate(ctx, ctx->stable_pstate); + mutex_lock(&adev->pm.stable_pstate_ctx_lock); + if (adev->pm.stable_pstate_ctx == ctx) { + __amdgpu_ctx_set_stable_pstate(ctx, ctx->stable_pstate); + adev->pm.stable_pstate_ctx = NULL; + } + mutex_unlock(&adev->pm.stable_pstate_ctx_lock); drm_dev_exit(idx); } -- cgit v1.2.3 From 251b67e82d9560e8964188bf1b43c2bf5b7282e1 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:23 +0100 Subject: drm/amdgpu: Remove arbitrary number of contexts limitation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no need for an arbitrary limit to number of contexts userspace can be allowed to create. Remove the AMDGPU_VM_MAX_NUM_CTX (4096) and allow for full 32-bit of handles to be allocated. Signed-off-by: Tvrtko Ursulin Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 - drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 45bf05306c90..e2d4be3c111d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -272,7 +272,6 @@ extern int amdgpu_ptl; extern uint amdgpu_hdmi_hpd_debounce_delay_ms; -#define AMDGPU_VM_MAX_NUM_CTX 4096 #define AMDGPU_SG_THRESHOLD (256*1024*1024) #define AMDGPU_WAIT_IDLE_TIMEOUT_IN_MS 3000 #define AMDGPU_MAX_USEC_TIMEOUT 100000 /* 100 ms */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index ce35b415093d..3f34ecda0288 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -505,14 +505,14 @@ static int amdgpu_ctx_alloc(struct amdgpu_device *adev, return -ENOMEM; mutex_lock(&mgr->lock); - r = idr_alloc(&mgr->ctx_handles, ctx, 1, AMDGPU_VM_MAX_NUM_CTX, GFP_KERNEL); - if (r < 0) { + *id = 1; + r = idr_alloc_u32(&mgr->ctx_handles, ctx, id, UINT_MAX, GFP_KERNEL); + if (r) { mutex_unlock(&mgr->lock); kfree(ctx); return r; } - *id = (uint32_t)r; r = amdgpu_ctx_init(mgr, priority, filp, ctx); if (r) { idr_remove(&mgr->ctx_handles, *id); -- cgit v1.2.3 From 12493d0dee76e1f7c17f601f24b83fa43201b846 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:24 +0100 Subject: drm/amdgpu: Consolidate ctx put MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently there are two flavours of the context reference count destructor: - amdgpu_ctx_do_release(), used from kref_put from places where the code thinks context may have been used, or is in active use, and; - amdgpu_ctx_fini(), used when code is sure context entities have already been idled. Since amdgpu_ctx_do_release() calls amdgpu_ctx_fini() after having idled and destroyed the scheduler entities, we can consolidate the two into a single function. Functional difference is that now drm_sched_entity_destroy() is called on context manager shutdown (file close), where previously it was drm_sched_entity_fini(). But the former is a superset of the latter, and during file close the flush method is also called, which calls drm_sched_entity_flush(), which is also called by drm_sched_entity_destroy(). And as it is safe to attempt to flush a never used entity, or flush it twice, there is actually no functional change. Signed-off-by: Tvrtko Ursulin Suggested-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 54 +++++---------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h | 9 +++++- 2 files changed, 15 insertions(+), 48 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index 3f34ecda0288..21104e91b44c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -283,6 +283,8 @@ static ktime_t amdgpu_ctx_fini_entity(struct amdgpu_device *adev, if (!entity) return res; + drm_sched_entity_destroy(&entity->entity); + for (i = 0; i < amdgpu_sched_jobs; ++i) { res = ktime_add(res, amdgpu_ctx_fence_time(entity->fences[i])); dma_fence_put(entity->fences[i]); @@ -416,7 +418,7 @@ static int amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, return r; } -static void amdgpu_ctx_fini(struct kref *ref) +void amdgpu_ctx_fini(struct kref *ref) { struct amdgpu_ctx *ctx = container_of(ref, struct amdgpu_ctx, refcount); struct amdgpu_ctx_mgr *mgr = ctx->mgr; @@ -523,24 +525,6 @@ static int amdgpu_ctx_alloc(struct amdgpu_device *adev, return r; } -static void amdgpu_ctx_do_release(struct kref *ref) -{ - struct amdgpu_ctx *ctx; - u32 i, j; - - ctx = container_of(ref, struct amdgpu_ctx, refcount); - for (i = 0; i < AMDGPU_HW_IP_NUM; ++i) { - for (j = 0; j < amdgpu_ctx_num_entities[i]; ++j) { - if (!ctx->entities[i][j]) - continue; - - drm_sched_entity_destroy(&ctx->entities[i][j]->entity); - } - } - - amdgpu_ctx_fini(ref); -} - static int amdgpu_ctx_free(struct amdgpu_fpriv *fpriv, uint32_t id) { struct amdgpu_ctx_mgr *mgr = &fpriv->ctx_mgr; @@ -548,8 +532,7 @@ static int amdgpu_ctx_free(struct amdgpu_fpriv *fpriv, uint32_t id) mutex_lock(&mgr->lock); ctx = idr_remove(&mgr->ctx_handles, id); - if (ctx) - kref_put(&ctx->refcount, amdgpu_ctx_do_release); + amdgpu_ctx_put(ctx); mutex_unlock(&mgr->lock); return ctx ? 0 : -EINVAL; } @@ -786,15 +769,6 @@ struct amdgpu_ctx *amdgpu_ctx_get(struct amdgpu_fpriv *fpriv, uint32_t id) return ctx; } -int amdgpu_ctx_put(struct amdgpu_ctx *ctx) -{ - if (ctx == NULL) - return -EINVAL; - - kref_put(&ctx->refcount, amdgpu_ctx_do_release); - return 0; -} - uint64_t amdgpu_ctx_add_fence(struct amdgpu_ctx *ctx, struct drm_sched_entity *entity, struct dma_fence *fence) @@ -964,29 +938,15 @@ long amdgpu_ctx_mgr_entity_flush(struct amdgpu_ctx_mgr *mgr, long timeout) static void amdgpu_ctx_mgr_entity_fini(struct amdgpu_ctx_mgr *mgr) { struct amdgpu_ctx *ctx; - struct idr *idp; - uint32_t id, i, j; - - idp = &mgr->ctx_handles; + uint32_t id; - idr_for_each_entry(idp, ctx, id) { + idr_for_each_entry(&mgr->ctx_handles, ctx, id) { if (kref_read(&ctx->refcount) != 1) { drm_err(adev_to_drm(mgr->adev), "ctx %p is still alive\n", ctx); continue; } - for (i = 0; i < AMDGPU_HW_IP_NUM; ++i) { - for (j = 0; j < amdgpu_ctx_num_entities[i]; ++j) { - struct drm_sched_entity *entity; - - if (!ctx->entities[i][j]) - continue; - - entity = &ctx->entities[i][j]->entity; - drm_sched_entity_fini(entity); - } - } - kref_put(&ctx->refcount, amdgpu_ctx_fini); + amdgpu_ctx_put(ctx); } } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h index e444b2088d40..90a56096fa3e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h @@ -69,7 +69,14 @@ struct amdgpu_ctx_mgr { extern const unsigned int amdgpu_ctx_num_entities[AMDGPU_HW_IP_NUM]; struct amdgpu_ctx *amdgpu_ctx_get(struct amdgpu_fpriv *fpriv, uint32_t id); -int amdgpu_ctx_put(struct amdgpu_ctx *ctx); + +void amdgpu_ctx_fini(struct kref *kref); + +static inline void amdgpu_ctx_put(struct amdgpu_ctx *ctx) +{ + if (ctx) + kref_put(&ctx->refcount, amdgpu_ctx_fini); +} int amdgpu_ctx_get_entity(struct amdgpu_ctx *ctx, u32 hw_ip, u32 instance, u32 ring, struct drm_sched_entity **entity); -- cgit v1.2.3 From ed2172c5ff5778bfca08f93bde9594c3bf1e454b Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:25 +0100 Subject: drm/amdgpu: Remove live context error log and skip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to Christian the skip can only cause memory leaks if it would to trigger, while it does nothing for the fact context manager will still get zapped with live back references from dangling contexts. Signed-off-by: Tvrtko Ursulin Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index 21104e91b44c..eb5f75a12c99 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -940,14 +940,8 @@ static void amdgpu_ctx_mgr_entity_fini(struct amdgpu_ctx_mgr *mgr) struct amdgpu_ctx *ctx; uint32_t id; - idr_for_each_entry(&mgr->ctx_handles, ctx, id) { - if (kref_read(&ctx->refcount) != 1) { - drm_err(adev_to_drm(mgr->adev), "ctx %p is still alive\n", ctx); - continue; - } - + idr_for_each_entry(&mgr->ctx_handles, ctx, id) amdgpu_ctx_put(ctx); - } } void amdgpu_ctx_mgr_fini(struct amdgpu_ctx_mgr *mgr) -- cgit v1.2.3 From 018c489c9572c822ae87049c4360f8ab00e1941b Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:26 +0100 Subject: drm/amdgpu: Simplify amdgpu_ctx_get_stable_pstate() amdgpu_ctx_get_stable_pstate() can never return other than success so instead of returning the pstate via a pointer we can simply return the pstate directly. While at it, rename the function to amdgpu_get_stable_pstate() to make it obvious it is not operating on the context at all. Signed-off-by: Tvrtko Ursulin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 38 +++++++++++---------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index eb5f75a12c99..f1143f2bfafb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -296,32 +296,20 @@ static ktime_t amdgpu_ctx_fini_entity(struct amdgpu_device *adev, return res; } -static int amdgpu_ctx_get_stable_pstate(struct amdgpu_ctx *ctx, - u32 *stable_pstate) +static u32 amdgpu_get_stable_pstate(struct amdgpu_device *adev) { - struct amdgpu_device *adev = ctx->mgr->adev; - enum amd_dpm_forced_level current_level; - - current_level = amdgpu_dpm_get_performance_level(adev); - - switch (current_level) { + switch (amdgpu_dpm_get_performance_level(adev)) { case AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD: - *stable_pstate = AMDGPU_CTX_STABLE_PSTATE_STANDARD; - break; + return AMDGPU_CTX_STABLE_PSTATE_STANDARD; case AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK: - *stable_pstate = AMDGPU_CTX_STABLE_PSTATE_MIN_SCLK; - break; + return AMDGPU_CTX_STABLE_PSTATE_MIN_SCLK; case AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK: - *stable_pstate = AMDGPU_CTX_STABLE_PSTATE_MIN_MCLK; - break; + return AMDGPU_CTX_STABLE_PSTATE_MIN_MCLK; case AMD_DPM_FORCED_LEVEL_PROFILE_PEAK: - *stable_pstate = AMDGPU_CTX_STABLE_PSTATE_PEAK; - break; + return AMDGPU_CTX_STABLE_PSTATE_PEAK; default: - *stable_pstate = AMDGPU_CTX_STABLE_PSTATE_NONE; - break; + return AMDGPU_CTX_STABLE_PSTATE_NONE; } - return 0; } static int amdgpu_ctx_init(struct amdgpu_ctx_mgr *mgr, int32_t priority, @@ -357,7 +345,7 @@ static int __amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, enum amd_dpm_forced_level level; struct amdgpu_ctx *current_ctx; u32 current_stable_pstate; - int r = 0; + int r; lockdep_assert_held(&adev->pm.stable_pstate_ctx_lock); @@ -385,9 +373,9 @@ static int __amdgpu_ctx_set_stable_pstate(struct amdgpu_ctx *ctx, if (current_ctx && current_ctx != ctx) return -EBUSY; - r = amdgpu_ctx_get_stable_pstate(ctx, ¤t_stable_pstate); - if (r || current_stable_pstate == stable_pstate) - return r; + current_stable_pstate = amdgpu_get_stable_pstate(adev); + if (current_stable_pstate == stable_pstate) + return 0; r = amdgpu_dpm_force_performance_level(adev, level); if (r) @@ -664,7 +652,7 @@ static int amdgpu_ctx_stable_pstate(struct amdgpu_device *adev, { struct amdgpu_ctx *ctx; struct amdgpu_ctx_mgr *mgr; - int r; + int r = 0; if (!fpriv) return -EINVAL; @@ -680,7 +668,7 @@ static int amdgpu_ctx_stable_pstate(struct amdgpu_device *adev, if (set) r = amdgpu_ctx_set_stable_pstate(ctx, *stable_pstate); else - r = amdgpu_ctx_get_stable_pstate(ctx, stable_pstate); + *stable_pstate = amdgpu_get_stable_pstate(adev); mutex_unlock(&mgr->lock); return r; -- cgit v1.2.3 From 847fb1c21680ba02807a012011e5e33e3a2126c2 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:27 +0100 Subject: drm/amdgpu: Convert context manager to xarray MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IDR is deprecated so let's convert the context manager to xarray. In doing so we remove the context manager mutex and switch call sites which required the guarantee context cannot go away while they walk the list of context, or otherwise operate on them, to use reference counting. This allows us to use the built in xarray spinlock for all operations and just temporarily drop it when we need to call sleeping functions. Signed-off-by: Tvrtko Ursulin Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 113 +++++++++++------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h | 5 +- drivers/gpu/drm/amd/amdgpu/amdgpu_sched.c | 8 +-- 3 files changed, 45 insertions(+), 81 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index f1143f2bfafb..047b83ce86d3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -494,34 +494,26 @@ static int amdgpu_ctx_alloc(struct amdgpu_device *adev, if (!ctx) return -ENOMEM; - mutex_lock(&mgr->lock); - *id = 1; - r = idr_alloc_u32(&mgr->ctx_handles, ctx, id, UINT_MAX, GFP_KERNEL); + r = amdgpu_ctx_init(mgr, priority, filp, ctx); if (r) { - mutex_unlock(&mgr->lock); kfree(ctx); return r; } - r = amdgpu_ctx_init(mgr, priority, filp, ctx); - if (r) { - idr_remove(&mgr->ctx_handles, *id); - *id = 0; - kfree(ctx); - } - mutex_unlock(&mgr->lock); + r = xa_alloc(&mgr->ctx_handles, id, ctx, xa_limit_32b, GFP_KERNEL); + if (r) + amdgpu_ctx_put(ctx); + return r; } static int amdgpu_ctx_free(struct amdgpu_fpriv *fpriv, uint32_t id) { - struct amdgpu_ctx_mgr *mgr = &fpriv->ctx_mgr; struct amdgpu_ctx *ctx; - mutex_lock(&mgr->lock); - ctx = idr_remove(&mgr->ctx_handles, id); + ctx = xa_erase(&fpriv->ctx_mgr.ctx_handles, id); amdgpu_ctx_put(ctx); - mutex_unlock(&mgr->lock); + return ctx ? 0 : -EINVAL; } @@ -530,19 +522,11 @@ static int amdgpu_ctx_query(struct amdgpu_device *adev, union drm_amdgpu_ctx_out *out) { struct amdgpu_ctx *ctx; - struct amdgpu_ctx_mgr *mgr; unsigned reset_counter; - if (!fpriv) - return -EINVAL; - - mgr = &fpriv->ctx_mgr; - mutex_lock(&mgr->lock); - ctx = idr_find(&mgr->ctx_handles, id); - if (!ctx) { - mutex_unlock(&mgr->lock); + ctx = amdgpu_ctx_get(fpriv, id); + if (!ctx) return -EINVAL; - } /* TODO: these two are always zero */ out->state.flags = 0x0; @@ -557,7 +541,8 @@ static int amdgpu_ctx_query(struct amdgpu_device *adev, out->state.reset_status = AMDGPU_CTX_UNKNOWN_RESET; ctx->reset_counter_query = reset_counter; - mutex_unlock(&mgr->lock); + amdgpu_ctx_put(ctx); + return 0; } @@ -590,18 +575,10 @@ static int amdgpu_ctx_query2(struct amdgpu_device *adev, { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); struct amdgpu_ctx *ctx; - struct amdgpu_ctx_mgr *mgr; - - if (!fpriv) - return -EINVAL; - mgr = &fpriv->ctx_mgr; - mutex_lock(&mgr->lock); - ctx = idr_find(&mgr->ctx_handles, id); - if (!ctx) { - mutex_unlock(&mgr->lock); + ctx = amdgpu_ctx_get(fpriv, id); + if (!ctx) return -EINVAL; - } out->state.flags = 0x0; out->state.hangs = 0x0; @@ -642,7 +619,8 @@ static int amdgpu_ctx_query2(struct amdgpu_device *adev, msecs_to_jiffies(AMDGPU_RAS_COUNTE_DELAY_MS)); } - mutex_unlock(&mgr->lock); + amdgpu_ctx_put(ctx); + return 0; } @@ -651,26 +629,18 @@ static int amdgpu_ctx_stable_pstate(struct amdgpu_device *adev, bool set, u32 *stable_pstate) { struct amdgpu_ctx *ctx; - struct amdgpu_ctx_mgr *mgr; int r = 0; - if (!fpriv) - return -EINVAL; - - mgr = &fpriv->ctx_mgr; - mutex_lock(&mgr->lock); - ctx = idr_find(&mgr->ctx_handles, id); - if (!ctx) { - mutex_unlock(&mgr->lock); + ctx = amdgpu_ctx_get(fpriv, id); + if (!ctx) return -EINVAL; - } if (set) r = amdgpu_ctx_set_stable_pstate(ctx, *stable_pstate); else *stable_pstate = amdgpu_get_stable_pstate(adev); - mutex_unlock(&mgr->lock); + amdgpu_ctx_put(ctx); return r; } @@ -749,11 +719,11 @@ struct amdgpu_ctx *amdgpu_ctx_get(struct amdgpu_fpriv *fpriv, uint32_t id) mgr = &fpriv->ctx_mgr; - mutex_lock(&mgr->lock); - ctx = idr_find(&mgr->ctx_handles, id); + xa_lock(&mgr->ctx_handles); + ctx = xa_load(&mgr->ctx_handles, id); if (ctx) kref_get(&ctx->refcount); - mutex_unlock(&mgr->lock); + xa_unlock(&mgr->ctx_handles); return ctx; } @@ -890,8 +860,7 @@ void amdgpu_ctx_mgr_init(struct amdgpu_ctx_mgr *mgr, unsigned int i; mgr->adev = adev; - mutex_init(&mgr->lock); - idr_init_base(&mgr->ctx_handles, 1); + xa_init_flags(&mgr->ctx_handles, XA_FLAGS_ALLOC1); for (i = 0; i < AMDGPU_HW_IP_NUM; ++i) atomic64_set(&mgr->time_spend[i], 0); @@ -900,13 +869,13 @@ void amdgpu_ctx_mgr_init(struct amdgpu_ctx_mgr *mgr, long amdgpu_ctx_mgr_entity_flush(struct amdgpu_ctx_mgr *mgr, long timeout) { struct amdgpu_ctx *ctx; - struct idr *idp; - uint32_t id, i, j; + unsigned long id; + int i, j; - idp = &mgr->ctx_handles; - - mutex_lock(&mgr->lock); - idr_for_each_entry(idp, ctx, id) { + xa_lock(&mgr->ctx_handles); + xa_for_each(&mgr->ctx_handles, id, ctx) { + kref_get(&ctx->refcount); + xa_unlock(&mgr->ctx_handles); for (i = 0; i < AMDGPU_HW_IP_NUM; ++i) { for (j = 0; j < amdgpu_ctx_num_entities[i]; ++j) { struct drm_sched_entity *entity; @@ -918,25 +887,21 @@ long amdgpu_ctx_mgr_entity_flush(struct amdgpu_ctx_mgr *mgr, long timeout) timeout = drm_sched_entity_flush(entity, timeout); } } + amdgpu_ctx_put(ctx); + xa_lock(&mgr->ctx_handles); } - mutex_unlock(&mgr->lock); + xa_unlock(&mgr->ctx_handles); return timeout; } -static void amdgpu_ctx_mgr_entity_fini(struct amdgpu_ctx_mgr *mgr) +void amdgpu_ctx_mgr_fini(struct amdgpu_ctx_mgr *mgr) { struct amdgpu_ctx *ctx; - uint32_t id; + unsigned long id; - idr_for_each_entry(&mgr->ctx_handles, ctx, id) + xa_for_each(&mgr->ctx_handles, id, ctx) amdgpu_ctx_put(ctx); -} - -void amdgpu_ctx_mgr_fini(struct amdgpu_ctx_mgr *mgr) -{ - amdgpu_ctx_mgr_entity_fini(mgr); - idr_destroy(&mgr->ctx_handles); - mutex_destroy(&mgr->lock); + xa_destroy(&mgr->ctx_handles); } void amdgpu_ctx_mgr_usage(struct amdgpu_ctx_mgr *mgr, @@ -944,21 +909,21 @@ void amdgpu_ctx_mgr_usage(struct amdgpu_ctx_mgr *mgr, { struct amdgpu_ctx *ctx; unsigned int hw_ip, i; - uint32_t id; + unsigned long id; /* * This is a little bit racy because it can be that a ctx or a fence are * destroyed just in the moment we try to account them. But that is ok * since exactly that case is explicitely allowed by the interface. */ - mutex_lock(&mgr->lock); for (hw_ip = 0; hw_ip < AMDGPU_HW_IP_NUM; ++hw_ip) { uint64_t ns = atomic64_read(&mgr->time_spend[hw_ip]); usage[hw_ip] = ns_to_ktime(ns); } - idr_for_each_entry(&mgr->ctx_handles, ctx, id) { + xa_lock(&mgr->ctx_handles); + xa_for_each(&mgr->ctx_handles, id, ctx) { for (hw_ip = 0; hw_ip < AMDGPU_HW_IP_NUM; ++hw_ip) { for (i = 0; i < amdgpu_ctx_num_entities[hw_ip]; ++i) { struct amdgpu_ctx_entity *centity; @@ -972,5 +937,5 @@ void amdgpu_ctx_mgr_usage(struct amdgpu_ctx_mgr *mgr, } } } - mutex_unlock(&mgr->lock); + xa_unlock(&mgr->ctx_handles); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h index 90a56096fa3e..a4b89eca4169 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h @@ -25,6 +25,7 @@ #include #include +#include #include "amdgpu_ring.h" @@ -60,9 +61,7 @@ struct amdgpu_ctx { struct amdgpu_ctx_mgr { struct amdgpu_device *adev; - struct mutex lock; - /* protected by lock */ - struct idr ctx_handles; + struct xarray ctx_handles; atomic64_t time_spend[AMDGPU_HW_IP_NUM]; }; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sched.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_sched.c index 0eecfaa3a94c..8effb1158430 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sched.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sched.c @@ -39,7 +39,7 @@ static int amdgpu_sched_process_priority_override(struct amdgpu_device *adev, struct amdgpu_fpriv *fpriv; struct amdgpu_ctx_mgr *mgr; struct amdgpu_ctx *ctx; - uint32_t id; + unsigned long id; int r; if (fd_empty(f)) @@ -50,10 +50,10 @@ static int amdgpu_sched_process_priority_override(struct amdgpu_device *adev, return r; mgr = &fpriv->ctx_mgr; - mutex_lock(&mgr->lock); - idr_for_each_entry(&mgr->ctx_handles, ctx, id) + xa_lock(&mgr->ctx_handles); + xa_for_each(&mgr->ctx_handles, id, ctx) amdgpu_ctx_priority_override(ctx, priority); - mutex_unlock(&mgr->lock); + xa_unlock(&mgr->ctx_handles); return 0; } -- cgit v1.2.3 From dbb0f5edcb62a300806ac5da67dfa432258fb45a Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Mon, 1 Jun 2026 15:08:28 +0100 Subject: drm/amdgpu: Clarify odd behaviour of AMDGPU_CTX_OP_GET_STABLE_PSTATE AMDGPU_CTX_OP_GET_STABLE_PSTATE is an unusual uapi - it will check whether the context id exist, but otherwise does nothing with it. In other words, the uapi has historically been implemented as being able to query the global device state, as long as the caller supplies a random valid context id. Lets just document this and later figure out if it can be changed to either more permissive (don't check context id), or more restrictive (only allow queries from contexts which have overriden the performance state). Signed-off-by: Tvrtko Ursulin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c index 047b83ce86d3..b15ed4a534f1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -635,6 +635,14 @@ static int amdgpu_ctx_stable_pstate(struct amdgpu_device *adev, if (!ctx) return -EINVAL; + /* + * The get path is odd in this uapi - it will check whether the context + * id exist, but otherwise does nothing with it. In other words, the + * uapi has historically been implemented as being able to query the + * global device state, as long as the caller supplies a random valid + * context id. + */ + if (set) r = amdgpu_ctx_set_stable_pstate(ctx, *stable_pstate); else -- cgit v1.2.3 From 6eadd448d7e7d5cc32b3cc371fc0762c3e1d1f56 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 24 Apr 2026 13:50:02 +0100 Subject: drm/amdgpu: Choose SOC15 RLC register read write functions at init time Currently on every RLC register read the driver checks for three different conditions to decide which of the two register read/write functions to call. As these register operations are macros, which is required for register name expansion to work, the result is a significant explosion of generated (redundant) code which the compiler cannot optimise away. We however know that all of the three conditional are static and can therefore move the decision to driver init time. All that we need to do is define a new vfunc table for the SOC12 RLC read/write functions and just use them directly. Bloat-o-meter agrees the driver size savings are significant: add/remove: 11/35 grow/shrink: 82/1117 up/down: 53024/-450922 (-397898) ... Total: Before=10293928, After=9896030, chg -3.87% Signed-off-by: Tvrtko Ursulin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.c | 39 ++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h | 10 ++++++++ drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 2 ++ drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 2 ++ drivers/gpu/drm/amd/amdgpu/soc15_common.h | 8 ++---- 10 files changed, 64 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index b29b60acd8f4..c66d3a24f54e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3777,6 +3777,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, spin_lock_init(&adev->irq.lock); + amdgpu_early_init_rlc_reg_funcs(adev); amdgpu_device_init_apu_flags(adev); r = amdgpu_device_check_arguments(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.c index 572a60e1b3cb..002fae3c380e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.c @@ -583,3 +583,42 @@ int amdgpu_gfx_rlc_init_microcode(struct amdgpu_device *adev, amdgpu_gfx_rlc_init_microcode_v2_5(adev); return 0; } + +static const struct amdgpu_rlc_reg_funcs amdgpu_sriov_rlc_reg_funcs = { + .rreg32 = amdgpu_sriov_rreg, + .wreg32 = amdgpu_sriov_wreg, +}; + +static u32 +amdgpu_rlc_rreg(struct amdgpu_device *adev, u32 reg, u32 acc_flags, u32 hwip, + u32 xcc_id) +{ + return amdgpu_device_rreg(adev, reg, 0); +} + +static void +amdgpu_rlc_wreg(struct amdgpu_device *adev, u32 reg, u32 value, u32 acc_flags, + u32 hwip, u32 xcc_id) +{ + amdgpu_device_wreg(adev, reg, value, 0); +} + +static const struct amdgpu_rlc_reg_funcs amdgpu_rlc_reg_funcs = { + .rreg32 = amdgpu_rlc_rreg, + .wreg32 = amdgpu_rlc_wreg, +}; + +void amdgpu_early_init_rlc_reg_funcs(struct amdgpu_device *adev) +{ + adev->gfx.rlc.reg_funcs = &amdgpu_rlc_reg_funcs; +} + +void amdgpu_init_rlc_reg_funcs(struct amdgpu_device *adev) +{ + if (amdgpu_sriov_vf(adev) && + adev->gfx.rlc.funcs && + adev->gfx.rlc.rlcg_reg_access_supported) + adev->gfx.rlc.reg_funcs = &amdgpu_sriov_rlc_reg_funcs; + else + adev->gfx.rlc.reg_funcs = &amdgpu_rlc_reg_funcs; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h index e535534237a1..959d60c90dcd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h @@ -262,6 +262,11 @@ struct amdgpu_rlc_funcs { bool (*is_rlcg_access_range)(struct amdgpu_device *adev, uint32_t reg); }; +struct amdgpu_rlc_reg_funcs { + u32 (*rreg32)(struct amdgpu_device *adev, u32 reg, u32 acc_flags, u32 hwip, u32 xcc_id); + void (*wreg32)(struct amdgpu_device *adev, u32 reg, u32 val, u32 acc_flags, u32 hwip, u32 xcc_id); +}; + struct amdgpu_rlcg_reg_access_ctrl { uint32_t scratch_reg0; uint32_t scratch_reg1; @@ -303,6 +308,7 @@ struct amdgpu_rlc { /* safe mode for updating CG/PG state */ bool in_safe_mode[AMDGPU_MAX_RLC_INSTANCES]; const struct amdgpu_rlc_funcs *funcs; + const struct amdgpu_rlc_reg_funcs *reg_funcs; /* for firmware data */ u32 save_and_restore_offset; @@ -374,4 +380,8 @@ void amdgpu_gfx_rlc_fini(struct amdgpu_device *adev); int amdgpu_gfx_rlc_init_microcode(struct amdgpu_device *adev, uint16_t version_major, uint16_t version_minor); + +void amdgpu_early_init_rlc_reg_funcs(struct amdgpu_device *adev); +void amdgpu_init_rlc_reg_funcs(struct amdgpu_device *adev); + #endif diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 0780c5e5de4f..76d4c33a6e65 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -7852,6 +7852,8 @@ static int gfx_v10_0_early_init(struct amdgpu_ip_block *ip_block) /* init rlcg reg access ctrl */ gfx_v10_0_init_rlcg_reg_access_ctrl(adev); + amdgpu_init_rlc_reg_funcs(adev); + return gfx_v10_0_init_microcode(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 0bd9d8a21f5e..6346f16c4e61 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -5411,6 +5411,8 @@ static int gfx_v11_0_early_init(struct amdgpu_ip_block *ip_block) gfx_v11_0_init_rlcg_reg_access_ctrl(adev); + amdgpu_init_rlc_reg_funcs(adev); + return gfx_v11_0_init_microcode(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 380ba062134e..f8280cc81a66 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -3982,6 +3982,8 @@ static int gfx_v12_0_early_init(struct amdgpu_ip_block *ip_block) gfx_v12_0_init_rlcg_reg_access_ctrl(adev); + amdgpu_init_rlc_reg_funcs(adev); + return gfx_v12_0_init_microcode(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index b4382b751614..30a38190f98a 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -2997,6 +2997,8 @@ static int gfx_v12_1_early_init(struct amdgpu_ip_block *ip_block) gfx_v12_1_init_rlcg_reg_access_ctrl(adev); + amdgpu_init_rlc_reg_funcs(adev); + return gfx_v12_1_init_microcode(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 47721d0c3781..6d52b19a5f1c 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -4836,6 +4836,8 @@ static int gfx_v9_0_early_init(struct amdgpu_ip_block *ip_block) /* init rlcg reg access ctrl */ gfx_v9_0_init_rlcg_reg_access_ctrl(adev); + amdgpu_init_rlc_reg_funcs(adev); + return gfx_v9_0_init_microcode(adev); } 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 510266ba0c38..71a2558acef8 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -2623,6 +2623,8 @@ static int gfx_v9_4_3_early_init(struct amdgpu_ip_block *ip_block) /* init rlcg reg access ctrl */ gfx_v9_4_3_init_rlcg_reg_access_ctrl(adev); + amdgpu_init_rlc_reg_funcs(adev); + return gfx_v9_4_3_init_microcode(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/soc15_common.h b/drivers/gpu/drm/amd/amdgpu/soc15_common.h index a7b5a95ebebb..a04f61b22379 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc15_common.h +++ b/drivers/gpu/drm/amd/amdgpu/soc15_common.h @@ -38,14 +38,10 @@ (adev->reg_offset[ip##_HWIP][inst][reg##_BASE_IDX] + (reg)+(offset)) #define __WREG32_SOC15_RLC__(reg, value, flag, hwip, inst) \ - ((amdgpu_sriov_vf(adev) && adev->gfx.rlc.funcs && adev->gfx.rlc.rlcg_reg_access_supported) ? \ - amdgpu_sriov_wreg(adev, reg, value, flag, hwip, inst) : \ - WREG32(reg, value)) + adev->gfx.rlc.reg_funcs->wreg32(adev, reg, value, flag, hwip, inst) #define __RREG32_SOC15_RLC__(reg, flag, hwip, inst) \ - ((amdgpu_sriov_vf(adev) && adev->gfx.rlc.funcs && adev->gfx.rlc.rlcg_reg_access_supported) ? \ - amdgpu_sriov_rreg(adev, reg, flag, hwip, inst) : \ - RREG32(reg)) + adev->gfx.rlc.reg_funcs->rreg32(adev, reg, flag, hwip, inst) #define WREG32_FIELD15(ip, idx, reg, field, val) \ __WREG32_SOC15_RLC__(adev->reg_offset[ip##_HWIP][idx][mm##reg##_BASE_IDX] + mm##reg, \ -- cgit v1.2.3 From 1eedf2c84bb5720c4296198a5b68ecd1927d9295 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 24 Apr 2026 13:50:03 +0100 Subject: drm/amdgpu: Only calculate register offset once in SOC15 RLC We can save some text by only calculating the register offset once in a few of the SOC15 RLC register read/write macros. add/remove: 0/0 grow/shrink: 3/69 up/down: 62/-1259 (-1197) ... Total: Before=9896030, After=9894833, chg -0.01% Signed-off-by: Tvrtko Ursulin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/soc15_common.h | 49 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/soc15_common.h b/drivers/gpu/drm/amd/amdgpu/soc15_common.h index a04f61b22379..e8c1d0f207e7 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc15_common.h +++ b/drivers/gpu/drm/amd/amdgpu/soc15_common.h @@ -43,21 +43,25 @@ #define __RREG32_SOC15_RLC__(reg, flag, hwip, inst) \ adev->gfx.rlc.reg_funcs->rreg32(adev, reg, flag, hwip, inst) -#define WREG32_FIELD15(ip, idx, reg, field, val) \ - __WREG32_SOC15_RLC__(adev->reg_offset[ip##_HWIP][idx][mm##reg##_BASE_IDX] + mm##reg, \ - (__RREG32_SOC15_RLC__( \ - adev->reg_offset[ip##_HWIP][idx][mm##reg##_BASE_IDX] + mm##reg, \ - 0, ip##_HWIP, idx) & \ - ~REG_FIELD_MASK(reg, field)) | (val) << REG_FIELD_SHIFT(reg, field), \ - 0, ip##_HWIP, idx) - -#define WREG32_FIELD15_PREREG(ip, idx, reg_name, field, val) \ - __WREG32_SOC15_RLC__(adev->reg_offset[ip##_HWIP][idx][reg##reg_name##_BASE_IDX] + reg##reg_name, \ - (__RREG32_SOC15_RLC__( \ - adev->reg_offset[ip##_HWIP][idx][reg##reg_name##_BASE_IDX] + reg##reg_name, \ - 0, ip##_HWIP, idx) & \ - ~REG_FIELD_MASK(reg_name, field)) | (val) << REG_FIELD_SHIFT(reg_name, field), \ - 0, ip##_HWIP, idx) +#define WREG32_FIELD15(ip, idx, reg_name, field, val) \ +do { \ + u32 reg__ = adev->reg_offset[ip##_HWIP][idx][mm##reg_name##_BASE_IDX] + mm##reg_name; \ + u32 val__ = __RREG32_SOC15_RLC__(reg__, 0, ip##_HWIP, idx); \ +\ + val__ &= ~REG_FIELD_MASK(reg_name, field); \ + val__ |= (val) << REG_FIELD_SHIFT(reg_name, field); \ + __WREG32_SOC15_RLC__(reg__, val__, 0, ip##_HWIP, idx); \ +} while (0) + +#define WREG32_FIELD15_PREREG(ip, idx, reg_name, field, val) \ +do { \ + u32 reg__ = adev->reg_offset[ip##_HWIP][idx][reg##reg_name##_BASE_IDX] + reg##reg_name; \ + u32 val__ = __RREG32_SOC15_RLC__(reg__, 0, ip##_HWIP, idx); \ +\ + val__ &= ~REG_FIELD_MASK(reg_name, field); \ + val__ |= (val) << REG_FIELD_SHIFT(reg_name, field); \ + __WREG32_SOC15_RLC__(reg__, val__, 0, ip##_HWIP, idx); \ +} while (0) #define RREG32_SOC15(ip, inst, reg) \ __RREG32_SOC15_RLC__(adev->reg_offset[ip##_HWIP][inst][reg##_BASE_IDX] + reg, \ @@ -177,12 +181,15 @@ WREG32_RLC_EX(prefix, target_reg, value, inst); \ } while (0) -#define WREG32_FIELD15_RLC(ip, idx, reg, field, val) \ - __WREG32_SOC15_RLC__((adev->reg_offset[ip##_HWIP][idx][mm##reg##_BASE_IDX] + mm##reg), \ - (__RREG32_SOC15_RLC__(adev->reg_offset[ip##_HWIP][idx][mm##reg##_BASE_IDX] + mm##reg, \ - AMDGPU_REGS_RLC, ip##_HWIP, idx) & \ - ~REG_FIELD_MASK(reg, field)) | (val) << REG_FIELD_SHIFT(reg, field), \ - AMDGPU_REGS_RLC, ip##_HWIP, idx) +#define WREG32_FIELD15_RLC(ip, idx, reg_name, field, val) \ +do { \ + u32 reg__ = adev->reg_offset[ip##_HWIP][idx][mm##reg_name##_BASE_IDX] + mm##reg_name; \ + u32 val__ = __RREG32_SOC15_RLC__(reg__, AMDGPU_REGS_RLC, ip##_HWIP, idx); \ +\ + val__ &= ~REG_FIELD_MASK(reg_name, field); \ + val__ |= (val) << REG_FIELD_SHIFT(reg_name, field); \ + __WREG32_SOC15_RLC__(reg__, val__, AMDGPU_REGS_RLC, ip##_HWIP, idx); \ +} while (0) #define WREG32_SOC15_OFFSET_RLC(ip, inst, reg, offset, value) \ __WREG32_SOC15_RLC__((adev->reg_offset[ip##_HWIP][inst][reg##_BASE_IDX] + reg) + offset, value, AMDGPU_REGS_RLC, ip##_HWIP, inst) -- cgit v1.2.3 From bc06579ca29dee9c245a41b12e39c7bb6938af5d Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Mon, 25 May 2026 13:33:17 +0200 Subject: drm/amdgpu: Respect placement requirements in amdgpu_gtt_mgr functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When testing intersection and compatibility, respect the actual placement requirements. This is a pre-requisite for ensuring that UVD CS BOs do not cross 256M segments. Fixes: ded910f368a5 ("drm/amdgpu: Implement intersect/compatible functions") Suggested-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c | 30 +++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c index d23a91d029aa..0ea32561c4bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c @@ -272,7 +272,20 @@ static bool amdgpu_gtt_mgr_intersects(struct ttm_resource_manager *man, const struct ttm_place *place, size_t size) { - return !place->lpfn || amdgpu_gtt_mgr_has_gart_addr(res); + const struct drm_mm_node *const node = &to_ttm_range_mgr_node(res)->mm_nodes[0]; + const u32 num_pages = PFN_UP(size); + + if (!place->lpfn) + return true; + + if (!amdgpu_gtt_mgr_has_gart_addr(res)) + return false; + + if (place->fpfn >= (node->start + num_pages) || + (place->lpfn && place->lpfn <= node->start)) + return false; + + return true; } /** @@ -290,7 +303,20 @@ static bool amdgpu_gtt_mgr_compatible(struct ttm_resource_manager *man, const struct ttm_place *place, size_t size) { - return !place->lpfn || amdgpu_gtt_mgr_has_gart_addr(res); + const struct drm_mm_node *const node = &to_ttm_range_mgr_node(res)->mm_nodes[0]; + const u32 num_pages = PFN_UP(size); + + if (!place->lpfn) + return true; + + if (!amdgpu_gtt_mgr_has_gart_addr(res)) + return false; + + if (node->start < place->fpfn || + (place->lpfn && (node->start + num_pages) > place->lpfn)) + return false; + + return true; } /** -- cgit v1.2.3 From 21fd45e5e2628d00b478590bcc3d14d3de5d45b6 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Mon, 25 May 2026 13:33:18 +0200 Subject: drm/amdgpu: Fix amdgpu_bo_move() when old_mem and new_mem are both GTT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UVD code relies on GTT to GTT moves in order to ensure that its BOs don't cross 256M segments. Fixes: bfe5e585b44f ("drm/ttm: move last binding into the drivers.") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 2740de94e93c..16c060badaee 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -515,6 +515,15 @@ static int amdgpu_bo_move(struct ttm_buffer_object *bo, bool evict, if (new_mem->mem_type == TTM_PL_TT || new_mem->mem_type == AMDGPU_PL_PREEMPT) { + if (old_mem && (old_mem->mem_type == TTM_PL_TT || + old_mem->mem_type == AMDGPU_PL_PREEMPT)) { + r = ttm_bo_wait_ctx(bo, ctx); + if (r) + return r; + + amdgpu_ttm_backend_unbind(bo->bdev, bo->ttm); + } + r = amdgpu_ttm_backend_bind(bo->bdev, bo->ttm, new_mem); if (r) return r; @@ -549,6 +558,15 @@ static int amdgpu_bo_move(struct ttm_buffer_object *bo, bool evict, ttm_bo_assign_mem(bo, new_mem); return 0; } + if ((old_mem->mem_type == TTM_PL_TT || + old_mem->mem_type == AMDGPU_PL_PREEMPT) && + (new_mem->mem_type == TTM_PL_TT || + new_mem->mem_type == AMDGPU_PL_PREEMPT)) { + amdgpu_bo_move_notify(bo, evict, new_mem); + ttm_resource_free(bo, &bo->resource); + ttm_bo_assign_mem(bo, new_mem); + return 0; + } if (old_mem->mem_type == AMDGPU_PL_GDS || old_mem->mem_type == AMDGPU_PL_GWS || -- cgit v1.2.3 From 01b8dfc0660db5d6cdd62c22dc20f774a26ce853 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Mon, 25 May 2026 13:33:19 +0200 Subject: drm/amdgpu/uvd: Place VCPU BO only in VRAM for UVD 4.x and older MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These UVD versions don't fully support GPUVM and are only validated to work when their VCPU BO is placed in VRAM. Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 3a3bc0d370fa..1e59ca924abe 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -188,6 +188,7 @@ int amdgpu_uvd_sw_init(struct amdgpu_device *adev) const struct common_firmware_header *hdr; unsigned int family_id; int i, j, r; + u32 vcpu_bo_domain; INIT_DELAYED_WORK(&adev->uvd.idle_work, amdgpu_uvd_idle_work_handler); @@ -319,12 +320,20 @@ int amdgpu_uvd_sw_init(struct amdgpu_device *adev) if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) bo_size += AMDGPU_GPU_PAGE_ALIGN(le32_to_cpu(hdr->ucode_size_bytes) + 8); + /* UVD 5.0 and newer HW can use 64 bit addressing. */ + adev->uvd.address_64_bit = + !amdgpu_device_ip_block_version_cmp(adev, AMD_IP_BLOCK_TYPE_UVD, 5, 0); + + vcpu_bo_domain = AMDGPU_GEM_DOMAIN_VRAM; + if (adev->uvd.address_64_bit) + vcpu_bo_domain |= AMDGPU_GEM_DOMAIN_GTT; + for (j = 0; j < adev->uvd.num_uvd_inst; j++) { if (adev->uvd.harvest_config & (1 << j)) continue; + r = amdgpu_bo_create_kernel(adev, bo_size, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_VRAM | - AMDGPU_GEM_DOMAIN_GTT, + vcpu_bo_domain, &adev->uvd.inst[j].vcpu_bo, &adev->uvd.inst[j].gpu_addr, &adev->uvd.inst[j].cpu_addr); @@ -339,10 +348,6 @@ int amdgpu_uvd_sw_init(struct amdgpu_device *adev) adev->uvd.filp[i] = NULL; } - /* from uvd v5.0 HW addressing capacity increased to 64 bits */ - if (!amdgpu_device_ip_block_version_cmp(adev, AMD_IP_BLOCK_TYPE_UVD, 5, 0)) - adev->uvd.address_64_bit = true; - r = amdgpu_uvd_create_msg_bo_helper(adev, 128 << 10, &adev->uvd.ib_bo); if (r) return r; -- cgit v1.2.3 From cbfd4d3fc2061a1ec8e9d36e65973ac3e813358a Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Mon, 25 May 2026 13:33:20 +0200 Subject: drm/amdgpu/uvd: Fix forcing MSG, FB BOs into VCPU segment when it isn't at 0 (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UVD 4.x and older can only access MSG, FEEDBACK buffers from a specific 256M VRAM segment that the VCPU BO is also located in. We already modify all placements of the given BO to ensure the BO is placed within this segment. Previously, it always assumed that the VCPU segment is the first 256M of VRAM, even though under some conditions the VCPU BO could be allocated outside this segment, which made UVD non-functional as the BOs were not inside the same segment as the UVD VCPU BO. Solve that by using the segment where the VCPU BO actually is. This fixes an issue with UVD failing to initialize on SI/CIK when resizable BAR is enabled and the VCPU BO is allocated in a different segment. v2: - For other BOs, keep using the same UVD segment as before. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/3851 Reviewed-by: Christian König Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 1e59ca924abe..480bf88def46 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -135,7 +135,7 @@ MODULE_FIRMWARE(FIRMWARE_VEGA12); MODULE_FIRMWARE(FIRMWARE_VEGA20); static void amdgpu_uvd_idle_work_handler(struct work_struct *work); -static void amdgpu_uvd_force_into_uvd_segment(struct amdgpu_bo *abo); +static void amdgpu_uvd_force_into_vcpu_segment(struct amdgpu_bo *abo); static int amdgpu_uvd_create_msg_bo_helper(struct amdgpu_device *adev, uint32_t size, @@ -158,7 +158,7 @@ static int amdgpu_uvd_create_msg_bo_helper(struct amdgpu_device *adev, amdgpu_bo_kunmap(bo); amdgpu_bo_unpin(bo); amdgpu_bo_placement_from_domain(bo, AMDGPU_GEM_DOMAIN_VRAM); - amdgpu_uvd_force_into_uvd_segment(bo); + amdgpu_uvd_force_into_vcpu_segment(bo); r = ttm_bo_validate(&bo->tbo, &bo->placement, &ctx); if (r) goto err; @@ -550,6 +550,24 @@ void amdgpu_uvd_free_handles(struct amdgpu_device *adev, struct drm_file *filp) } } +static void amdgpu_uvd_force_into_vcpu_segment(struct amdgpu_bo *bo) +{ + struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); + struct amdgpu_bo *vcpu_bo = adev->uvd.inst[0].vcpu_bo; + struct amdgpu_res_cursor vcpu_cur; + + amdgpu_res_first(vcpu_bo->tbo.resource, 0, + amdgpu_bo_size(vcpu_bo), &vcpu_cur); + + bo->placement.num_placement = 1; + bo->placement.placement = &bo->placements[0]; + bo->placements[0].fpfn = ALIGN_DOWN(vcpu_cur.start, SZ_256M) >> PAGE_SHIFT; + bo->placements[0].lpfn = bo->placements[0].fpfn + (SZ_256M >> PAGE_SHIFT); + bo->placements[0].mem_type = vcpu_bo->tbo.resource->mem_type; + if (bo->placements[0].mem_type == TTM_PL_VRAM) + bo->placements[0].flags |= TTM_PL_FLAG_CONTIGUOUS; +} + static void amdgpu_uvd_force_into_uvd_segment(struct amdgpu_bo *abo) { int i; @@ -600,13 +618,10 @@ static int amdgpu_uvd_cs_pass1(struct amdgpu_uvd_cs_ctx *ctx) if (!ctx->parser->adev->uvd.address_64_bit) { /* check if it's a message or feedback command */ cmd = amdgpu_ib_get_value(ctx->ib, ctx->idx) >> 1; - if (cmd == 0x0 || cmd == 0x3) { - /* yes, force it into VRAM */ - uint32_t domain = AMDGPU_GEM_DOMAIN_VRAM; - - amdgpu_bo_placement_from_domain(bo, domain); - } - amdgpu_uvd_force_into_uvd_segment(bo); + if (cmd == 0x0 || cmd == 0x3) + amdgpu_uvd_force_into_vcpu_segment(bo); + else + amdgpu_uvd_force_into_uvd_segment(bo); r = ttm_bo_validate(&bo->tbo, &bo->placement, &tctx); } -- cgit v1.2.3 From 9d31190a40d77be192fabdcc5dfc4d7ad753c906 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:05:34 -0400 Subject: drm/amdgpu/jpeg: add flags for disabling KQs/UQs Add flags for handling disabling of kernel queues or user queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.h index 346ae0ab09d3..fe95d9188713 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.h @@ -149,6 +149,9 @@ struct amdgpu_jpeg { u32 *ip_dump; u32 reg_count; const struct amdgpu_hwip_reg_entry *reg_list; + + bool disable_uq; + bool disable_kq; }; int amdgpu_jpeg_sw_init(struct amdgpu_device *adev); -- cgit v1.2.3 From eb0afbcc61eba824d476158cc870d50e118d7780 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:07:15 -0400 Subject: drm/amdgpu/jpeg4.0.3: add support for disabling kernel queues Allow the user to disable kernel queues. This can be used to free up vmid resources if kernel queues are not needed. Set amdgpu.user_queue=2 to disable kernel queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c index 0c746580de11..b0bdb449538e 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c @@ -119,6 +119,19 @@ static int jpeg_v4_0_3_early_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; + switch (amdgpu_user_queue) { + case -1: + case 0: + default: + adev->jpeg.disable_kq = false; + adev->jpeg.disable_uq = true; + break; + case 2: + adev->jpeg.disable_kq = true; + adev->jpeg.disable_uq = true; + break; + } + adev->jpeg.num_jpeg_rings = AMDGPU_MAX_JPEG_RINGS_4_0_3; jpeg_v4_0_3_set_dec_ring_funcs(adev); @@ -175,6 +188,10 @@ static int jpeg_v4_0_3_sw_init(struct amdgpu_ip_block *ip_block) for (j = 0; j < adev->jpeg.num_jpeg_rings; ++j) { ring = &adev->jpeg.inst[i].ring_dec[j]; ring->use_doorbell = true; + if (adev->jpeg.disable_kq) { + ring->no_scheduler = true; + ring->no_user_submission = true; + } ring->vm_hub = AMDGPU_MMHUB0(adev->jpeg.inst[i].aid_id); if (!amdgpu_sriov_vf(adev)) { ring->doorbell_index = -- cgit v1.2.3 From 8b5585574e49159232dd58c6131dfb4c986d1ee5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:10:47 -0400 Subject: drm/amdgpu/jpeg5.0.1: add support for disabling kernel queues Allow the user to disable kernel queues. This can be used to free up vmid resources if kernel queues are not needed. Set amdgpu.user_queue=2 to disable kernel queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index 250316704dfa..e023ae958459 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -118,6 +118,19 @@ static int jpeg_v5_0_1_early_init(struct amdgpu_ip_block *ip_block) if (!adev->jpeg.num_jpeg_inst || adev->jpeg.num_jpeg_inst > AMDGPU_MAX_JPEG_INSTANCES) return -ENOENT; + switch (amdgpu_user_queue) { + case -1: + case 0: + default: + adev->jpeg.disable_kq = false; + adev->jpeg.disable_uq = true; + break; + case 2: + adev->jpeg.disable_kq = true; + adev->jpeg.disable_uq = true; + break; + } + adev->jpeg.num_jpeg_rings = AMDGPU_MAX_JPEG_RINGS; jpeg_v5_0_1_set_dec_ring_funcs(adev); jpeg_v5_0_1_set_irq_funcs(adev); @@ -172,6 +185,10 @@ static int jpeg_v5_0_1_sw_init(struct amdgpu_ip_block *ip_block) for (j = 0; j < adev->jpeg.num_jpeg_rings; ++j) { ring = &adev->jpeg.inst[i].ring_dec[j]; ring->use_doorbell = true; + if (adev->jpeg.disable_kq) { + ring->no_scheduler = true; + ring->no_user_submission = true; + } ring->vm_hub = AMDGPU_MMHUB0(adev->jpeg.inst[i].aid_id); if (!amdgpu_sriov_vf(adev)) { ring->doorbell_index = -- cgit v1.2.3 From dddf9044d1683ed512fa26e0762c4a4c94095097 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:06:27 -0400 Subject: drm/amdgpu/vcn: add flags for disabling KQs/UQs Add flags for handling disabling of kernel queues or user queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.h index 82624b44e661..bea95307fd42 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.h @@ -368,6 +368,9 @@ struct amdgpu_vcn { struct mutex workload_profile_mutex; u32 reg_count; const struct amdgpu_hwip_reg_entry *reg_list; + + bool disable_uq; + bool disable_kq; }; struct amdgpu_fw_shared_rb_ptrs_struct { -- cgit v1.2.3 From a63e82d499315fd94d105de073b4151aae450672 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:11:09 -0400 Subject: drm/amdgpu/vcn4.0.3: add support for disabling kernel queues Allow the user to disable kernel queues. This can be used to free up vmid resources if kernel queues are not needed. Set amdgpu.user_queue=2 to disable kernel queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c index 7f001c32e911..3c3f3d1a040d 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c @@ -115,6 +115,19 @@ static int vcn_v4_0_3_early_init(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int i, r; + switch (amdgpu_user_queue) { + case -1: + case 0: + default: + adev->vcn.disable_kq = false; + adev->vcn.disable_uq = true; + break; + case 2: + adev->vcn.disable_kq = true; + adev->vcn.disable_uq = true; + break; + } + for (i = 0; i < adev->vcn.num_vcn_inst; ++i) /* re-use enc ring as unified ring */ adev->vcn.inst[i].num_enc_rings = 1; @@ -217,6 +230,10 @@ static int vcn_v4_0_3_sw_init(struct amdgpu_ip_block *ip_block) ring = &adev->vcn.inst[i].ring_enc[0]; ring->use_doorbell = true; + if (adev->vcn.disable_kq) { + ring->no_scheduler = true; + ring->no_user_submission = true; + } if (!amdgpu_sriov_vf(adev)) ring->doorbell_index = -- cgit v1.2.3 From db15c49a17b8fab9ada38f3a548e5353d7d6b1a8 Mon Sep 17 00:00:00 2001 From: YiPeng Chai Date: Tue, 16 Jun 2026 16:18:22 +0800 Subject: drm/amdgpu: add bounds check to prevent array overflow Add bounds check to prevent array overflow. v2: Add warning messages. Signed-off-by: YiPeng Chai Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 764cd4950408..58dd8f29734e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -5064,6 +5064,13 @@ static void amdgpu_register_bad_pages_mca_notifier(struct amdgpu_device *adev) * Use this list instead of mgpu_info to find the amdgpu * device on which the UMC error was reported. */ + if (mce_adev_list.num_gpu >= MAX_GPU_INSTANCE) { + dev_warn_ratelimited(adev->dev, + "mce_adev_list full, skip notifier registration (max=%d)\n", + MAX_GPU_INSTANCE); + return; + } + mce_adev_list.devs[mce_adev_list.num_gpu++] = adev; /* -- cgit v1.2.3 From d73289a64e802b4b60d6501a3964682d8db2ac0f Mon Sep 17 00:00:00 2001 From: YiPeng Chai Date: Tue, 16 Jun 2026 16:24:13 +0800 Subject: drm/amd/ras: use IS_ERR() to check thread creation result Use IS_ERR() to check thread creation result. Signed-off-by: YiPeng Chai Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_process.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_process.c b/drivers/gpu/drm/amd/ras/rascore/ras_process.c index 3267dcdb169c..c001074c8c56 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_process.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_process.c @@ -248,9 +248,10 @@ int ras_process_init(struct ras_core_context *ras_core) ras_proc->ras_process_thread = kthread_run(ras_process_thread, (void *)ras_core, "ras_process_thread"); - if (!ras_proc->ras_process_thread) { + if (IS_ERR(ras_proc->ras_process_thread)) { RAS_DEV_ERR(ras_core->dev, "Failed to create ras_process_thread.\n"); - ret = -ENOMEM; + ret = PTR_ERR(ras_proc->ras_process_thread); + ras_proc->ras_process_thread = NULL; goto err; } -- cgit v1.2.3 From c3988a7ad4799514447294f04f063b422e0551df Mon Sep 17 00:00:00 2001 From: Jiqian Chen Date: Thu, 4 Jun 2026 18:30:23 +0800 Subject: drm/amdgpu/gfx9: Fix Ring and IB test fail after mode2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For Renior APU with gfx9, in some test scenarios with disabling ring_reset, like accessing an unmapped invalid address, it can trigger a gpu job timeout event, then driver uses Mode2 reset to reset GPU, but after Mode2 compute Ring test and IB test fail randomly. It because the HQDs of MECs are always active before or after Mode2, that causes MECs use stale HQDs when MECs are unhalted before driver restore MQDs, and causes CPC and CPF are still stuck after Mode2, then causes compute Ring and IB tests fail. So, add sequences to deactivate HQDs of MECs in suspend IP function of the resetting process. v2: Move all sequences into a new function gfx_v9_0_cp_mode2_clear_state (Ray Huang) To check reset Mode2 method in the if condition (Ray Huang) v3: Move all sequences before Mode2 instead of after Mode2 (Timur Kristóf) v4: Call amdgpu_gfx_rlc_enter/exit_safe_mode int the begin and end of gfx_v9_0_deactivate_kcq_hqd (Alex Deucher) Signed-off-by: Jiqian Chen Reviewed-by: Huang Rui Reviewed-by: Timur Kristóf Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 6d52b19a5f1c..f836621c46eb 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -4071,6 +4071,41 @@ err_priv_inst: return r; } +static void gfx_v9_0_deactivate_kcq_hqd(struct amdgpu_device *adev) +{ + amdgpu_gfx_rlc_enter_safe_mode(adev, 0); + for (int i = 0; i < adev->gfx.num_compute_rings; i++) { + u32 tmp; + struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; + + mutex_lock(&adev->srbm_mutex); + soc15_grbm_select(adev, ring->me, ring->pipe, ring->queue, 0, 0); + tmp = RREG32_SOC15(GC, 0, mmCP_HQD_ACTIVE); + /* disable the queue if it's active */ + if (tmp & CP_HQD_ACTIVE__ACTIVE_MASK) { + int j; + + WREG32_SOC15(GC, 0, mmCP_HQD_DEQUEUE_REQUEST, 1); + for (j = 0; j < adev->usec_timeout; j++) { + tmp = RREG32_SOC15(GC, 0, mmCP_HQD_ACTIVE); + if (!(tmp & CP_HQD_ACTIVE__ACTIVE_MASK)) + break; + udelay(1); + } + if (j == AMDGPU_MAX_USEC_TIMEOUT) { + DRM_DEBUG("comp_%u_%u_%u dequeue request failed.\n", + ring->me, ring->pipe, ring->queue); + /* Manual disable if dequeue request times out */ + WREG32_SOC15(GC, 0, mmCP_HQD_ACTIVE, 0); + } + WREG32_SOC15(GC, 0, mmCP_HQD_DEQUEUE_REQUEST, 0); + } + soc15_grbm_select(adev, 0, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + } + amdgpu_gfx_rlc_exit_safe_mode(adev, 0); +} + static int gfx_v9_0_hw_fini(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -4095,6 +4130,10 @@ static int gfx_v9_0_hw_fini(struct amdgpu_ip_block *ip_block) return 0; } + if ((adev->flags & AMD_IS_APU) && amdgpu_in_reset(adev) && + amdgpu_asic_reset_method(adev) == AMD_RESET_METHOD_MODE2) + gfx_v9_0_deactivate_kcq_hqd(adev); + /* Use deinitialize sequence from CAIL when unbinding device from driver, * otherwise KIQ is hanging when binding back */ -- cgit v1.2.3 From a60ea15807126b148a328051636977a33ad0e9bb Mon Sep 17 00:00:00 2001 From: Gerhard Schwanzer Date: Tue, 16 Jun 2026 10:56:06 +0000 Subject: drm/amdkfd: Use exclusive bounds for SVM split alignment checks SVM ranges use inclusive page indices: prange->last is the last page in the range. The split-remap logic introduced by commit 448ee45353ef ("drm/amdkfd: Use huge page size to check split svm range alignment") uses ALIGN_DOWN(prange->last, 512) to determine whether the original range can contain a 2MB huge-page mapping. That aligns the last page itself down. Thus a range ending one page before the next 2MB boundary is classified as if the final 2MB block did not exist. When such a range is split inside that final block, the split head or tail can be left off the remap list even though it was derived from an original range that may have PMD mappings. Use prange->last + 1 as the exclusive upper bound when computing the original range's last 2MB-aligned boundary. Then use the actual split boundary for the head and tail alignment checks: tail->start for a tail split, and new_start for a head split. new_start is equivalent to head->last + 1 and directly names the exclusive end of the split head. Using head->last for the head-side check can both remap a head that ends exactly one page before a 2MB boundary and miss a head whose split boundary is one page after such a boundary. Philip Yang pointed out in the review of the original change that this condition should use head->last + 1 or new_start. Xiaogang Chen identified the inclusive-last cause and posted the candidate fix in the regression thread. With the culprit change active and the local revert not applied, the unchanged C/HSA reproducer completes 10/10 runs with this change on an RX 7600 XT. Fixes: 448ee45353ef ("drm/amdkfd: Use huge page size to check split svm range alignment") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/4914 Link: https://lore.kernel.org/stable/IA1PR12MB85172F7FE9157C092EDA46A0E3112@IA1PR12MB8517.namprd12.prod.outlook.com/ Link: https://lore.kernel.org/all/32ce2b72-aa16-4202-9f99-92e3cd4408bc@amd.com/ Suggested-by: Xiaogang Chen Acked-by: Alex Deucher Signed-off-by: Gerhard Schwanzer Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 5a56d86b3ecf..0900bb23349e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1144,7 +1144,7 @@ static int svm_range_split_tail(struct svm_range *prange, uint64_t new_last, struct list_head *insert_list, struct list_head *remap_list) { - unsigned long last_align_down = ALIGN_DOWN(prange->last, 512); + unsigned long last_align_down = ALIGN_DOWN(prange->last + 1, 512); unsigned long start_align = ALIGN(prange->start, 512); bool huge_page_mapping = last_align_down > start_align; struct svm_range *tail = NULL; @@ -1168,7 +1168,7 @@ static int svm_range_split_head(struct svm_range *prange, uint64_t new_start, struct list_head *insert_list, struct list_head *remap_list) { - unsigned long last_align_down = ALIGN_DOWN(prange->last, 512); + unsigned long last_align_down = ALIGN_DOWN(prange->last + 1, 512); unsigned long start_align = ALIGN(prange->start, 512); bool huge_page_mapping = last_align_down > start_align; struct svm_range *head = NULL; @@ -1181,8 +1181,8 @@ svm_range_split_head(struct svm_range *prange, uint64_t new_start, list_add(&head->list, insert_list); - if (huge_page_mapping && head->last + 1 > start_align && - head->last + 1 < last_align_down && (!IS_ALIGNED(head->last, 512))) + if (huge_page_mapping && new_start > start_align && + new_start < last_align_down && !IS_ALIGNED(new_start, 512)) list_add(&head->update_list, remap_list); return 0; -- cgit v1.2.3 From 940d33ebbcdebaf095fade86e9c981ad8789aee2 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 13 May 2026 19:08:47 +0200 Subject: amdgpu/ih6.1: Fix minor version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report the correct version of IH v6.1 (previously it showed v6.0). Reviewed-by: Tvrtko Ursulin Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/ih_v6_1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c b/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c index 95b3f4e55ec3..699c274d357e 100644 --- a/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c +++ b/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c @@ -790,7 +790,7 @@ static void ih_v6_1_set_interrupt_funcs(struct amdgpu_device *adev) const struct amdgpu_ip_block_version ih_v6_1_ip_block = { .type = AMD_IP_BLOCK_TYPE_IH, .major = 6, - .minor = 0, + .minor = 1, .rev = 0, .funcs = &ih_v6_1_ip_funcs, }; -- cgit v1.2.3 From 3cdff3c8b93c2834977224d9c2b201fc334dd184 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 13 May 2026 19:08:49 +0200 Subject: drm/amdgpu: Use system unbound workqueue for soft IH ring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow the kernel to dispatch the soft IH work on other CPUs. Otherwise it can happen that the soft IH ring fills up before it actually starts processing anything, which can easily happen with retry page faults, in which case the CP repeatedly spams the CPU with a lot of interrupts. This significantly improves retry page fault handling on GPUs that don't have the filter CAM and must rely on software based filtering. Reviewed-by: Tvrtko Ursulin Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c index 40b8506ac66f..53be764968e4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -545,7 +545,7 @@ void amdgpu_irq_delegate(struct amdgpu_device *adev, unsigned int num_dw) { amdgpu_ih_ring_write(adev, &adev->irq.ih_soft, entry->iv_entry, num_dw); - schedule_work(&adev->irq.ih_soft_work); + queue_work(system_unbound_wq, &adev->irq.ih_soft_work); } /** -- cgit v1.2.3 From 27f4f5546e36cdb63fc5d045758091f0b1166817 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Tue, 9 Jun 2026 20:13:15 +0800 Subject: drm/amdgpu: set the userq xcp_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initialize the userq xcp_id. Signed-off-by: Prike Liang Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 3644e9193f58..285dcadb9342 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -627,6 +627,8 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) queue->queue_type = args->in.ip_type; queue->vm = &fpriv->vm; queue->priority = priority; + queue->xcp_id = (fpriv->xcp_id != AMDGPU_XCP_NO_PARTITION) ? + fpriv->xcp_id : 0; queue->userq_mgr = uq_mgr; INIT_DELAYED_WORK(&queue->hang_detect_work, amdgpu_userq_hang_detect_work); -- cgit v1.2.3 From f41e74f2111c1f8a31822e0e43db2edcc3125100 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Wed, 20 May 2026 11:22:12 +0800 Subject: drm/amdgpu: add userq create and destroy tracepoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ftrace events around user queue creation and destruction to profile queue setup and teardown latency. Signed-off-by: Prike Liang Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h | 58 +++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 9 +++++ 2 files changed, 67 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h index 85724ec6aaf8..0d5fb5daddfe 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h @@ -582,6 +582,64 @@ TRACE_EVENT(amdgpu_reset_reg_dumps, __entry->value) ); +DECLARE_EVENT_CLASS(amdgpu_userq_queue, + TP_PROTO(struct amdgpu_usermode_queue *queue), + TP_ARGS(queue), + TP_STRUCT__entry( + __field(void *, queue) + __field(u64, doorbell_index) + __field(int, queue_type) + __field(int, state) + __field(u32, xcp_id) + ), + TP_fast_assign( + __entry->queue = queue; + __entry->doorbell_index = queue->doorbell_index; + __entry->queue_type = queue->queue_type; + __entry->state = queue->state; + __entry->xcp_id = queue->xcp_id; + ), + TP_printk("queue=%p, doorbell=%llu, type=%d, state=%d, xcp_id=%u", + __entry->queue, __entry->doorbell_index, + __entry->queue_type, __entry->state, __entry->xcp_id) +); +DEFINE_EVENT(amdgpu_userq_queue, amdgpu_userq_create_start, + TP_PROTO(struct amdgpu_usermode_queue *queue), + TP_ARGS(queue)); +DEFINE_EVENT(amdgpu_userq_queue, amdgpu_userq_destroy_start, + TP_PROTO(struct amdgpu_usermode_queue *queue), + TP_ARGS(queue)); +DECLARE_EVENT_CLASS(amdgpu_userq_queue_result, + TP_PROTO(struct amdgpu_usermode_queue *queue, int result), + TP_ARGS(queue, result), + TP_STRUCT__entry( + __field(void *, queue) + __field(u64, doorbell_index) + __field(int, queue_type) + __field(int, state) + __field(u32, xcp_id) + __field(int, result) + ), + TP_fast_assign( + __entry->queue = queue; + __entry->doorbell_index = queue->doorbell_index; + __entry->queue_type = queue->queue_type; + __entry->state = queue->state; + __entry->xcp_id = queue->xcp_id; + __entry->result = result; + ), + TP_printk("queue=%p, doorbell=%llu, type=%d, state=%d, xcp_id=%u, result=%d", + __entry->queue, __entry->doorbell_index, + __entry->queue_type, __entry->state, + __entry->xcp_id, __entry->result) +); +DEFINE_EVENT(amdgpu_userq_queue_result, amdgpu_userq_create_end, + TP_PROTO(struct amdgpu_usermode_queue *queue, int result), + TP_ARGS(queue, result)); +DEFINE_EVENT(amdgpu_userq_queue_result, amdgpu_userq_destroy_end, + TP_PROTO(struct amdgpu_usermode_queue *queue, int result), + TP_ARGS(queue, result)); + #undef AMDGPU_JOB_GET_TIMELINE_NAME #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 285dcadb9342..b8aa2adc399e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -33,6 +33,7 @@ #include "amdgpu_userq.h" #include "amdgpu_hmm.h" #include "amdgpu_userq_fence.h" +#include "amdgpu_trace.h" u32 amdgpu_userq_get_supported_ip_mask(struct amdgpu_device *adev) { @@ -505,6 +506,8 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que const struct amdgpu_userq_funcs *uq_funcs = adev->userq_funcs[queue->queue_type]; int r = 0; + trace_amdgpu_userq_destroy_start(queue); + cancel_delayed_work_sync(&uq_mgr->resume_work); /* Cancel any pending hang detection work and cleanup */ @@ -530,6 +533,7 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que amdgpu_bo_unreserve(queue->db_obj.obj); amdgpu_bo_unref(&queue->db_obj.obj); + trace_amdgpu_userq_destroy_end(queue, r); kfree(queue); pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -671,6 +675,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) } queue->doorbell_index = index; + trace_amdgpu_userq_create_start(queue); r = uq_funcs->mqd_create(queue, &args->in); if (r) { drm_file_err(uq_mgr->file, "Failed to create Queue\n"); @@ -694,6 +699,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) r = amdgpu_userq_map_helper(queue); if (r) { drm_file_err(uq_mgr->file, "Failed to map Queue\n"); + trace_amdgpu_userq_create_end(queue, r); mutex_unlock(&uq_mgr->userq_mutex); goto erase_doorbell; } @@ -710,11 +716,13 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) * This drops the last reference which should take care of * all cleanup. */ + trace_amdgpu_userq_create_end(queue, r); amdgpu_userq_put(queue); return r; } amdgpu_debugfs_userq_init(filp, queue, qid); + trace_amdgpu_userq_create_end(queue, 0); args->out.queue_id = qid; return 0; @@ -730,6 +738,7 @@ clean_doorbell_bo: free_fence_drv: amdgpu_userq_fence_driver_free(queue); free_queue: + trace_amdgpu_userq_create_end(queue, r); kfree(queue); err_pm_runtime: pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); -- cgit v1.2.3 From f8c38071b96e8231110b8e8755ebc835a058ef56 Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Wed, 27 May 2026 16:59:44 +0800 Subject: drm/amdgpu: add userq job and state transition trace events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ftrace events for tracking the userq fence emit, signal and queue state transition. Signed-off-by: Pierre-Eric Pelloux-Prayer Co-developed-by: Prike Liang Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h | 92 +++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 21 ++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 10 ++- 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h index 0d5fb5daddfe..5324030a13f5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h @@ -28,6 +28,8 @@ #include #include +#include "amdgpu_userq_fence.h" + #undef TRACE_SYSTEM #define TRACE_SYSTEM amdgpu #define TRACE_INCLUDE_FILE amdgpu_trace @@ -640,6 +642,96 @@ DEFINE_EVENT(amdgpu_userq_queue_result, amdgpu_userq_destroy_end, TP_PROTO(struct amdgpu_usermode_queue *queue, int result), TP_ARGS(queue, result)); +TRACE_EVENT(amdgpu_userq_emit_fence, + TP_PROTO(struct device *device, struct amdgpu_usermode_queue *queue, struct amdgpu_userq_fence *fence), + TP_ARGS(device, queue, fence), + TP_STRUCT__entry( + __field(u64, fence_context) + __field(u64, fence_seqno) + __string(dev, dev_name(device)) + __field(u64, doorbell_index) + __field(u64, client_id) + __field(u32, queue_type) + ), + TP_fast_assign( + __entry->fence_context = fence->base.context; + __entry->fence_seqno = fence->base.seqno; + __assign_str(dev); + __entry->doorbell_index = queue->doorbell_index; + __entry->client_id = queue->userq_mgr->file->client_id; + __entry->queue_type = queue->queue_type; + ), + TP_printk("dev=%s, client_id=%llu, type=%u, doorbell=%llu, fence=%llu:%llu", + __get_str(dev), __entry->client_id, __entry->queue_type, __entry->doorbell_index, + __entry->fence_context, + __entry->fence_seqno) +); + +TRACE_EVENT(amdgpu_userq_wait_deps, + TP_PROTO(struct device *device, struct amdgpu_usermode_queue *queue, struct amdgpu_userq_fence *dep), + TP_ARGS(device, queue, dep), + TP_STRUCT__entry( + __field(u64, context) + __field(u64, dep_context) + __field(u64, dep_seqno) + __string(dev, dev_name(device)) + __field(u64, doorbell_index) + __field(u64, client_id) + __field(u32, queue_type) + ), + TP_fast_assign( + __assign_str(dev); + __entry->doorbell_index = queue->doorbell_index; + __entry->queue_type = queue->queue_type; + __entry->client_id = queue->userq_mgr->file->client_id; + __entry->context = queue->fence_drv->context; + __entry->dep_context = dep->base.context; + __entry->dep_seqno = dep->base.seqno; + ), + TP_printk("dev=%s, client_id=%llu, type=%u, doorbell=%llu, context=%llu depends on fence=%llu:%llu", + __get_str(dev), __entry->client_id, __entry->queue_type, __entry->doorbell_index, __entry->context, + __entry->dep_context, + __entry->dep_seqno) +); + +TRACE_EVENT(amdgpu_userq_state_start, + TP_PROTO(struct amdgpu_usermode_queue *queue), + TP_ARGS(queue), + TP_STRUCT__entry( + __field(u64, doorbell_index) + __field(u64, client_id) + __field(u32, queue_type) + __field(u32, from) + ), + TP_fast_assign( + __entry->doorbell_index = queue->doorbell_index; + __entry->queue_type = queue->queue_type; + __entry->client_id = queue->userq_mgr->file->client_id; + __entry->from = queue->state; + ), + TP_printk("client_id=%llu, type=%u, doorbell=%llu, from=%d", + __entry->client_id, __entry->queue_type, __entry->doorbell_index, __entry->from) +); + +TRACE_EVENT(amdgpu_userq_state_changed, + TP_PROTO(struct amdgpu_usermode_queue *queue, enum amdgpu_userq_state new_state), + TP_ARGS(queue, new_state), + TP_STRUCT__entry( + __field(u64, doorbell_index) + __field(u64, client_id) + __field(u32, queue_type) + __field(u32, to) + ), + TP_fast_assign( + __entry->doorbell_index = queue->doorbell_index; + __entry->queue_type = queue->queue_type; + __entry->client_id = queue->userq_mgr->file->client_id; + __entry->to = new_state; + ), + TP_printk("client_id=%llu, type=%u, doorbell=%llu, to=%d", + __entry->client_id, __entry->queue_type, __entry->doorbell_index, __entry->to) +); + #undef AMDGPU_JOB_GET_TIMELINE_NAME #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index b8aa2adc399e..4494a98026cb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -292,11 +292,15 @@ static int amdgpu_userq_preempt_helper(struct amdgpu_usermode_queue *queue) int r; if (queue->state == AMDGPU_USERQ_STATE_MAPPED) { + trace_amdgpu_userq_state_start(queue); + r = userq_funcs->preempt(queue); if (r) { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); queue->state = AMDGPU_USERQ_STATE_HUNG; return r; } else { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_PREEMPTED); queue->state = AMDGPU_USERQ_STATE_PREEMPTED; } } @@ -312,10 +316,14 @@ static int amdgpu_userq_restore_helper(struct amdgpu_usermode_queue *queue) int r = 0; if (queue->state == AMDGPU_USERQ_STATE_PREEMPTED) { + trace_amdgpu_userq_state_start(queue); + r = userq_funcs->restore(queue); if (r) { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); queue->state = AMDGPU_USERQ_STATE_HUNG; } else { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_MAPPED); queue->state = AMDGPU_USERQ_STATE_MAPPED; } } @@ -333,12 +341,15 @@ static int amdgpu_userq_unmap_helper(struct amdgpu_usermode_queue *queue) if ((queue->state == AMDGPU_USERQ_STATE_MAPPED) || (queue->state == AMDGPU_USERQ_STATE_PREEMPTED)) { + trace_amdgpu_userq_state_start(queue); r = userq_funcs->unmap(queue); if (r) { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); queue->state = AMDGPU_USERQ_STATE_HUNG; return r; } else { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_UNMAPPED); queue->state = AMDGPU_USERQ_STATE_UNMAPPED; } } @@ -355,11 +366,15 @@ static int amdgpu_userq_map_helper(struct amdgpu_usermode_queue *queue) int r; if (queue->state == AMDGPU_USERQ_STATE_UNMAPPED) { + trace_amdgpu_userq_state_start(queue); + r = userq_funcs->map(queue); if (r) { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); queue->state = AMDGPU_USERQ_STATE_HUNG; return r; } else { + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_MAPPED); queue->state = AMDGPU_USERQ_STATE_MAPPED; } } @@ -888,6 +903,7 @@ amdgpu_userq_restore_all(struct amdgpu_userq_mgr *uq_mgr) if (!amdgpu_userq_buffer_vas_mapped(queue)) { drm_file_err(uq_mgr->file, "trying restore queue without va mapping\n"); + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_INVALID_VA); queue->state = AMDGPU_USERQ_STATE_INVALID_VA; continue; } @@ -1386,12 +1402,14 @@ void amdgpu_userq_pre_reset(struct amdgpu_device *adev) if (queue->state != AMDGPU_USERQ_STATE_MAPPED) continue; + trace_amdgpu_userq_state_start(queue); userq_funcs = adev->userq_funcs[queue->queue_type]; userq_funcs->unmap(queue); /* just mark all queues as hung at this point. * if unmap succeeds, we could map again * in amdgpu_userq_post_reset() if vram is not lost */ + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); queue->state = AMDGPU_USERQ_STATE_HUNG; amdgpu_userq_fence_driver_force_completion(queue); } @@ -1410,6 +1428,8 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { if (queue->state == AMDGPU_USERQ_STATE_HUNG && !vram_lost) { + trace_amdgpu_userq_state_start(queue); + userq_funcs = adev->userq_funcs[queue->queue_type]; /* Re-map queue */ r = userq_funcs->map(queue); @@ -1417,6 +1437,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) dev_err(adev->dev, "Failed to remap queue %ld\n", queue_id); continue; } + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_MAPPED); queue->state = AMDGPU_USERQ_STATE_MAPPED; } } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index f74ad378e407..7e80442ec3e5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -30,7 +30,7 @@ #include #include "amdgpu.h" -#include "amdgpu_userq_fence.h" +#include "amdgpu_trace.h" #define AMDGPU_USERQ_MAX_HANDLES (1U << 16) @@ -528,6 +528,8 @@ int amdgpu_userq_signal_ioctl(struct drm_device *dev, void *data, /* Create the new fence */ amdgpu_userq_fence_init(queue, fence, wptr); + trace_amdgpu_userq_emit_fence(dev->dev, queue, fence); + mutex_unlock(&userq_mgr->userq_mutex); /* @@ -701,7 +703,7 @@ amdgpu_userq_wait_add_fence(struct drm_amdgpu_userq_wait *wait_info, } static int -amdgpu_userq_wait_return_fence_info(struct drm_file *filp, +amdgpu_userq_wait_return_fence_info(struct drm_device *dev, struct drm_file *filp, struct drm_amdgpu_userq_wait *wait_info, u32 *syncobj_handles, u64 *timeline_points, u32 *timeline_handles, @@ -869,6 +871,8 @@ amdgpu_userq_wait_return_fence_info(struct drm_file *filp, amdgpu_userq_fence_driver_get(fence_drv); + trace_amdgpu_userq_wait_deps(dev->dev, waitq, userq_fence); + /* Store drm syncobj's gpu va address and value */ fence_info[cnt].va = fence_drv->va; fence_info[cnt].value = fences[i]->seqno; @@ -969,7 +973,7 @@ int amdgpu_userq_wait_ioctl(struct drm_device *dev, void *data, gobj_write, gobj_read); } else { - r = amdgpu_userq_wait_return_fence_info(filp, wait_info, + r = amdgpu_userq_wait_return_fence_info(dev, filp, wait_info, syncobj_handles, timeline_points, timeline_handles, -- cgit v1.2.3 From a28667f75a6cb5413f97732e25a5df74959b5bd8 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:11:35 -0400 Subject: drm/amdgpu/vcn5.0.1: add support for disabling kernel queues Allow the user to disable kernel queues. This can be used to free up vmid resources if kernel queues are not needed. Set amdgpu.user_queue=2 to disable kernel queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index d3db0494341e..95f55bab528a 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -94,6 +94,19 @@ static int vcn_v5_0_1_early_init(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int i, r; + switch (amdgpu_user_queue) { + case -1: + case 0: + default: + adev->vcn.disable_kq = false; + adev->vcn.disable_uq = true; + break; + case 2: + adev->vcn.disable_kq = true; + adev->vcn.disable_uq = true; + break; + } + for (i = 0; i < adev->vcn.num_vcn_inst; ++i) /* re-use enc ring as unified ring */ adev->vcn.inst[i].num_enc_rings = 1; @@ -188,6 +201,10 @@ static int vcn_v5_0_1_sw_init(struct amdgpu_ip_block *ip_block) ring = &adev->vcn.inst[i].ring_enc[0]; ring->use_doorbell = true; + if (adev->vcn.disable_kq) { + ring->no_scheduler = true; + ring->no_user_submission = true; + } if (!amdgpu_sriov_vf(adev)) ring->doorbell_index = (adev->doorbell_index.vcn.vcn_ring0_1 << 1) + -- cgit v1.2.3 From 991fd2cb908bf5d35a496760519442d6e9f8763d Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:12:07 -0400 Subject: drm/amdgpu/sdma4.4.2: add support for disabling kernel queues Allow the user to disable kernel queues. This can be used to free up vmid resources if kernel queues are not needed. Set amdgpu.user_queue=2 to disable kernel queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c index 88428b88e00f..777a70852883 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c @@ -1359,6 +1359,19 @@ static int sdma_v4_4_2_early_init(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int r; + switch (amdgpu_user_queue) { + case -1: + case 0: + default: + adev->sdma.no_user_submission = false; + adev->sdma.disable_uq = true; + break; + case 2: + adev->sdma.no_user_submission = true; + adev->sdma.disable_uq = true; + break; + } + r = sdma_v4_4_2_init_microcode(adev); if (r) return r; @@ -1478,6 +1491,7 @@ static int sdma_v4_4_2_sw_init(struct amdgpu_ip_block *ip_block) /* doorbell size is 2 dwords, get DWORD offset */ ring->doorbell_index = adev->doorbell_index.sdma_engine[i] << 1; ring->vm_hub = AMDGPU_MMHUB0(aid_id); + ring->no_user_submission = adev->sdma.no_user_submission; sprintf(ring->name, "sdma%d.%d", aid_id, i % adev->sdma.num_inst_per_aid); -- cgit v1.2.3 From 5c64e5c768beca6ad1468aa6cc50307f54402053 Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Wed, 17 Jun 2026 16:51:18 +0800 Subject: drm/amdgpu: dump RAS EEPROM table via debugfs When the RAS core manages the EEPROM, the eeprom_control is never initialized (amdgpu_ras_init_badpage_info() returns early), so reading ras/ras_eeprom_table in debugfs printed only a zeroed header and no records, even though bad-page records exist in the RAS core EEPROM. Source the table header and records from the RAS core EEPROM (ras_core->ras_eeprom) in that case, reusing the existing output layout so the debugfs node keeps the same format. Skip the dump when the firmware manages the EEPROM, since the records are not stored in the I2C-backed table then. Signed-off-by: Xiang Liu Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index fca2b49bc13b..36f584f05e2f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -1398,6 +1398,86 @@ Out: return res < 0 ? res : orig_size - size; } +static ssize_t +amdgpu_ras_debugfs_table_read_uniras(struct amdgpu_device *adev, + char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_ras_mgr *ras_mgr = amdgpu_ras_mgr_get_context(adev); + struct ras_core_context *ras_core = ras_mgr ? ras_mgr->ras_core : NULL; + struct eeprom_umc_record *records = NULL; + struct ras_eeprom_control *control; + size_t bufsz, len = 0; + u32 num_recs; + char *kbuf; + ssize_t res; + int i; + + if (!ras_core) + return 0; + + /* pmfw manages eeprom data by itself */ + if (ras_fw_eeprom_supported(ras_core)) + return 0; + + control = &ras_core->ras_eeprom; + num_recs = ras_eeprom_get_record_count(ras_core); + + bufsz = strlen(tbl_hdr_str) + tbl_hdr_fmt_size + + strlen(rec_hdr_str) + (size_t)rec_hdr_fmt_size * num_recs + 1; + + kbuf = kvmalloc(bufsz, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + if (num_recs) { + records = kvcalloc(num_recs, sizeof(*records), GFP_KERNEL); + if (!records) { + res = -ENOMEM; + goto out; + } + + res = ras_eeprom_read(ras_core, records, num_recs); + if (res) + goto out; + } + + len += scnprintf(kbuf + len, bufsz - len, "%s", tbl_hdr_str); + len += scnprintf(kbuf + len, bufsz - len, tbl_hdr_fmt, + control->tbl_hdr.header, + control->tbl_hdr.version, + control->tbl_hdr.first_rec_offset, + control->tbl_hdr.tbl_size, + control->tbl_hdr.checksum); + len += scnprintf(kbuf + len, bufsz - len, "%s", rec_hdr_str); + + for (i = 0; i < num_recs; i++) { + u32 ai = RAS_RI_TO_AI(control, i); + int et = records[i].err_type; + const char *ets = (et >= 0 && et < AMDGPU_RAS_EEPROM_ERR_COUNT) ? + record_err_type_str[et] : "na"; + + len += scnprintf(kbuf + len, bufsz - len, rec_hdr_fmt, + i, + RAS_INDEX_TO_OFFSET(control, ai), + ets, + records[i].bank, + records[i].ts, + records[i].offset, + records[i].mem_channel, + records[i].mcumc_id, + records[i].retired_row_pfn); + } + + res = simple_read_from_buffer(buf, size, pos, kbuf, len); + +out: + kvfree(records); + kvfree(kbuf); + + return res; +} + static ssize_t amdgpu_ras_debugfs_eeprom_table_read(struct file *f, char __user *buf, size_t size, loff_t *pos) @@ -1411,6 +1491,10 @@ amdgpu_ras_debugfs_eeprom_table_read(struct file *f, char __user *buf, if (!size) return size; + if (amdgpu_uniras_enabled(adev)) + return amdgpu_ras_debugfs_table_read_uniras(adev, buf, + size, pos); + if (!ras || !control) { res = snprintf(data, sizeof(data), "Not supported\n"); if (*pos >= res) -- cgit v1.2.3 From 832f0aa050ff9780bc0902c0cbb0af55f3de618d Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 27 May 2026 16:12:34 -0400 Subject: drm/amdgpu/gfx9.4.3: add support for disabling kernel queues Allow the user to disable kernel queues. This can be used to free up vmid and HQD resources if kernel queues are not needed. Set amdgpu.user_queue=2 to disable kernel queues. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 119 +++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 18 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 71a2558acef8..e50a66e9ee96 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -1107,22 +1107,24 @@ static int gfx_v9_4_3_sw_init(struct amdgpu_ip_block *ip_block) /* set up the compute queues - allocate horizontally across pipes */ for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { ring_id = 0; - for (i = 0; i < adev->gfx.mec.num_mec; ++i) { - for (j = 0; j < adev->gfx.mec.num_queue_per_pipe; j++) { - for (k = 0; k < adev->gfx.mec.num_pipe_per_mec; - k++) { - if (!amdgpu_gfx_is_mec_queue_enabled( - adev, xcc_id, i, k, j)) - continue; - - r = gfx_v9_4_3_compute_ring_init(adev, - ring_id, - xcc_id, - i, k, j); - if (r) - return r; - - ring_id++; + if (!adev->gfx.disable_kq) { + for (i = 0; i < adev->gfx.mec.num_mec; ++i) { + for (j = 0; j < adev->gfx.mec.num_queue_per_pipe; j++) { + for (k = 0; k < adev->gfx.mec.num_pipe_per_mec; + k++) { + if (!amdgpu_gfx_is_mec_queue_enabled( + adev, xcc_id, i, k, j)) + continue; + + r = gfx_v9_4_3_compute_ring_init(adev, + ring_id, + xcc_id, + i, k, j); + if (r) + return r; + + ring_id++; + } } } } @@ -2350,6 +2352,65 @@ static void gfx_v9_4_3_xcc_fini(struct amdgpu_device *adev, int xcc_id) gfx_v9_4_3_xcc_cp_compute_enable(adev, false, xcc_id); } +static int gfx_v9_4_3_set_userq_eop_interrupts(struct amdgpu_device *adev, + bool enable) +{ + int num_xcc = NUM_XCC(adev->gfx.xcc_mask); + unsigned int irq_type; + int m, p, xcc_id, r; + + if (adev->gfx.disable_kq) { + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + for (m = 0; m < adev->gfx.mec.num_mec; ++m) { + for (p = 0; p < adev->gfx.mec.num_pipe_per_mec; p++) { + irq_type = AMDGPU_CP_IRQ_COMPUTE_MEC1_PIPE0_EOP + + (m * adev->gfx.mec.num_pipe_per_mec) + + p; + + if (enable) + r = amdgpu_irq_get(adev, &adev->gfx.eop_irq, + irq_type); + else + r = amdgpu_irq_put(adev, &adev->gfx.eop_irq, + irq_type); + if (r) { + if (!enable) + return r; + goto err_compute; + } + } + } + } + } + + return 0; + +err_compute: + for (p--; p >= 0; p--) { + irq_type = AMDGPU_CP_IRQ_COMPUTE_MEC1_PIPE0_EOP + + (m * adev->gfx.mec.num_pipe_per_mec) + p; + amdgpu_irq_put(adev, &adev->gfx.eop_irq, irq_type); + } + for (m--; 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; + amdgpu_irq_put(adev, &adev->gfx.eop_irq, irq_type); + } + } + for (xcc_id--; xcc_id >= 0; xcc_id--) { + 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; + amdgpu_irq_put(adev, &adev->gfx.eop_irq, irq_type); + } + } + } + + return r; +} + static int gfx_v9_4_3_hw_init(struct amdgpu_ip_block *ip_block) { int r; @@ -2382,9 +2443,14 @@ static int gfx_v9_4_3_hw_init(struct amdgpu_ip_block *ip_block) r = amdgpu_irq_get(adev, &adev->gfx.bad_op_irq, 0); if (r) goto err_bad_op; + r = gfx_v9_4_3_set_userq_eop_interrupts(adev, true); + if (r) + goto err_bad_eop; return 0; +err_bad_eop: + amdgpu_irq_put(adev, &adev->gfx.bad_op_irq, 0); err_bad_op: amdgpu_irq_put(adev, &adev->gfx.priv_inst_irq, 0); err_priv_inst: @@ -2467,6 +2533,7 @@ static int gfx_v9_4_3_hw_fini(struct amdgpu_ip_block *ip_block) amdgpu_irq_put(adev, &adev->gfx.bad_op_irq, 0); amdgpu_irq_put(adev, &adev->gfx.priv_inst_irq, 0); amdgpu_irq_put(adev, &adev->gfx.priv_reg_irq, 0); + gfx_v9_4_3_set_userq_eop_interrupts(adev, false); num_xcc = NUM_XCC(adev->gfx.xcc_mask); for (i = 0; i < num_xcc; i++) { @@ -2612,8 +2679,24 @@ static int gfx_v9_4_3_early_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; - adev->gfx.num_compute_rings = min(amdgpu_gfx_get_num_kcq(adev), - AMDGPU_MAX_COMPUTE_RINGS); + switch (amdgpu_user_queue) { + case -1: + case 0: + default: + adev->gfx.disable_kq = false; + adev->gfx.disable_uq = true; + break; + case 2: + adev->gfx.disable_kq = true; + adev->gfx.disable_uq = true; + break; + } + + if (adev->gfx.disable_kq) + adev->gfx.num_compute_rings = 0; + else + adev->gfx.num_compute_rings = min(amdgpu_gfx_get_num_kcq(adev), + AMDGPU_MAX_COMPUTE_RINGS); gfx_v9_4_3_set_kiq_pm4_funcs(adev); gfx_v9_4_3_set_ring_funcs(adev); gfx_v9_4_3_set_irq_funcs(adev); -- cgit v1.2.3 From 2a8e1e297cfc3d8430b964be164de02d24efe761 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 29 May 2026 10:34:33 +0100 Subject: drm/amdgpu: Drop support for variable struct drm_amdgpu_bo_list_entry size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Userspace always uses struct drm_amdgpu_bo_list_in->bo_info_size equal to sizeof(struct drm_amdgpu_bo_list_entry) and there are no plans to extend it. Even if the structure is extended at some point, older kernels will note that they do not support the additional fields by rejecting the new structure size. Signed-off-by: Tvrtko Ursulin Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c index 43864df8af04..5ce3160ce55a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c @@ -183,36 +183,19 @@ void amdgpu_bo_list_put(struct amdgpu_bo_list *list) int amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in, struct drm_amdgpu_bo_list_entry **info_param) { - const uint32_t info_size = sizeof(struct drm_amdgpu_bo_list_entry); const void __user *uptr = u64_to_user_ptr(in->bo_info_ptr); - const uint32_t bo_info_size = in->bo_info_size; const uint32_t bo_number = in->bo_number; struct drm_amdgpu_bo_list_entry *info; if (bo_number > AMDGPU_BO_LIST_MAX_ENTRIES) return -EINVAL; - /* copy the handle array from userspace to a kernel buffer */ - if (likely(info_size == bo_info_size)) { - info = vmemdup_array_user(uptr, bo_number, info_size); - if (IS_ERR(info)) - return PTR_ERR(info); - } else { - const uint32_t bytes = min(bo_info_size, info_size); - unsigned i; - - info = kvmalloc_array(bo_number, info_size, GFP_KERNEL); - if (!info) - return -ENOMEM; - - memset(info, 0, bo_number * info_size); - for (i = 0; i < bo_number; ++i, uptr += bo_info_size) { - if (copy_from_user(&info[i], uptr, bytes)) { - kvfree(info); - return -EFAULT; - } - } - } + if (in->bo_info_size != sizeof(struct drm_amdgpu_bo_list_entry)) + return -EINVAL; + + info = vmemdup_array_user(uptr, bo_number, sizeof(*info)); + if (IS_ERR(info)) + return PTR_ERR(info); *info_param = info; return 0; -- cgit v1.2.3 From a300c90f00905632ad9f89ad719537941a88600c Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 29 May 2026 10:34:34 +0100 Subject: drm/amdgpu: Remove the bo list mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bo list is immutable during command submission since the drm_exec conversion so we can remove the mutex. Signed-off-by: Tvrtko Ursulin Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c | 3 +-- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h | 4 ---- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 15 ++++----------- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c index 5ce3160ce55a..fa230d480ab0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c @@ -42,7 +42,7 @@ static void amdgpu_bo_list_free_rcu(struct rcu_head *rcu) { struct amdgpu_bo_list *list = container_of(rcu, struct amdgpu_bo_list, rhead); - mutex_destroy(&list->bo_list_mutex); + kvfree(list); } @@ -134,7 +134,6 @@ int amdgpu_bo_list_create(struct amdgpu_device *adev, struct drm_file *filp, trace_amdgpu_cs_bo_status(list->num_entries, total_size); - mutex_init(&list->bo_list_mutex); *result = list; return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h index 2b5e7c46a39d..1acf53f8b2f9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h @@ -51,10 +51,6 @@ struct amdgpu_bo_list { unsigned first_userptr; unsigned num_entries; - /* Protect access during command submission. - */ - struct mutex bo_list_mutex; - struct amdgpu_bo_list_entry entries[] __counted_by(num_entries); }; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index c2e6495a28bc..3867d2205d0e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -869,8 +869,6 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, return r; } - mutex_lock(&p->bo_list->bo_list_mutex); - /* Get userptr backing pages. If pages are updated after registered * in amdgpu_gem_userptr_ioctl(), amdgpu_cs_list_validate() will do * amdgpu_ttm_backend_bind() to flush and invalidate new pages @@ -987,7 +985,6 @@ out_free_user_pages: amdgpu_hmm_range_free(e->range); e->range = NULL; } - mutex_unlock(&p->bo_list->bo_list_mutex); return r; } @@ -1371,7 +1368,6 @@ static int amdgpu_cs_submit(struct amdgpu_cs_parser *p, amdgpu_vm_move_to_lru_tail(p->adev, &fpriv->vm); mutex_unlock(&p->adev->notifier_lock); - mutex_unlock(&p->bo_list->bo_list_mutex); return 0; } @@ -1443,28 +1439,25 @@ int amdgpu_cs_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) r = amdgpu_cs_patch_jobs(&parser); if (r) - goto error_backoff; + goto error_fini; r = amdgpu_cs_vm_handling(&parser); if (r) - goto error_backoff; + goto error_fini; r = amdgpu_cs_sync_rings(&parser); if (r) - goto error_backoff; + goto error_fini; trace_amdgpu_cs_ibs(&parser); r = amdgpu_cs_submit(&parser, data); if (r) - goto error_backoff; + goto error_fini; amdgpu_cs_parser_fini(&parser); return 0; -error_backoff: - mutex_unlock(&parser.bo_list->bo_list_mutex); - error_fini: amdgpu_cs_parser_fini(&parser); return r; -- cgit v1.2.3 From 89a069d0d9f5882d05aed46fc43c96b1f40905f8 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 29 May 2026 10:34:35 +0100 Subject: drm/amdgpu: Replace idr with xarray in amdgpu_bo_list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IDR is deprecated so let's replace it with xarray. Conversion is mostly 1:1 apart from AMDGPU_BO_LIST_OP_UPDATE which was implemented with idr_replace, and has now been replaced with a sequence of xa_load and xa_cmpxchg. Should userspace attempt multi-threaded update operations on the same handle it could theoretically hit a new -ENOENT path. But I believe this is purely theoretical and still safe. Also, since we have removed the RCU protection around the handle lookup we also removed the RCU freeing of the list. Signed-off-by: Tvrtko Ursulin Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 4 +- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c | 79 +++++++++++++---------------- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h | 3 +- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 11 ++-- 4 files changed, 41 insertions(+), 56 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index e2d4be3c111d..4213272637d8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -445,8 +446,7 @@ struct amdgpu_fpriv { struct amdgpu_bo_va *prt_va; struct amdgpu_bo_va *csa_va; struct amdgpu_bo_va *seq64_va; - struct mutex bo_list_lock; - struct idr bo_list_handles; + struct xarray bo_list_handles; struct amdgpu_ctx_mgr ctx_mgr; struct amdgpu_userq_mgr userq_mgr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c index fa230d480ab0..02e097b0f286 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c @@ -38,14 +38,6 @@ #define AMDGPU_BO_LIST_NUM_BUCKETS (AMDGPU_BO_LIST_MAX_PRIORITY + 1) #define AMDGPU_BO_LIST_MAX_ENTRIES (128 * 1024) -static void amdgpu_bo_list_free_rcu(struct rcu_head *rcu) -{ - struct amdgpu_bo_list *list = container_of(rcu, struct amdgpu_bo_list, - rhead); - - kvfree(list); -} - static void amdgpu_bo_list_free(struct kref *ref) { struct amdgpu_bo_list *list = container_of(ref, struct amdgpu_bo_list, @@ -54,7 +46,8 @@ static void amdgpu_bo_list_free(struct kref *ref) amdgpu_bo_list_for_each_entry(e, list) amdgpu_bo_unref(&e->bo); - call_rcu(&list->rhead, amdgpu_bo_list_free_rcu); + + kvfree(list); } static int amdgpu_bo_list_entry_cmp(const void *_a, const void *_b) @@ -147,36 +140,26 @@ error_free: } -static void amdgpu_bo_list_destroy(struct amdgpu_fpriv *fpriv, int id) +int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id, + struct amdgpu_bo_list **result) { struct amdgpu_bo_list *list; - mutex_lock(&fpriv->bo_list_lock); - list = idr_remove(&fpriv->bo_list_handles, id); - mutex_unlock(&fpriv->bo_list_lock); + xa_lock(&fpriv->bo_list_handles); + list = xa_load(&fpriv->bo_list_handles, id); if (list) - kref_put(&list->refcount, amdgpu_bo_list_free); -} + kref_get(&list->refcount); + xa_unlock(&fpriv->bo_list_handles); -int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, int id, - struct amdgpu_bo_list **result) -{ - rcu_read_lock(); - *result = idr_find(&fpriv->bo_list_handles, id); - - if (*result && kref_get_unless_zero(&(*result)->refcount)) { - rcu_read_unlock(); - return 0; - } + *result = list; - rcu_read_unlock(); - *result = NULL; - return -ENOENT; + return list ? 0 : -ENOENT; } void amdgpu_bo_list_put(struct amdgpu_bo_list *list) { - kref_put(&list->refcount, amdgpu_bo_list_free); + if (list) + kref_put(&list->refcount, amdgpu_bo_list_free); } int amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in, @@ -203,12 +186,12 @@ int amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in, int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) { - struct amdgpu_device *adev = drm_to_adev(dev); struct amdgpu_fpriv *fpriv = filp->driver_priv; + struct amdgpu_device *adev = drm_to_adev(dev); + struct drm_amdgpu_bo_list_entry *info = NULL; + struct amdgpu_bo_list *list, *prev, *curr; union drm_amdgpu_bo_list *args = data; uint32_t handle = args->in.list_handle; - struct drm_amdgpu_bo_list_entry *info = NULL; - struct amdgpu_bo_list *list, *old; int r; r = amdgpu_bo_create_list_entry_array(&args->in, &info); @@ -222,19 +205,18 @@ int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, if (r) goto error_free; - mutex_lock(&fpriv->bo_list_lock); - r = idr_alloc(&fpriv->bo_list_handles, list, 1, 0, GFP_KERNEL); - mutex_unlock(&fpriv->bo_list_lock); - if (r < 0) { + r = xa_alloc(&fpriv->bo_list_handles, &handle, list, + xa_limit_32b, GFP_KERNEL); + if (r) goto error_put_list; - } - handle = r; break; case AMDGPU_BO_LIST_OP_DESTROY: - amdgpu_bo_list_destroy(fpriv, handle); + list = xa_erase(&fpriv->bo_list_handles, handle); + amdgpu_bo_list_put(list); handle = 0; + break; case AMDGPU_BO_LIST_OP_UPDATE: @@ -243,16 +225,23 @@ int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, if (r) goto error_free; - mutex_lock(&fpriv->bo_list_lock); - old = idr_replace(&fpriv->bo_list_handles, list, handle); - mutex_unlock(&fpriv->bo_list_lock); + curr = xa_load(&fpriv->bo_list_handles, handle); + if (!curr) { + r = -ENOENT; + goto error_put_list; + } - if (IS_ERR(old)) { - r = PTR_ERR(old); + prev = xa_cmpxchg(&fpriv->bo_list_handles, handle, curr, list, + GFP_KERNEL); + if (xa_is_err(prev)) { + r = xa_err(prev); + goto error_put_list; + } else if (prev != curr) { + r = -ENOENT; goto error_put_list; } - amdgpu_bo_list_put(old); + amdgpu_bo_list_put(curr); break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h index 1acf53f8b2f9..cf127bc66f53 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h @@ -43,7 +43,6 @@ struct amdgpu_bo_list_entry { }; struct amdgpu_bo_list { - struct rcu_head rhead; struct kref refcount; struct amdgpu_bo *gds_obj; struct amdgpu_bo *gws_obj; @@ -54,7 +53,7 @@ struct amdgpu_bo_list { struct amdgpu_bo_list_entry entries[] __counted_by(num_entries); }; -int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, int id, +int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id, struct amdgpu_bo_list **result); void amdgpu_bo_list_put(struct amdgpu_bo_list *list); int amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 72b6f55699a4..215aa678d1d0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1531,8 +1531,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) if (r) goto error_vm; - mutex_init(&fpriv->bo_list_lock); - idr_init_base(&fpriv->bo_list_handles, 1); + xa_init_flags(&fpriv->bo_list_handles, XA_FLAGS_ALLOC1); r = amdgpu_userq_mgr_init(&fpriv->userq_mgr, file_priv, adev); if (r) @@ -1577,8 +1576,8 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev, struct amdgpu_fpriv *fpriv = file_priv->driver_priv; struct amdgpu_bo_list *list; struct amdgpu_bo *pd; + unsigned long handle; u32 pasid; - int handle; if (!fpriv) return; @@ -1614,11 +1613,9 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev, amdgpu_pasid_free_delayed(pd->tbo.base.resv, pasid); amdgpu_bo_unref(&pd); - idr_for_each_entry(&fpriv->bo_list_handles, list, handle) + xa_for_each(&fpriv->bo_list_handles, handle, list) amdgpu_bo_list_put(list); - - idr_destroy(&fpriv->bo_list_handles); - mutex_destroy(&fpriv->bo_list_lock); + xa_destroy(&fpriv->bo_list_handles); kfree(fpriv); file_priv->driver_priv = NULL; -- cgit v1.2.3 From 0131a305886fa083e8be7b33eb22c5e25cd30472 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 29 May 2026 10:34:36 +0100 Subject: drm/amdgpu: Remove output parameter in bo list handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing the output parameter from a few functions should result in more readable code and also enables us to save some lines. v2: fix build (Alex) Signed-off-by: Tvrtko Ursulin Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c | 87 ++++++++++++++--------------- drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h | 17 +++--- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 54 +++++++++--------- 3 files changed, 74 insertions(+), 84 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c index 02e097b0f286..ce1d08f112a8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c @@ -59,9 +59,9 @@ static int amdgpu_bo_list_entry_cmp(const void *_a, const void *_b) return (int)a->priority - (int)b->priority; } -int amdgpu_bo_list_create(struct amdgpu_device *adev, struct drm_file *filp, - struct drm_amdgpu_bo_list_entry *info, - size_t num_entries, struct amdgpu_bo_list **result) +struct amdgpu_bo_list * +amdgpu_bo_list_create(struct amdgpu_device *adev, struct drm_file *filp, + struct drm_amdgpu_bo_list_entry *info, size_t num_entries) { unsigned last_entry = 0, first_userptr = num_entries; struct amdgpu_bo_list_entry *array; @@ -72,7 +72,7 @@ int amdgpu_bo_list_create(struct amdgpu_device *adev, struct drm_file *filp, list = kvzalloc_flex(*list, entries, num_entries); if (!list) - return -ENOMEM; + return ERR_PTR(-ENOMEM); kref_init(&list->refcount); @@ -127,8 +127,7 @@ int amdgpu_bo_list_create(struct amdgpu_device *adev, struct drm_file *filp, trace_amdgpu_cs_bo_status(list->num_entries, total_size); - *result = list; - return 0; + return list; error_free: for (i = 0; i < last_entry; ++i) @@ -136,12 +135,11 @@ error_free: for (i = first_userptr; i < num_entries; ++i) amdgpu_bo_unref(&array[i].bo); kvfree(list); - return r; + return ERR_PTR(r); } -int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id, - struct amdgpu_bo_list **result) +struct amdgpu_bo_list *amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id) { struct amdgpu_bo_list *list; @@ -149,11 +147,11 @@ int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id, list = xa_load(&fpriv->bo_list_handles, id); if (list) kref_get(&list->refcount); + else + list = ERR_PTR(-ENOENT); xa_unlock(&fpriv->bo_list_handles); - *result = list; - - return list ? 0 : -ENOENT; + return list; } void amdgpu_bo_list_put(struct amdgpu_bo_list *list) @@ -162,25 +160,20 @@ void amdgpu_bo_list_put(struct amdgpu_bo_list *list) kref_put(&list->refcount, amdgpu_bo_list_free); } -int amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in, - struct drm_amdgpu_bo_list_entry **info_param) +struct drm_amdgpu_bo_list_entry * +amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in) { const void __user *uptr = u64_to_user_ptr(in->bo_info_ptr); const uint32_t bo_number = in->bo_number; - struct drm_amdgpu_bo_list_entry *info; if (bo_number > AMDGPU_BO_LIST_MAX_ENTRIES) - return -EINVAL; + return ERR_PTR(-EINVAL); if (in->bo_info_size != sizeof(struct drm_amdgpu_bo_list_entry)) - return -EINVAL; - - info = vmemdup_array_user(uptr, bo_number, sizeof(*info)); - if (IS_ERR(info)) - return PTR_ERR(info); + return ERR_PTR(-EINVAL); - *info_param = info; - return 0; + return vmemdup_array_user(uptr, bo_number, + sizeof(struct drm_amdgpu_bo_list_entry)); } int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, @@ -188,27 +181,24 @@ int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, { struct amdgpu_fpriv *fpriv = filp->driver_priv; struct amdgpu_device *adev = drm_to_adev(dev); - struct drm_amdgpu_bo_list_entry *info = NULL; struct amdgpu_bo_list *list, *prev, *curr; union drm_amdgpu_bo_list *args = data; uint32_t handle = args->in.list_handle; + struct drm_amdgpu_bo_list_entry *info; int r; - r = amdgpu_bo_create_list_entry_array(&args->in, &info); - if (r) - return r; - switch (args->in.operation) { case AMDGPU_BO_LIST_OP_CREATE: - r = amdgpu_bo_list_create(adev, filp, info, args->in.bo_number, - &list); - if (r) - goto error_free; + case AMDGPU_BO_LIST_OP_UPDATE: + info = amdgpu_bo_create_list_entry_array(&args->in); + if (IS_ERR(info)) + return PTR_ERR(info); - r = xa_alloc(&fpriv->bo_list_handles, &handle, list, - xa_limit_32b, GFP_KERNEL); - if (r) - goto error_put_list; + list = amdgpu_bo_list_create(adev, filp, info, + args->in.bo_number); + kvfree(info); + if (IS_ERR(list)) + return PTR_ERR(list); break; @@ -219,12 +209,20 @@ int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, break; - case AMDGPU_BO_LIST_OP_UPDATE: - r = amdgpu_bo_list_create(adev, filp, info, args->in.bo_number, - &list); + default: + return -EINVAL; + }; + + switch (args->in.operation) { + case AMDGPU_BO_LIST_OP_CREATE: + r = xa_alloc(&fpriv->bo_list_handles, &handle, list, + xa_limit_32b, GFP_KERNEL); if (r) - goto error_free; + goto error_put_list; + + break; + case AMDGPU_BO_LIST_OP_UPDATE: curr = xa_load(&fpriv->bo_list_handles, handle); if (!curr) { r = -ENOENT; @@ -244,21 +242,18 @@ int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, amdgpu_bo_list_put(curr); break; + case AMDGPU_BO_LIST_OP_DESTROY: default: - r = -EINVAL; - goto error_free; + /* Handled above. */ + break; } memset(args, 0, sizeof(*args)); args->out.list_handle = handle; - kvfree(info); return 0; error_put_list: amdgpu_bo_list_put(list); - -error_free: - kvfree(info); return r; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h index cf127bc66f53..bde912150824 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h @@ -53,17 +53,16 @@ struct amdgpu_bo_list { struct amdgpu_bo_list_entry entries[] __counted_by(num_entries); }; -int amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id, - struct amdgpu_bo_list **result); +struct amdgpu_bo_list *amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, u32 id); void amdgpu_bo_list_put(struct amdgpu_bo_list *list); -int amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in, - struct drm_amdgpu_bo_list_entry **info_param); +struct drm_amdgpu_bo_list_entry * +amdgpu_bo_create_list_entry_array(struct drm_amdgpu_bo_list_in *in); -int amdgpu_bo_list_create(struct amdgpu_device *adev, - struct drm_file *filp, - struct drm_amdgpu_bo_list_entry *info, - size_t num_entries, - struct amdgpu_bo_list **list); +struct amdgpu_bo_list * +amdgpu_bo_list_create(struct amdgpu_device *adev, + struct drm_file *filp, + struct drm_amdgpu_bo_list_entry *info, + size_t num_entries); #define amdgpu_bo_list_for_each_entry(e, list) \ for (e = list->entries; \ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 3867d2205d0e..4ad8f1c31e55 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -140,24 +140,19 @@ static int amdgpu_cs_p1_bo_handles(struct amdgpu_cs_parser *p, struct drm_amdgpu_bo_list_in *data) { struct drm_amdgpu_bo_list_entry *info; - int r; - - r = amdgpu_bo_create_list_entry_array(data, &info); - if (r) - return r; - - r = amdgpu_bo_list_create(p->adev, p->filp, info, data->bo_number, - &p->bo_list); - if (r) - goto error_free; + struct amdgpu_bo_list *list; - kvfree(info); - return 0; + info = amdgpu_bo_create_list_entry_array(data); + if (IS_ERR(info)) + return PTR_ERR(info); -error_free: + list = amdgpu_bo_list_create(p->adev, p->filp, info, data->bo_number); kvfree(info); + if (IS_ERR(list)) + return PTR_ERR(list); - return r; + p->bo_list = list; + return 0; } /* Copy the data from userspace and go over it the first time */ @@ -846,6 +841,7 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, { struct amdgpu_fpriv *fpriv = p->filp->driver_priv; struct ttm_operation_ctx ctx = { true, false }; + struct amdgpu_bo_list *list = NULL; struct amdgpu_vm *vm = &fpriv->vm; struct amdgpu_bo_list_entry *e; struct drm_gem_object *obj; @@ -857,23 +853,24 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, if (p->bo_list) return -EINVAL; - r = amdgpu_bo_list_get(fpriv, cs->in.bo_list_handle, - &p->bo_list); - if (r) - return r; + list = amdgpu_bo_list_get(fpriv, cs->in.bo_list_handle); } else if (!p->bo_list) { /* Create a empty bo_list when no handle is provided */ - r = amdgpu_bo_list_create(p->adev, p->filp, NULL, 0, - &p->bo_list); - if (r) - return r; + list = amdgpu_bo_list_create(p->adev, p->filp, NULL, 0); } + if (IS_ERR(list)) + return PTR_ERR(list); + else if (list) + p->bo_list = list; + else + list = p->bo_list; + /* Get userptr backing pages. If pages are updated after registered * in amdgpu_gem_userptr_ioctl(), amdgpu_cs_list_validate() will do * amdgpu_ttm_backend_bind() to flush and invalidate new pages */ - amdgpu_bo_list_for_each_userptr_entry(e, p->bo_list) { + amdgpu_bo_list_for_each_userptr_entry(e, list) { bool userpage_invalidated = false; struct amdgpu_bo *bo = e->bo; @@ -903,7 +900,7 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, if (unlikely(r)) goto out_free_user_pages; - amdgpu_bo_list_for_each_entry(e, p->bo_list) { + amdgpu_bo_list_for_each_entry(e, list) { r = drm_exec_prepare_obj(&p->exec, &e->bo->tbo.base, TTM_NUM_MOVE_FENCES + p->gang_size); drm_exec_retry_on_contention(&p->exec); @@ -922,7 +919,7 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, } } - amdgpu_bo_list_for_each_userptr_entry(e, p->bo_list) { + amdgpu_bo_list_for_each_userptr_entry(e, list) { struct mm_struct *usermm; usermm = amdgpu_ttm_tt_get_usermm(e->bo->tbo.ttm); @@ -975,13 +972,12 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, p->bytes_moved_vis); for (i = 0; i < p->gang_size; ++i) - amdgpu_job_set_resources(p->jobs[i], p->bo_list->gds_obj, - p->bo_list->gws_obj, - p->bo_list->oa_obj); + amdgpu_job_set_resources(p->jobs[i], list->gds_obj, + list->gws_obj, list->oa_obj); return 0; out_free_user_pages: - amdgpu_bo_list_for_each_userptr_entry(e, p->bo_list) { + amdgpu_bo_list_for_each_userptr_entry(e, list) { amdgpu_hmm_range_free(e->range); e->range = NULL; } -- cgit v1.2.3 From 62d8b452615fd7a61976a1372bb81937807968c3 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 17 Jun 2026 14:22:08 +0530 Subject: drm/amdgpu: Fix kobject cleanup in xcp sysfs Fix the indexing issue. Release the kobject whose init/add failed, and unwind the successfully added ones. Signed-off-by: Lijo Lazar Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c index 409e103ffe8c..9202ddf3d69c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c @@ -903,7 +903,7 @@ static void amdgpu_xcp_cfg_sysfs_init(struct amdgpu_device *adev) { struct amdgpu_xcp_res_details *xcp_res; struct amdgpu_xcp_cfg *xcp_cfg; - int i, r, j, rid, mode; + int i, r, rid, mode; if (!adev->xcp_mgr) return; @@ -949,14 +949,16 @@ static void amdgpu_xcp_cfg_sysfs_init(struct amdgpu_device *adev) &xcp_cfg_res_sysfs_ktype, &xcp_cfg->kobj, "%s", xcp_res_names[rid]); - if (r) + if (r) { + kobject_put(&xcp_res->kobj); goto err; + } } adev->xcp_mgr->xcp_cfg = xcp_cfg; return; err: - for (j = 0; j < i; j++) { + while (i--) { xcp_res = &xcp_cfg->xcp_res[i]; kobject_put(&xcp_res->kobj); } -- cgit v1.2.3 From 8c3fcfc14fc1320a1155acb2dd7656fce7003bc0 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Mon, 15 Jun 2026 11:02:51 +0530 Subject: drm/amdgpu: Add checks to vbios fetch through ATRM Check if a valid buffer object is returned after ATRM call. Also, match the buffer length against requested size before copying. Signed-off-by: Lijo Lazar Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c index aa039e148a5e..3ebdd792feec 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -296,8 +296,14 @@ static int amdgpu_atrm_call(acpi_handle atrm_handle, uint8_t *bios, } obj = (union acpi_object *)buffer.pointer; - memcpy(bios+offset, obj->buffer.pointer, obj->buffer.length); - len = obj->buffer.length; + if (!obj || obj->type != ACPI_TYPE_BUFFER) { + DRM_ERROR("ATRM returned an invalid object\n"); + kfree(buffer.pointer); + return -EINVAL; + } + + len = min_t(size_t, obj->buffer.length, len); + memcpy(bios+offset, obj->buffer.pointer, len); kfree(buffer.pointer); return len; } -- cgit v1.2.3 From d077a0d57c6d151866c4914e7890b6117d255c61 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Wed, 17 Jun 2026 13:15:55 -0400 Subject: drm/amdgpu: Fix mes remove_hw_queue lock down_read/up_read adev->reset_domain semaphore should be placed around remove queue. v2: remove the empty function, recover_bad_queue_mes to avoid compile error on rhel Fixes: f401a2633e02 ("drm/amdgpu: Remove faulty queue before resume") Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 5 +++++ .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 23 +++++----------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 0506b90f318e..982b41606d48 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -2358,9 +2358,14 @@ fence_reset: * preempted successfuly. Remove it before resume all so it * doesn't get mapped back */ + if (!down_read_trylock(&adev->reset_domain->sem)) { + r = -EIO; + goto out; + } amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->remove_hw_queue(&adev->mes, queue_input); amdgpu_mes_unlock(&adev->mes); + up_read(&adev->reset_domain->sem); } out: 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 5c9dfb0c424f..3b1a5a2a37ca 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -71,11 +71,11 @@ static int allocate_sdma_queue(struct device_queue_manager *dqm, struct queue *q, const uint32_t *restore_sdma_id); static int reset_queues_on_hws_hang(struct device_queue_manager *dqm, bool is_sdma); -static int recover_bad_queue_mes(struct device_queue_manager *dqm, struct queue *q); static struct queue *find_queue_by_doorbell_offset(struct device_queue_manager *dqm, u32 doorbell_offset); static void set_queue_as_reset(struct device_queue_manager *dqm, struct queue *q, struct qcm_process_device *qpd); +static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q); static inline enum KFD_MQD_TYPE get_mqd_type_from_queue_type(enum kfd_queue_type type) @@ -307,11 +307,12 @@ static int remove_queue_mes_on_reset_option(struct device_queue_manager *dqm, st amdgpu_mes_unlock(&adev->mes); up_read(&adev->reset_domain->sem); + /* If is_for_reset set, it is a mes internal cleanup */ if (!r || is_for_reset) return r; - /* remove_hw_queue failed. try to recover */ - r = recover_bad_queue_mes(dqm, q); + /* remove_hw_queue failure indicates a queue hang. reset the queue */ + r = reset_queues_mes(dqm, q); if (r && amdgpu_gpu_recovery) { dev_err(adev->dev, "failed to remove queue from MES, doorbell=0x%x\n", q->properties.doorbell_off); @@ -485,20 +486,6 @@ fail: return r; } -static int recover_bad_queue_mes(struct device_queue_manager *dqm, struct queue *q) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; - int r = 0; - - if (!down_read_trylock(&adev->reset_domain->sem)) - return -EIO; - - r = reset_queues_mes(dqm, q); - - up_read(&adev->reset_domain->sem); - return r; -} - static void increment_queue_count(struct device_queue_manager *dqm, struct qcm_process_device *qpd, struct queue *q) @@ -3242,7 +3229,7 @@ int kfd_dqm_suspend_bad_queue_mes(struct kfd_node *knode, u32 pasid, u32 doorbel list_for_each_entry(q, &qpd->queues_list, list) { if (q->doorbell_id == doorbell_id && q->properties.is_active) { - recover_bad_queue_mes(dqm, q); + reset_queues_mes(dqm, q); q->properties.is_evicted = true; q->properties.is_active = false; decrement_queue_count(dqm, qpd, q); -- cgit v1.2.3 From b68f1654927fe841e71816933d0b50efb4f0196d Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Tue, 19 May 2026 19:29:20 +0530 Subject: drm/amd/pm: Add helper for parameter parsing Add a helper function to extract long values passed in a string. The string may have values of multiple parameters separated by space char. Signed-off-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 116 +++++++++++++++---------------------- 1 file changed, 48 insertions(+), 68 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index f43d09769320..538b6736e9f8 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -98,6 +98,37 @@ const char * const amdgpu_pp_profile_name[] = { "UNCAPPED", }; +static int amdgpu_pm_parse_long_params(char *str, long *params, + uint32_t max_params, + uint32_t *num_params) +{ + const char delimiter[] = { ' ', '\n', '\0' }; + uint32_t count = 0; + char *sub_str; + int ret; + + if (!params || !num_params) + return -EINVAL; + + while ((sub_str = strsep(&str, delimiter)) != NULL) { + if (strlen(sub_str) == 0) + continue; + if (count >= max_params) + return -EINVAL; + ret = kstrtol(sub_str, 0, ¶ms[count]); + if (ret) + return -EINVAL; + count++; + if (!str) + break; + while (isspace(*str)) + str++; + } + *num_params = count; + + return 0; +} + /** * amdgpu_pm_dev_state_check - Check if device can be accessed. * @adev: Target device. @@ -767,8 +798,6 @@ static ssize_t amdgpu_set_pp_od_clk_voltage(struct device *dev, long parameter[64]; char buf_cpy[128]; char *tmp_str; - char *sub_str; - const char delimiter[3] = {' ', '\n', '\0'}; uint32_t type; if (count > 127 || count == 0) @@ -803,22 +832,10 @@ static ssize_t amdgpu_set_pp_od_clk_voltage(struct device *dev, tmp_str++; while (isspace(*++tmp_str)); - while ((sub_str = strsep(&tmp_str, delimiter)) != NULL) { - if (strlen(sub_str) == 0) - continue; - if (parameter_size >= ARRAY_SIZE(parameter)) - return -EINVAL; - ret = kstrtol(sub_str, 0, ¶meter[parameter_size]); - if (ret) - return -EINVAL; - parameter_size++; - - if (!tmp_str) - break; - - while (isspace(*tmp_str)) - tmp_str++; - } + ret = amdgpu_pm_parse_long_params( + tmp_str, parameter, ARRAY_SIZE(parameter), ¶meter_size); + if (ret) + return ret; ret = amdgpu_pm_get_access(adev); if (ret < 0) @@ -1391,11 +1408,9 @@ static ssize_t amdgpu_set_pp_power_profile_mode(struct device *dev, struct amdgpu_device *adev = drm_to_adev(ddev); uint32_t parameter_size = 0; long parameter[64]; - char *sub_str, buf_cpy[128]; - char *tmp_str; + char buf_cpy[128]; char tmp[2]; long int profile_mode = 0; - const char delimiter[3] = {' ', '\n', '\0'}; /* Reject empty/whitespace strings - fuzzing found this is not validated */ if (count == 0 || sysfs_streq(buf, "")) @@ -1413,19 +1428,11 @@ static ssize_t amdgpu_set_pp_power_profile_mode(struct device *dev, while (isspace(*buf)) buf++; strscpy(buf_cpy, buf, sizeof(buf_cpy)); - tmp_str = buf_cpy; - while ((sub_str = strsep(&tmp_str, delimiter)) != NULL) { - if (strlen(sub_str) == 0) - continue; - ret = kstrtol(sub_str, 0, ¶meter[parameter_size]); - if (ret) - return -EINVAL; - parameter_size++; - if (!tmp_str) - break; - while (isspace(*tmp_str)) - tmp_str++; - } + ret = amdgpu_pm_parse_long_params(buf_cpy, parameter, + ARRAY_SIZE(parameter) - 1, + ¶meter_size); + if (ret) + return ret; } parameter[parameter_size] = profile_mode; @@ -3954,18 +3961,14 @@ out_pm_put: return size; } -static int parse_input_od_command_lines(const char *buf, - size_t count, - u32 *type, - long *params, - size_t params_max, +static int parse_input_od_command_lines(const char *buf, size_t count, + u32 *type, long *params, + uint32_t max_params, uint32_t *num_of_params) { - const char delimiter[3] = {' ', '\n', '\0'}; uint32_t parameter_size = 0; char buf_cpy[128] = {0}; - char *tmp_str, *sub_str; - int ret; + char *tmp_str; if (count > sizeof(buf_cpy) - 1) return -EINVAL; @@ -3990,28 +3993,8 @@ static int parse_input_od_command_lines(const char *buf, break; } - while ((sub_str = strsep(&tmp_str, delimiter)) != NULL) { - if (strlen(sub_str) == 0) - continue; - - if (parameter_size >= params_max) - return -EINVAL; - - ret = kstrtol(sub_str, 0, ¶ms[parameter_size]); - if (ret) - return -EINVAL; - parameter_size++; - - if (!tmp_str) - break; - - while (isspace(*tmp_str)) - tmp_str++; - } - - *num_of_params = parameter_size; - - return 0; + return amdgpu_pm_parse_long_params(tmp_str, params, max_params, + num_of_params); } static int @@ -4024,10 +4007,7 @@ amdgpu_distribute_custom_od_settings(struct amdgpu_device *adev, long parameter[64]; int ret; - ret = parse_input_od_command_lines(in_buf, - count, - &cmd_type, - parameter, + ret = parse_input_od_command_lines(in_buf, count, &cmd_type, parameter, ARRAY_SIZE(parameter), ¶meter_size); if (ret) -- cgit v1.2.3 From 0eda57ee303b4392057b7d4c32e8fda9d14cffad Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 16 Jun 2026 21:24:05 +0800 Subject: drm/amdgpu: validate XCP topology counts before division In aqua_vanjaram_get_xcp_res_info(), max_res[i] can be zero. When res_lt_xcp is true the code divides num_xcp by max_res[i], causing a divide fault. Skip the loop body for absent resources. v2: Remove redundant checks (Lijo) Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c b/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c index 72ea37dbfea8..1c11cc280599 100644 --- a/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c +++ b/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c @@ -273,8 +273,10 @@ static int aqua_vanjaram_get_xcp_res_info(struct amdgpu_xcp_mgr *xcp_mgr, xcp_cfg->num_res = ARRAY_SIZE(max_res); for (i = 0; i < xcp_cfg->num_res; i++) { - res_lt_xcp = max_res[i] < num_xcp; xcp_cfg->xcp_res[i].id = i; + if (!max_res[i]) + continue; + res_lt_xcp = max_res[i] < num_xcp; xcp_cfg->xcp_res[i].num_inst = res_lt_xcp ? 1 : max_res[i] / num_xcp; xcp_cfg->xcp_res[i].num_inst = -- cgit v1.2.3 From a101cfbd2771199af1396954f5bb007df94cebb0 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 16 Jun 2026 22:25:08 +0800 Subject: drm/amdgpu: guard zero divisors in soc_v1_0 partition code Abort driver load when num_mem_partitions is zero since operation is unreliable without valid memory partition info. Skip absent resources in soc_v1_0_get_xcp_res_info() to avoid divide-by-zero on firmware- reported zero instance counts. v2: Remove redundant checks (Lijo) v3: Return error instead when num_mem_partitions is zero (Lijo) Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 9 +++++++-- drivers/gpu/drm/amd/amdgpu/soc_v1_0.c | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 5f7745143f56..aeda54ee2c9d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -1761,10 +1761,15 @@ int amdgpu_gmc_init_mem_ranges(struct amdgpu_device *adev) valid = true; else valid = amdgpu_gmc_validate_partition_info(adev); - if (!valid) { - /* TODO: handle invalid case */ + if (!valid) dev_warn(adev->dev, "Mem ranges not matching with hardware config\n"); + + if (!adev->gmc.num_mem_partitions) { + dev_err(adev->dev, "num_mem_partitions is zero\n"); + kfree(adev->gmc.mem_partitions); + adev->gmc.mem_partitions = NULL; + return -EINVAL; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c b/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c index 5f05c8e68297..f3f3fac435d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c @@ -600,8 +600,10 @@ static int soc_v1_0_get_xcp_res_info(struct amdgpu_xcp_mgr *xcp_mgr, xcp_cfg->num_res = ARRAY_SIZE(max_res); for (i = 0; i < xcp_cfg->num_res; i++) { - res_lt_xcp = max_res[i] < num_xcp; xcp_cfg->xcp_res[i].id = i; + if (!max_res[i]) + continue; + res_lt_xcp = max_res[i] < num_xcp; xcp_cfg->xcp_res[i].num_inst = res_lt_xcp ? 1 : max_res[i] / num_xcp; xcp_cfg->xcp_res[i].num_inst = -- cgit v1.2.3 From e007d04334c2aa8a5e84e220547b604624954447 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 20 May 2026 16:00:21 +0530 Subject: drm/amd/pm: Add helper functions to fetch pptable PPTables could be embedded in firmware binaries with v2.0 or v2.1 format. Add a common helper to get pptable from firmware binaries. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c | 140 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h | 3 + 2 files changed, 143 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c index d365f06ac1ac..2bd3ea17e789 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c @@ -1544,3 +1544,143 @@ int smu_cmn_dpm_pcie_width_idx(int width) return ret; } + +static int smu_cmn_get_pptable_v2_0(struct smu_context *smu, void **table, uint32_t *size) +{ + const struct smc_firmware_header_v2_0 *v2; + struct amdgpu_device *adev = smu->adev; + size_t fw_size = adev->pm.fw->size; + uint32_t ppt_offset_bytes; + uint32_t ppt_size_bytes; + + if (fw_size < sizeof(*v2)) { + dev_err(adev->dev, + "SMC firmware too small for v2.0 header: %zu < %zu\n", + fw_size, sizeof(*v2)); + return -EINVAL; + } + + v2 = (const struct smc_firmware_header_v2_0 *)adev->pm.fw->data; + + ppt_offset_bytes = le32_to_cpu(v2->ppt_offset_bytes); + ppt_size_bytes = le32_to_cpu(v2->ppt_size_bytes); + + if (ppt_offset_bytes > fw_size || + ppt_size_bytes > fw_size - ppt_offset_bytes) { + dev_err(adev->dev, + "pptable v2.0 exceeds firmware binary: offset %u + size %u > %zu\n", + ppt_offset_bytes, ppt_size_bytes, fw_size); + return -EINVAL; + } + + *size = ppt_size_bytes; + *table = (uint8_t *)v2 + ppt_offset_bytes; + + return 0; +} + +static int smu_cmn_get_pptable_v2_1(struct smu_context *smu, void **table, + uint32_t *size, uint32_t pptable_id) +{ + const struct smc_firmware_header_v2_1 *v2_1; + struct amdgpu_device *adev = smu->adev; + struct smc_soft_pptable_entry *entries; + size_t fw_size = adev->pm.fw->size; + uint32_t pptable_entry_offset; + uint32_t ppt_offset_bytes; + uint32_t ppt_size_bytes; + uint32_t pptable_count; + int i; + + if (fw_size < sizeof(*v2_1)) { + dev_err(adev->dev, + "SMC firmware too small for v2.1 header: %zu < %zu\n", + fw_size, sizeof(*v2_1)); + return -EINVAL; + } + + v2_1 = (const struct smc_firmware_header_v2_1 *)adev->pm.fw->data; + + pptable_entry_offset = le32_to_cpu(v2_1->pptable_entry_offset); + pptable_count = le32_to_cpu(v2_1->pptable_count); + + if (pptable_entry_offset > fw_size || + pptable_count > (fw_size - pptable_entry_offset) / sizeof(*entries)) { + dev_err(adev->dev, + "pptable v2.1 entry array exceeds firmware binary: offset %u, count %u\n", + pptable_entry_offset, pptable_count); + return -EINVAL; + } + + entries = (struct smc_soft_pptable_entry *) + ((uint8_t *)v2_1 + pptable_entry_offset); + + for (i = 0; i < pptable_count; i++) { + if (le32_to_cpu(entries[i].id) != pptable_id) + continue; + + ppt_offset_bytes = le32_to_cpu(entries[i].ppt_offset_bytes); + ppt_size_bytes = le32_to_cpu(entries[i].ppt_size_bytes); + + if (ppt_offset_bytes > fw_size || + ppt_size_bytes > fw_size - ppt_offset_bytes) { + dev_err(adev->dev, + "pptable entry %d exceeds firmware binary: offset %u + size %u > %zu\n", + i, ppt_offset_bytes, ppt_size_bytes, fw_size); + return -EINVAL; + } + + *table = (uint8_t *)v2_1 + ppt_offset_bytes; + *size = ppt_size_bytes; + return 0; + } + + return -EINVAL; +} + +/** + * smu_cmn_get_pptable_from_firmware - locate the soft pptable embedded in the + * SMC firmware binary. + * @smu: SMU context + * @table: on success, set to the start of the pptable within the firmware + * blob + * @size: on success, set to the pptable size in bytes + * @pptable_id: the entry ID to search for (used only for v2.1 binaries) + * + * Reads the firmware header version and dispatches to the appropriate v2.x + * parser. Only major version 2 is supported; minor version selects between + * the single-entry (v2.0) and multi-entry directory (v2.1) layouts. + * + * Return: 0 on success, -EINVAL for an unsupported version or if the + * requested pptable cannot be found or exceeds the binary bounds. + */ +int smu_cmn_get_pptable_from_firmware(struct smu_context *smu, void **table, + uint32_t *size, uint32_t pptable_id) +{ + const struct smc_firmware_header_v1_0 *hdr; + struct amdgpu_device *adev = smu->adev; + uint16_t version_major, version_minor; + + hdr = (const struct smc_firmware_header_v1_0 *)adev->pm.fw->data; + if (!hdr) + return -EINVAL; + + dev_info(adev->dev, "use driver provided pptable %d\n", pptable_id); + + version_major = le16_to_cpu(hdr->header.header_version_major); + version_minor = le16_to_cpu(hdr->header.header_version_minor); + if (version_major != 2) { + dev_err(adev->dev, "Unsupported smu firmware version %d.%d\n", + version_major, version_minor); + return -EINVAL; + } + + switch (version_minor) { + case 0: + return smu_cmn_get_pptable_v2_0(smu, table, size); + case 1: + return smu_cmn_get_pptable_v2_1(smu, table, size, pptable_id); + default: + return -EINVAL; + } +} diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h index 5b7f64b94179..ae6742f5298f 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h @@ -249,6 +249,9 @@ int smu_cmn_dpm_pcie_gen_idx(int gen); int smu_cmn_dpm_pcie_width_idx(int width); int smu_cmn_check_fw_version(struct smu_context *smu); +int smu_cmn_get_pptable_from_firmware(struct smu_context *smu, void **table, + uint32_t *size, uint32_t pptable_id); + /*SMU gpu metrics */ /* Attribute ID mapping */ -- cgit v1.2.3 From 860d8dc7e7f12d64c6cc2a701a910119e71bb1da Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 20 May 2026 16:10:21 +0530 Subject: drm/amd/pm: Use helper to get pptable in SMUv11 Use common helper function to get pptable from firmware binary in SMUv11. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c | 79 ++++---------------------- 1 file changed, 10 insertions(+), 69 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c index d68ceee16d8f..b2cba36046a1 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c @@ -192,81 +192,22 @@ int smu_v11_0_check_fw_status(struct smu_context *smu) return -EIO; } -static int smu_v11_0_set_pptable_v2_0(struct smu_context *smu, void **table, uint32_t *size) -{ - struct amdgpu_device *adev = smu->adev; - uint32_t ppt_offset_bytes; - const struct smc_firmware_header_v2_0 *v2; - - v2 = (const struct smc_firmware_header_v2_0 *) adev->pm.fw->data; - - ppt_offset_bytes = le32_to_cpu(v2->ppt_offset_bytes); - *size = le32_to_cpu(v2->ppt_size_bytes); - *table = (uint8_t *)v2 + ppt_offset_bytes; - - return 0; -} - -static int smu_v11_0_set_pptable_v2_1(struct smu_context *smu, void **table, - uint32_t *size, uint32_t pptable_id) -{ - struct amdgpu_device *adev = smu->adev; - const struct smc_firmware_header_v2_1 *v2_1; - struct smc_soft_pptable_entry *entries; - uint32_t pptable_count = 0; - int i = 0; - - v2_1 = (const struct smc_firmware_header_v2_1 *) adev->pm.fw->data; - entries = (struct smc_soft_pptable_entry *) - ((uint8_t *)v2_1 + le32_to_cpu(v2_1->pptable_entry_offset)); - pptable_count = le32_to_cpu(v2_1->pptable_count); - for (i = 0; i < pptable_count; i++) { - if (le32_to_cpu(entries[i].id) == pptable_id) { - *table = ((uint8_t *)v2_1 + le32_to_cpu(entries[i].ppt_offset_bytes)); - *size = le32_to_cpu(entries[i].ppt_size_bytes); - break; - } - } - - if (i == pptable_count) - return -EINVAL; - - return 0; -} - int smu_v11_0_setup_pptable(struct smu_context *smu) { struct amdgpu_device *adev = smu->adev; - const struct smc_firmware_header_v1_0 *hdr; - int ret, index; - uint32_t size = 0; uint16_t atom_table_size; uint8_t frev, crev; + uint32_t size = 0; + int ret, index; void *table; - uint16_t version_major, version_minor; - - if (!amdgpu_sriov_vf(adev)) { - hdr = (const struct smc_firmware_header_v1_0 *) adev->pm.fw->data; - version_major = le16_to_cpu(hdr->header.header_version_major); - version_minor = le16_to_cpu(hdr->header.header_version_minor); - if (version_major == 2 && smu->smu_table.boot_values.pp_table_id > 0) { - dev_info(adev->dev, "use driver provided pptable %d\n", smu->smu_table.boot_values.pp_table_id); - switch (version_minor) { - case 0: - ret = smu_v11_0_set_pptable_v2_0(smu, &table, &size); - break; - case 1: - ret = smu_v11_0_set_pptable_v2_1(smu, &table, &size, - smu->smu_table.boot_values.pp_table_id); - break; - default: - ret = -EINVAL; - break; - } - if (ret) - return ret; - goto out; - } + + if (!amdgpu_sriov_vf(adev) && + smu->smu_table.boot_values.pp_table_id > 0) { + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, + smu->smu_table.boot_values.pp_table_id); + if (ret) + return ret; + goto out; } dev_info(adev->dev, "use vbios provided pptable\n"); -- cgit v1.2.3 From c60960bb85357445c9e58a7457634db7bd45ceb8 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 20 May 2026 16:21:42 +0530 Subject: drm/amd/pm: Use helper to get pptable in SMUv13 Use common helper function to get pptable from firmware binary in SMUv13. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h | 4 -- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c | 85 +------------------------- 2 files changed, 2 insertions(+), 87 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h index 89bbda0670ef..68f4de5f800c 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h @@ -255,10 +255,6 @@ void smu_v13_0_init_msg_ctl(struct smu_context *smu, int smu_v13_0_mode1_reset(struct smu_context *smu); -int smu_v13_0_get_pptable_from_firmware(struct smu_context *smu, - void **table, - uint32_t *size, - uint32_t pptable_id); int smu_v13_0_update_pcie_parameters(struct smu_context *smu, uint8_t pcie_gen_cap, diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c index be9a7a32de99..492467154ab9 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c @@ -218,7 +218,7 @@ int smu_v13_0_init_pptable_microcode(struct smu_context *smu) if (!pptable_id) return 0; - ret = smu_v13_0_get_pptable_from_firmware(smu, &table, &size, pptable_id); + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, pptable_id); if (ret) return ret; @@ -258,48 +258,6 @@ int smu_v13_0_check_fw_status(struct smu_context *smu) return -EIO; } -static int smu_v13_0_set_pptable_v2_0(struct smu_context *smu, void **table, uint32_t *size) -{ - struct amdgpu_device *adev = smu->adev; - uint32_t ppt_offset_bytes; - const struct smc_firmware_header_v2_0 *v2; - - v2 = (const struct smc_firmware_header_v2_0 *) adev->pm.fw->data; - - ppt_offset_bytes = le32_to_cpu(v2->ppt_offset_bytes); - *size = le32_to_cpu(v2->ppt_size_bytes); - *table = (uint8_t *)v2 + ppt_offset_bytes; - - return 0; -} - -static int smu_v13_0_set_pptable_v2_1(struct smu_context *smu, void **table, - uint32_t *size, uint32_t pptable_id) -{ - struct amdgpu_device *adev = smu->adev; - const struct smc_firmware_header_v2_1 *v2_1; - struct smc_soft_pptable_entry *entries; - uint32_t pptable_count = 0; - int i = 0; - - v2_1 = (const struct smc_firmware_header_v2_1 *) adev->pm.fw->data; - entries = (struct smc_soft_pptable_entry *) - ((uint8_t *)v2_1 + le32_to_cpu(v2_1->pptable_entry_offset)); - pptable_count = le32_to_cpu(v2_1->pptable_count); - for (i = 0; i < pptable_count; i++) { - if (le32_to_cpu(entries[i].id) == pptable_id) { - *table = ((uint8_t *)v2_1 + le32_to_cpu(entries[i].ppt_offset_bytes)); - *size = le32_to_cpu(entries[i].ppt_size_bytes); - break; - } - } - - if (i == pptable_count) - return -EINVAL; - - return 0; -} - static int smu_v13_0_get_pptable_from_vbios(struct smu_context *smu, void **table, uint32_t *size) { struct amdgpu_device *adev = smu->adev; @@ -322,45 +280,6 @@ static int smu_v13_0_get_pptable_from_vbios(struct smu_context *smu, void **tabl return 0; } -int smu_v13_0_get_pptable_from_firmware(struct smu_context *smu, - void **table, - uint32_t *size, - uint32_t pptable_id) -{ - const struct smc_firmware_header_v1_0 *hdr; - struct amdgpu_device *adev = smu->adev; - uint16_t version_major, version_minor; - int ret; - - hdr = (const struct smc_firmware_header_v1_0 *) adev->pm.fw->data; - if (!hdr) - return -EINVAL; - - dev_info(adev->dev, "use driver provided pptable %d\n", pptable_id); - - version_major = le16_to_cpu(hdr->header.header_version_major); - version_minor = le16_to_cpu(hdr->header.header_version_minor); - if (version_major != 2) { - dev_err(adev->dev, "Unsupported smu firmware version %d.%d\n", - version_major, version_minor); - return -EINVAL; - } - - switch (version_minor) { - case 0: - ret = smu_v13_0_set_pptable_v2_0(smu, table, size); - break; - case 1: - ret = smu_v13_0_set_pptable_v2_1(smu, table, size, pptable_id); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - int smu_v13_0_setup_pptable(struct smu_context *smu) { struct amdgpu_device *adev = smu->adev; @@ -380,7 +299,7 @@ int smu_v13_0_setup_pptable(struct smu_context *smu) if ((amdgpu_sriov_vf(adev) || !pptable_id) && (amdgpu_emu_mode != 1)) ret = smu_v13_0_get_pptable_from_vbios(smu, &table, &size); else - ret = smu_v13_0_get_pptable_from_firmware(smu, &table, &size, pptable_id); + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, pptable_id); if (ret) return ret; -- cgit v1.2.3 From d1331c7d89b8ffc630e2d8dc44db4539f00f7544 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 20 May 2026 19:43:03 +0530 Subject: drm/amd/pm: Use helper to get pptable in SMUv14 Use common helper function to get pptable from firmware binary in SMUv14. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h | 4 -- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c | 85 +------------------------- 2 files changed, 2 insertions(+), 87 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h index 4eb40ff8aff2..dc8e13a7c879 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h @@ -203,10 +203,6 @@ int smu_v14_0_set_gfx_power_up_by_imu(struct smu_context *smu); int smu_v14_0_set_default_dpm_tables(struct smu_context *smu); -int smu_v14_0_get_pptable_from_firmware(struct smu_context *smu, - void **table, - uint32_t *size, - uint32_t pptable_id); int smu_v14_0_od_edit_dpm_table(struct smu_context *smu, enum PP_OD_DPM_TABLE_COMMAND type, diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c index d0a8df1aa6b6..2a0c7cde938d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c @@ -194,7 +194,7 @@ int smu_v14_0_init_pptable_microcode(struct smu_context *smu) if (!pptable_id) return 0; - ret = smu_v14_0_get_pptable_from_firmware(smu, &table, &size, pptable_id); + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, pptable_id); if (ret) return ret; @@ -229,48 +229,6 @@ int smu_v14_0_check_fw_status(struct smu_context *smu) return -EIO; } -static int smu_v14_0_set_pptable_v2_0(struct smu_context *smu, void **table, uint32_t *size) -{ - struct amdgpu_device *adev = smu->adev; - uint32_t ppt_offset_bytes; - const struct smc_firmware_header_v2_0 *v2; - - v2 = (const struct smc_firmware_header_v2_0 *) adev->pm.fw->data; - - ppt_offset_bytes = le32_to_cpu(v2->ppt_offset_bytes); - *size = le32_to_cpu(v2->ppt_size_bytes); - *table = (uint8_t *)v2 + ppt_offset_bytes; - - return 0; -} - -static int smu_v14_0_set_pptable_v2_1(struct smu_context *smu, void **table, - uint32_t *size, uint32_t pptable_id) -{ - struct amdgpu_device *adev = smu->adev; - const struct smc_firmware_header_v2_1 *v2_1; - struct smc_soft_pptable_entry *entries; - uint32_t pptable_count = 0; - int i = 0; - - v2_1 = (const struct smc_firmware_header_v2_1 *) adev->pm.fw->data; - entries = (struct smc_soft_pptable_entry *) - ((uint8_t *)v2_1 + le32_to_cpu(v2_1->pptable_entry_offset)); - pptable_count = le32_to_cpu(v2_1->pptable_count); - for (i = 0; i < pptable_count; i++) { - if (le32_to_cpu(entries[i].id) == pptable_id) { - *table = ((uint8_t *)v2_1 + le32_to_cpu(entries[i].ppt_offset_bytes)); - *size = le32_to_cpu(entries[i].ppt_size_bytes); - break; - } - } - - if (i == pptable_count) - return -EINVAL; - - return 0; -} - static int smu_v14_0_get_pptable_from_vbios(struct smu_context *smu, void **table, uint32_t *size) { struct amdgpu_device *adev = smu->adev; @@ -293,45 +251,6 @@ static int smu_v14_0_get_pptable_from_vbios(struct smu_context *smu, void **tabl return 0; } -int smu_v14_0_get_pptable_from_firmware(struct smu_context *smu, - void **table, - uint32_t *size, - uint32_t pptable_id) -{ - const struct smc_firmware_header_v1_0 *hdr; - struct amdgpu_device *adev = smu->adev; - uint16_t version_major, version_minor; - int ret; - - hdr = (const struct smc_firmware_header_v1_0 *) adev->pm.fw->data; - if (!hdr) - return -EINVAL; - - dev_info(adev->dev, "use driver provided pptable %d\n", pptable_id); - - version_major = le16_to_cpu(hdr->header.header_version_major); - version_minor = le16_to_cpu(hdr->header.header_version_minor); - if (version_major != 2) { - dev_err(adev->dev, "Unsupported smu firmware version %d.%d\n", - version_major, version_minor); - return -EINVAL; - } - - switch (version_minor) { - case 0: - ret = smu_v14_0_set_pptable_v2_0(smu, table, size); - break; - case 1: - ret = smu_v14_0_set_pptable_v2_1(smu, table, size, pptable_id); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - int smu_v14_0_setup_pptable(struct smu_context *smu) { struct amdgpu_device *adev = smu->adev; @@ -351,7 +270,7 @@ int smu_v14_0_setup_pptable(struct smu_context *smu) if ((amdgpu_sriov_vf(adev) || !pptable_id) && (amdgpu_emu_mode != 1)) ret = smu_v14_0_get_pptable_from_vbios(smu, &table, &size); else - ret = smu_v14_0_get_pptable_from_firmware(smu, &table, &size, pptable_id); + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, pptable_id); if (ret) return ret; -- cgit v1.2.3 From 5afa19c41ee40d3ad30d9b24ca8151fce12d21f7 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 20 May 2026 19:46:19 +0530 Subject: drm/amd/pm: Use helper to get pptable in SMUv15 Use common helper function to get pptable from firmware binary in SMUv15. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h | 4 -- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c | 85 +------------------------- 2 files changed, 2 insertions(+), 87 deletions(-) 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 e6fd8be2cc4a..13723d45a7de 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 @@ -211,10 +211,6 @@ int smu_v15_0_deep_sleep_control(struct smu_context *smu, int smu_v15_0_set_gfx_power_up_by_imu(struct smu_context *smu); -int smu_v15_0_get_pptable_from_firmware(struct smu_context *smu, - void **table, - uint32_t *size, - uint32_t pptable_id); int smu_v15_0_od_edit_dpm_table(struct smu_context *smu, enum PP_OD_DPM_TABLE_COMMAND type, diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c index a1318409e4b5..f3fb6ed4bc95 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c @@ -174,7 +174,7 @@ int smu_v15_0_init_pptable_microcode(struct smu_context *smu) if (!pptable_id) return 0; - ret = smu_v15_0_get_pptable_from_firmware(smu, &table, &size, pptable_id); + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, pptable_id); if (ret) return ret; @@ -207,48 +207,6 @@ int smu_v15_0_check_fw_status(struct smu_context *smu) return -EIO; } -static int smu_v15_0_set_pptable_v2_0(struct smu_context *smu, void **table, uint32_t *size) -{ - struct amdgpu_device *adev = smu->adev; - uint32_t ppt_offset_bytes; - const struct smc_firmware_header_v2_0 *v2; - - v2 = (const struct smc_firmware_header_v2_0 *) adev->pm.fw->data; - - ppt_offset_bytes = le32_to_cpu(v2->ppt_offset_bytes); - *size = le32_to_cpu(v2->ppt_size_bytes); - *table = (uint8_t *)v2 + ppt_offset_bytes; - - return 0; -} - -static int smu_v15_0_set_pptable_v2_1(struct smu_context *smu, void **table, - uint32_t *size, uint32_t pptable_id) -{ - struct amdgpu_device *adev = smu->adev; - const struct smc_firmware_header_v2_1 *v2_1; - struct smc_soft_pptable_entry *entries; - uint32_t pptable_count = 0; - int i = 0; - - v2_1 = (const struct smc_firmware_header_v2_1 *) adev->pm.fw->data; - entries = (struct smc_soft_pptable_entry *) - ((uint8_t *)v2_1 + le32_to_cpu(v2_1->pptable_entry_offset)); - pptable_count = le32_to_cpu(v2_1->pptable_count); - for (i = 0; i < pptable_count; i++) { - if (le32_to_cpu(entries[i].id) == pptable_id) { - *table = ((uint8_t *)v2_1 + le32_to_cpu(entries[i].ppt_offset_bytes)); - *size = le32_to_cpu(entries[i].ppt_size_bytes); - break; - } - } - - if (i == pptable_count) - return -EINVAL; - - return 0; -} - static int smu_v15_0_get_pptable_from_vbios(struct smu_context *smu, void **table, uint32_t *size) { struct amdgpu_device *adev = smu->adev; @@ -271,45 +229,6 @@ static int smu_v15_0_get_pptable_from_vbios(struct smu_context *smu, void **tabl return 0; } -int smu_v15_0_get_pptable_from_firmware(struct smu_context *smu, - void **table, - uint32_t *size, - uint32_t pptable_id) -{ - const struct smc_firmware_header_v1_0 *hdr; - struct amdgpu_device *adev = smu->adev; - uint16_t version_major, version_minor; - int ret; - - hdr = (const struct smc_firmware_header_v1_0 *) adev->pm.fw->data; - if (!hdr) - return -EINVAL; - - dev_info(adev->dev, "use driver provided pptable %d\n", pptable_id); - - version_major = le16_to_cpu(hdr->header.header_version_major); - version_minor = le16_to_cpu(hdr->header.header_version_minor); - if (version_major != 2) { - dev_err(adev->dev, "Unsupported smu firmware version %d.%d\n", - version_major, version_minor); - return -EINVAL; - } - - switch (version_minor) { - case 0: - ret = smu_v15_0_set_pptable_v2_0(smu, table, size); - break; - case 1: - ret = smu_v15_0_set_pptable_v2_1(smu, table, size, pptable_id); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - int smu_v15_0_setup_pptable(struct smu_context *smu) { struct amdgpu_device *adev = smu->adev; @@ -329,7 +248,7 @@ int smu_v15_0_setup_pptable(struct smu_context *smu) if ((amdgpu_sriov_vf(adev) || !pptable_id) && (amdgpu_emu_mode != 1)) ret = smu_v15_0_get_pptable_from_vbios(smu, &table, &size); else - ret = smu_v15_0_get_pptable_from_firmware(smu, &table, &size, pptable_id); + ret = smu_cmn_get_pptable_from_firmware(smu, &table, &size, pptable_id); if (ret) return ret; -- cgit v1.2.3 From b978b7d43963bbb3e2d850288f7c21ac11c3b751 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Mon, 18 May 2026 17:43:32 +0530 Subject: drm/amdgpu: Validate ATIF buffer length before use Add a min_size parameter to amdgpu_atif_call() to validate that the returned ACPI buffer is of type ACPI_TYPE_BUFFER, holds at least a u16 size field, does not claim more data than was actually returned, and meets the minimum size required by the calling function. Each caller passes its required minimum via sizeof() or offsetof() of the expected output struct and drops its own size check. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c | 68 ++++++++++++++++---------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c index 516ab9cf88fc..7f5abb03be1b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c @@ -140,13 +140,15 @@ static struct amdgpu_acpi_priv { * @atif: atif structure * @function: the ATIF function to execute * @params: ATIF function params + * @min_size: minimum size of the expected output buffer in bytes * * Executes the requested ATIF function (all asics). * Returns a pointer to the acpi output buffer. */ static union acpi_object *amdgpu_atif_call(struct amdgpu_atif *atif, int function, - struct acpi_buffer *params) + struct acpi_buffer *params, + size_t min_size) { acpi_status status; union acpi_object *obj; @@ -189,6 +191,28 @@ static union acpi_object *amdgpu_atif_call(struct amdgpu_atif *atif, return NULL; } + if (obj->buffer.length < sizeof(u16)) { + DRM_DEBUG_DRIVER("ATIF buffer too small to hold size field: %u\n", + obj->buffer.length); + kfree(obj); + return NULL; + } + + if (obj->buffer.length < *(u16 *)obj->buffer.pointer) { + DRM_DEBUG_DRIVER("ATIF buffer length mismatch: reported %u, actual %u\n", + *(u16 *)obj->buffer.pointer, + obj->buffer.length); + kfree(obj); + return NULL; + } + + if (*(u16 *)obj->buffer.pointer < min_size) { + DRM_DEBUG_DRIVER("ATIF buffer too small: expected %zu, got %u\n", + min_size, *(u16 *)obj->buffer.pointer); + kfree(obj); + return NULL; + } + return obj; } @@ -251,19 +275,14 @@ int amdgpu_atif_verify_interface(struct amdgpu_atif *atif) size_t size; int err = 0; - info = amdgpu_atif_call(atif, ATIF_FUNCTION_VERIFY_INTERFACE, NULL); + info = amdgpu_atif_call(atif, ATIF_FUNCTION_VERIFY_INTERFACE, NULL, + sizeof(output)); if (!info) return -EIO; memset(&output, 0, sizeof(output)); - size = *(u16 *) info->buffer.pointer; - if (size < 12) { - DRM_INFO("ATIF buffer is too small: %zu\n", size); - err = -EINVAL; - goto out; - } - size = min(sizeof(output), size); + size = min(sizeof(output), (size_t)*(u16 *)info->buffer.pointer); memcpy(&output, info->buffer.pointer, size); @@ -273,7 +292,6 @@ int amdgpu_atif_verify_interface(struct amdgpu_atif *atif) amdgpu_atif_parse_notification(&atif->notifications, output.notification_mask); amdgpu_atif_parse_functions(&atif->functions, output.function_bits); -out: kfree(info); return err; } @@ -299,20 +317,14 @@ int amdgpu_atif_get_notification_params(struct amdgpu_atif *atif) int err = 0; info = amdgpu_atif_call(atif, ATIF_FUNCTION_GET_SYSTEM_PARAMETERS, - NULL); + NULL, offsetof(struct atif_system_params, command_code)); if (!info) { err = -EIO; goto out; } - size = *(u16 *) info->buffer.pointer; - if (size < 10) { - err = -EINVAL; - goto out; - } - memset(¶ms, 0, sizeof(params)); - size = min(sizeof(params), size); + size = min(sizeof(params), (size_t)*(u16 *)info->buffer.pointer); memcpy(¶ms, info->buffer.pointer, size); DRM_DEBUG_DRIVER("SYSTEM_PARAMS: mask = %#x, flags = %#x\n", @@ -376,20 +388,14 @@ int amdgpu_atif_query_backlight_caps(struct amdgpu_atif *atif) info = amdgpu_atif_call(atif, ATIF_FUNCTION_QUERY_BRIGHTNESS_TRANSFER_CHARACTERISTICS, - ¶ms); + ¶ms, offsetof(struct atif_qbtc_output, data_points)); if (!info) { err = -EIO; goto out; } - size = *(u16 *) info->buffer.pointer; - if (size < 10) { - err = -EINVAL; - goto out; - } - memset(&characteristics, 0, sizeof(characteristics)); - size = min(sizeof(characteristics), size); + size = min(sizeof(characteristics), (size_t)*(u16 *)info->buffer.pointer); memcpy(&characteristics, info->buffer.pointer, size); atif->backlight_caps.caps_valid = true; @@ -427,24 +433,18 @@ static int amdgpu_atif_get_sbios_requests(struct amdgpu_atif *atif, int count = 0; info = amdgpu_atif_call(atif, ATIF_FUNCTION_GET_SYSTEM_BIOS_REQUESTS, - NULL); + NULL, sizeof(*req)); if (!info) return -EIO; - size = *(u16 *)info->buffer.pointer; - if (size < 0xd) { - count = -EINVAL; - goto out; - } memset(req, 0, sizeof(*req)); - size = min(sizeof(*req), size); + size = min(sizeof(*req), (size_t)*(u16 *)info->buffer.pointer); memcpy(req, info->buffer.pointer, size); DRM_DEBUG_DRIVER("SBIOS pending requests: %#x\n", req->pending); count = hweight32(req->pending); -out: kfree(info); return count; } -- cgit v1.2.3 From 916867cd75dbbc383aa6cb724556de769912157a Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Mon, 18 May 2026 17:43:44 +0530 Subject: drm/amdgpu: Validate ATPX buffer length before use Add amdgpu_atpx_buffer_validate() to check that the returned ACPI buffer is of type ACPI_TYPE_BUFFER, is large enough to hold the u16 size field, and that the BIOS-reported size does not exceed the actual allocation length or fall below the minimum required by the caller. Use it in VERIFY_INTERFACE and GET_PX_PARAMETERS callers. Signed-off-by: Lijo Lazar Assisted-by: Claude Sonnet (Cursor AI) Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c | 33 +++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c index 3893e6fc2f03..e2a4644896ca 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c @@ -89,6 +89,15 @@ bool amdgpu_is_atpx_hybrid(void) return amdgpu_atpx_priv.atpx.is_hybrid; } +static bool amdgpu_atpx_buffer_validate(const union acpi_object *obj, + size_t min_size) +{ + return obj && obj->type == ACPI_TYPE_BUFFER && + obj->buffer.length >= sizeof(u16) && + obj->buffer.length >= *(u16 *)obj->buffer.pointer && + *(u16 *)obj->buffer.pointer >= min_size; +} + /** * amdgpu_atpx_call - call an ATPX method * @@ -179,15 +188,15 @@ static int amdgpu_atpx_validate(struct amdgpu_atpx *atpx) if (!info) return -EIO; - memset(&output, 0, sizeof(output)); - - size = *(u16 *) info->buffer.pointer; - if (size < 10) { - pr_err("ATPX buffer is too small: %zu\n", size); + if (!amdgpu_atpx_buffer_validate(info, sizeof(output))) { + pr_err("Invalid ATPX GET_PX_PARAMETERS response\n"); kfree(info); return -EINVAL; } - size = min(sizeof(output), size); + + memset(&output, 0, sizeof(output)); + + size = min(sizeof(output), (size_t)*(u16 *)info->buffer.pointer); memcpy(&output, info->buffer.pointer, size); @@ -258,15 +267,15 @@ static int amdgpu_atpx_verify_interface(struct amdgpu_atpx *atpx) if (!info) return -EIO; - memset(&output, 0, sizeof(output)); - - size = *(u16 *) info->buffer.pointer; - if (size < 8) { - pr_err("ATPX buffer is too small: %zu\n", size); + if (!amdgpu_atpx_buffer_validate(info, sizeof(output))) { + pr_err("Invalid ATPX VERIFY_INTERFACE response\n"); err = -EINVAL; goto out; } - size = min(sizeof(output), size); + + memset(&output, 0, sizeof(output)); + + size = min(sizeof(output), (size_t)*(u16 *)info->buffer.pointer); memcpy(&output, info->buffer.pointer, size); -- cgit v1.2.3 From fed5bdbfe1d4a19a26c70f7fc58017dc88be1c18 Mon Sep 17 00:00:00 2001 From: Jakob Linke Date: Wed, 17 Jun 2026 08:24:15 +0200 Subject: drm/amdgpu/soc24: reset dGPU if suspend got aborted For SOC24 ASICs (RDNA4 / Navi 4x dGPUs) re-enabling PM features fails if an S3 suspend got aborted, the same issue already handled for SOC21 and SOC15: commit df3c7dc5c58b ("drm/amdgpu: Reset dGPU if suspend got aborted") commit 38e8ca3e4b6d ("amdgpu/soc15: enable asic reset for dGPU in case of suspend abort") The aborted resume fails with: amdgpu: SMU: No response msg_reg: 6 resp_reg: 0 amdgpu: Failed to enable requested dpm features! amdgpu: resume of IP block failed -62 Apply the same workaround for soc24: detect the aborted-suspend state at resume via the sign-of-life register and reset the device before re-init. This is a workaround till a proper solution is finalized. Fixes: 98b912c50e44 ("drm/amdgpu: Add soc24 common ip block (v2)") Signed-off-by: Jakob Linke Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/soc24.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/soc24.c b/drivers/gpu/drm/amd/amdgpu/soc24.c index 265db9331d0b..9dce30d2bb8d 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc24.c +++ b/drivers/gpu/drm/amd/amdgpu/soc24.c @@ -496,8 +496,36 @@ static int soc24_common_suspend(struct amdgpu_ip_block *ip_block) return soc24_common_hw_fini(ip_block); } +static bool soc24_need_reset_on_resume(struct amdgpu_device *adev) +{ + u32 sol_reg1, sol_reg2; + + /* Will reset for the following suspend abort cases. + * 1) Only reset dGPU side. + * 2) S3 suspend got aborted and TOS is active. + * As for dGPU suspend abort cases the SOL value + * will be kept as zero at this resume point. + */ + if (!(adev->flags & AMD_IS_APU) && adev->in_s3) { + sol_reg1 = RREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_81); + msleep(100); + sol_reg2 = RREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_81); + + return (sol_reg1 != sol_reg2); + } + + return false; +} + static int soc24_common_resume(struct amdgpu_ip_block *ip_block) { + struct amdgpu_device *adev = ip_block->adev; + + if (soc24_need_reset_on_resume(adev)) { + dev_info(adev->dev, "S3 suspend aborted, resetting..."); + soc24_asic_reset(adev); + } + return soc24_common_hw_init(ip_block); } -- cgit v1.2.3 From 7445035dd3f22fc4c151058304b2dc6df4aca59b Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Thu, 18 Jun 2026 10:45:10 +0530 Subject: drm/amdgpu: bounds check xcp ip block index Check out of range values for ip block. Signed-off-by: Lijo Lazar Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c index 9202ddf3d69c..c6b43353e08e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c @@ -381,7 +381,8 @@ int amdgpu_xcp_get_inst_details(struct amdgpu_xcp *xcp, enum AMDGPU_XCP_IP_BLOCK ip, uint32_t *inst_mask) { - if (!xcp->valid || !inst_mask || !(xcp->ip[ip].valid)) + if (!xcp->valid || !inst_mask || ip >= AMDGPU_XCP_MAX_BLOCKS || + !(xcp->ip[ip].valid)) return -EINVAL; *inst_mask = xcp->ip[ip].inst_mask; -- cgit v1.2.3 From bf939f2a1687ef4ab815640096357f8ef5bd5fb6 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Thu, 18 Jun 2026 10:56:54 +0530 Subject: drm/amd/pm: validate vega10 profile mode inputs Check for out of range profile modes and custom params that exceed 8 bits. Signed-off-by: Lijo Lazar Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 9 +++++++++ 1 file changed, 9 insertions(+) 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 a5896ce59097..629815f0c5d4 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -5215,6 +5215,11 @@ static int vega10_set_power_profile_mode(struct pp_hwmgr *hwmgr, long *input, ui uint8_t min_active_level; uint32_t power_profile_mode = input[size]; + if (power_profile_mode > PP_SMC_POWER_PROFILE_CUSTOM) { + pr_err("Invalid power profile mode %u\n", power_profile_mode); + return -EINVAL; + } + if (power_profile_mode == PP_SMC_POWER_PROFILE_CUSTOM) { if (size != 0 && size != 4) return -EINVAL; @@ -5230,6 +5235,10 @@ static int vega10_set_power_profile_mode(struct pp_hwmgr *hwmgr, long *input, ui return -EINVAL; } + if ((input[0] & ~0xFF) || (input[1] & ~0xFF) || + (input[2] & ~0xFF) || (input[3] & ~0xFF)) + return -EINVAL; + data->custom_profile_mode[0] = busy_set_point = input[0]; data->custom_profile_mode[1] = FPS = input[1]; data->custom_profile_mode[2] = use_rlc_busy = input[2]; -- cgit v1.2.3 From b9086b6e75b982ba72496134c892f16d99822e19 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 17:12:30 -0400 Subject: drm/amdgpu/gmc9: make all vmids available to KFD if KQs are disabled If the user has disabled kernel queues, then make all vmids available to HWS. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c index 8a5c44810ba1..5166055c6692 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c @@ -2025,11 +2025,19 @@ static int gmc_v9_0_sw_init(struct amdgpu_ip_block *ip_block) * The first KFD VMID is 8 for GPUs with graphics, 3 for * compute-only GPUs. On compute-only GPUs that leaves 2 VMIDs * for video processing. + * + * If kernel queues are disabled, allow KFD to use all vmids. */ - adev->vm_manager.first_kfd_vmid = - (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 1) || - amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 2) || - amdgpu_is_multi_aid(adev)) ? + if (adev->gfx.disable_kq && + adev->jpeg.disable_kq && + adev->vcn.disable_kq && + adev->sdma.no_user_submission) + adev->vm_manager.first_kfd_vmid = 1; + else + adev->vm_manager.first_kfd_vmid = + (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 1) || + amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 2) || + amdgpu_is_multi_aid(adev)) ? 3 : 8; -- cgit v1.2.3 From 6e8a3c24bd75f057b7e6d5d90829550b7af44496 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Tue, 16 Jun 2026 10:57:44 +0530 Subject: drm/amdgpu: bounds check xcp_id in release_sched Avoid out-of-bounds xcp[] access, e.g. when xcp_id is AMDGPU_XCP_NO_PARTITION. Signed-off-by: Lijo Lazar Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c index c6b43353e08e..cf71b4f55252 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c @@ -476,7 +476,8 @@ void amdgpu_xcp_release_sched(struct amdgpu_device *adev, if (drm_sched_wqueue_ready(sched)) { struct amdgpu_ring *ring = to_amdgpu_ring(sched); - atomic_dec(&adev->xcp_mgr->xcp[ring->xcp_id].ref_cnt); + if (ring->xcp_id < MAX_XCP) + atomic_dec(&adev->xcp_mgr->xcp[ring->xcp_id].ref_cnt); } } -- cgit v1.2.3 From 73826ae3cbe2ffc79576dcddd467ba981790e2e6 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:12 +0200 Subject: drm/amdgpu: Clarify name of soft recovery to avoid confusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Soft recovery is not the same as soft reset: * Soft recovery attempts to resolve a GPU hang by sending a command to terminate shaders. * Soft reset completely re-initializes an entire device IP block, which may affect multiple rings and jobs at the same time. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 +++--- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 4213272637d8..55a10d4a3a60 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -305,7 +305,7 @@ extern uint amdgpu_hdmi_hpd_debounce_delay_ms; /* reset mask */ #define AMDGPU_RESET_TYPE_FULL (1 << 0) /* full adapter reset, mode1/mode2/BACO/etc. */ -#define AMDGPU_RESET_TYPE_SOFT_RESET (1 << 1) /* IP level soft reset */ +#define AMDGPU_RESET_TYPE_SOFT_RECOVERY (1 << 1) /* soft recovery, eg. kill shaders */ #define AMDGPU_RESET_TYPE_PER_QUEUE (1 << 2) /* per queue */ #define AMDGPU_RESET_TYPE_PER_PIPE (1 << 3) /* per pipe */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index c66d3a24f54e..c9ea0c3ac935 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -6899,7 +6899,7 @@ ssize_t amdgpu_get_soft_full_reset_mask(struct amdgpu_ring *ring) if (unlikely(!ring->adev->debug_disable_soft_recovery) && !amdgpu_sriov_vf(ring->adev) && ring->funcs->soft_recovery) - size |= AMDGPU_RESET_TYPE_SOFT_RESET; + size |= AMDGPU_RESET_TYPE_SOFT_RECOVERY; return size; } @@ -6915,8 +6915,8 @@ ssize_t amdgpu_show_reset_mask(char *buf, uint32_t supported_reset) } - if (supported_reset & AMDGPU_RESET_TYPE_SOFT_RESET) - size += sysfs_emit_at(buf, size, "soft "); + if (supported_reset & AMDGPU_RESET_TYPE_SOFT_RECOVERY) + size += sysfs_emit_at(buf, size, "soft_recovery "); if (supported_reset & AMDGPU_RESET_TYPE_PER_QUEUE) size += sysfs_emit_at(buf, size, "queue "); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 65f2de86fdd2..157c0f260cc0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2250,7 +2250,7 @@ static void amdgpu_init_debug_options(struct amdgpu_device *adev) } if (amdgpu_debug_mask & AMDGPU_DEBUG_DISABLE_GPU_SOFT_RECOVERY) { - pr_info("debug: soft reset for GPU recovery disabled\n"); + pr_info("debug: soft recovery disabled\n"); adev->debug_disable_soft_recovery = true; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index 9ecc6387c1eb..8c40eb8cec51 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -112,7 +112,7 @@ static enum drm_gpu_sched_stat amdgpu_job_timedout(struct drm_sched_job *s_job) amdgpu_job_core_dump(adev, job); if (amdgpu_gpu_recovery && - amdgpu_ring_is_reset_type_supported(ring, AMDGPU_RESET_TYPE_SOFT_RESET) && + amdgpu_ring_is_reset_type_supported(ring, AMDGPU_RESET_TYPE_SOFT_RECOVERY) && amdgpu_ring_soft_recovery(ring, job->vmid, s_job->s_fence->parent)) { dev_err(adev->dev, "ring %s timeout, but soft recovered\n", s_job->sched->name); -- cgit v1.2.3 From a86f14aebb8f1e017a32f326ca177a149690e74e Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:13 +0200 Subject: drm/amdgpu: Clean up defunct soft reset from ASIC reset code path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Soft reset means resetting IP blocks individually using a hardware interconnect (SRBM or GRBM) without assistance from firmware. Soft reset is a useful tool for implementing GPU recovery, eg. it is already successfully used for SDMA queue resets. It should be used by a GPU recovery method instead of being called directly from the ASIC reset code path. Currently, this is only used on Carrizo and Stoney, but doesn't work well and fails on those chips. A subsequent commit will add a working GFX8 recovery implementation after the cleanups. Note that this commit only cleans up the ASIC reset path, which also unblocks more opportunities for cleanup for the various IP blocks. Those will be done in subsequent commits. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 - drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 173 +---------------------------- drivers/gpu/drm/amd/amdgpu/cik.c | 7 -- drivers/gpu/drm/amd/amdgpu/nv.c | 6 - drivers/gpu/drm/amd/amdgpu/si.c | 7 -- drivers/gpu/drm/amd/amdgpu/soc15.c | 9 -- drivers/gpu/drm/amd/amdgpu/soc21.c | 12 -- drivers/gpu/drm/amd/amdgpu/soc24.c | 11 -- drivers/gpu/drm/amd/amdgpu/soc_v1_0.c | 10 -- drivers/gpu/drm/amd/amdgpu/vi.c | 22 ---- 10 files changed, 2 insertions(+), 258 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 55a10d4a3a60..3178e5f9b415 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -586,8 +586,6 @@ struct amdgpu_asic_funcs { /* invalidate hdp read cache */ void (*invalidate_hdp)(struct amdgpu_device *adev, struct amdgpu_ring *ring); - /* check if the asic needs a full reset of if soft reset will work */ - bool (*need_full_reset)(struct amdgpu_device *adev); /* initialize doorbell layout for specific asic*/ void (*init_doorbell_index)(struct amdgpu_device *adev); /* PCIe bandwidth usage */ @@ -1356,7 +1354,6 @@ int emu_soc_asic_init(struct amdgpu_device *adev); #define amdgpu_asic_read_bios_from_rom(adev, b, l) (adev)->asic_funcs->read_bios_from_rom((adev), (b), (l)) #define amdgpu_asic_read_register(adev, se, sh, offset, v)((adev)->asic_funcs->read_register((adev), (se), (sh), (offset), (v))) #define amdgpu_asic_get_config_memsize(adev) (adev)->asic_funcs->get_config_memsize((adev)) -#define amdgpu_asic_need_full_reset(adev) (adev)->asic_funcs->need_full_reset((adev)) #define amdgpu_asic_init_doorbell_index(adev) (adev)->asic_funcs->init_doorbell_index((adev)) #define amdgpu_asic_get_pcie_usage(adev, cnt0, cnt1) ((adev)->asic_funcs->get_pcie_usage((adev), (cnt0), (cnt1))) #define amdgpu_asic_need_reset_on_init(adev) (adev)->asic_funcs->need_reset_on_init((adev)) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index c9ea0c3ac935..b427d963c604 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4714,161 +4714,6 @@ exit: return 0; } -/** - * amdgpu_device_ip_check_soft_reset - did soft reset succeed - * - * @adev: amdgpu_device pointer - * - * The list of all the hardware IPs that make up the asic is walked and - * the check_soft_reset callbacks are run. check_soft_reset determines - * if the asic is still hung or not. - * Returns true if any of the IPs are still in a hung state, false if not. - */ -static bool amdgpu_device_ip_check_soft_reset(struct amdgpu_device *adev) -{ - int i; - bool asic_hang = false; - - if (amdgpu_sriov_vf(adev)) - return true; - - if (amdgpu_asic_need_full_reset(adev)) - return true; - - for (i = 0; i < adev->num_ip_blocks; i++) { - if (!adev->ip_blocks[i].status.valid) - continue; - if (adev->ip_blocks[i].version->funcs->check_soft_reset) - adev->ip_blocks[i].status.hang = - adev->ip_blocks[i].version->funcs->check_soft_reset( - &adev->ip_blocks[i]); - if (adev->ip_blocks[i].status.hang) { - dev_info(adev->dev, "IP block:%s is hung!\n", adev->ip_blocks[i].version->funcs->name); - asic_hang = true; - } - } - return asic_hang; -} - -/** - * amdgpu_device_ip_pre_soft_reset - prepare for soft reset - * - * @adev: amdgpu_device pointer - * - * The list of all the hardware IPs that make up the asic is walked and the - * pre_soft_reset callbacks are run if the block is hung. pre_soft_reset - * handles any IP specific hardware or software state changes that are - * necessary for a soft reset to succeed. - * Returns 0 on success, negative error code on failure. - */ -static int amdgpu_device_ip_pre_soft_reset(struct amdgpu_device *adev) -{ - int i, r = 0; - - for (i = 0; i < adev->num_ip_blocks; i++) { - if (!adev->ip_blocks[i].status.valid) - continue; - if (adev->ip_blocks[i].status.hang && - adev->ip_blocks[i].version->funcs->pre_soft_reset) { - r = adev->ip_blocks[i].version->funcs->pre_soft_reset(&adev->ip_blocks[i]); - if (r) - return r; - } - } - - return 0; -} - -/** - * amdgpu_device_ip_need_full_reset - check if a full asic reset is needed - * - * @adev: amdgpu_device pointer - * - * Some hardware IPs cannot be soft reset. If they are hung, a full gpu - * reset is necessary to recover. - * Returns true if a full asic reset is required, false if not. - */ -static bool amdgpu_device_ip_need_full_reset(struct amdgpu_device *adev) -{ - int i; - - if (amdgpu_asic_need_full_reset(adev)) - return true; - - for (i = 0; i < adev->num_ip_blocks; i++) { - if (!adev->ip_blocks[i].status.valid) - continue; - if ((adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_GMC) || - (adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_SMC) || - (adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_ACP) || - (adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_DCE) || - adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_PSP) { - if (adev->ip_blocks[i].status.hang) { - dev_info(adev->dev, "Some block need full reset!\n"); - return true; - } - } - } - return false; -} - -/** - * amdgpu_device_ip_soft_reset - do a soft reset - * - * @adev: amdgpu_device pointer - * - * The list of all the hardware IPs that make up the asic is walked and the - * soft_reset callbacks are run if the block is hung. soft_reset handles any - * IP specific hardware or software state changes that are necessary to soft - * reset the IP. - * Returns 0 on success, negative error code on failure. - */ -static int amdgpu_device_ip_soft_reset(struct amdgpu_device *adev) -{ - int i, r = 0; - - for (i = 0; i < adev->num_ip_blocks; i++) { - if (!adev->ip_blocks[i].status.valid) - continue; - if (adev->ip_blocks[i].status.hang && - adev->ip_blocks[i].version->funcs->soft_reset) { - r = adev->ip_blocks[i].version->funcs->soft_reset(&adev->ip_blocks[i]); - if (r) - return r; - } - } - - return 0; -} - -/** - * amdgpu_device_ip_post_soft_reset - clean up from soft reset - * - * @adev: amdgpu_device pointer - * - * The list of all the hardware IPs that make up the asic is walked and the - * post_soft_reset callbacks are run if the asic was hung. post_soft_reset - * handles any IP specific hardware or software state changes that are - * necessary after the IP has been soft reset. - * Returns 0 on success, negative error code on failure. - */ -static int amdgpu_device_ip_post_soft_reset(struct amdgpu_device *adev) -{ - int i, r = 0; - - for (i = 0; i < adev->num_ip_blocks; i++) { - if (!adev->ip_blocks[i].status.valid) - continue; - if (adev->ip_blocks[i].status.hang && - adev->ip_blocks[i].version->funcs->post_soft_reset) - r = adev->ip_blocks[i].version->funcs->post_soft_reset(&adev->ip_blocks[i]); - if (r) - return r; - } - - return 0; -} - /** * amdgpu_device_reset_sriov - reset ASIC for SR-IOV vf * @@ -5160,20 +5005,7 @@ int amdgpu_device_pre_asic_reset(struct amdgpu_device *adev, /* Don't suspend on bare metal if we are not going to HW reset the ASIC */ if (!amdgpu_sriov_vf(adev)) { - - if (!need_full_reset) - need_full_reset = amdgpu_device_ip_need_full_reset(adev); - - if (!need_full_reset && amdgpu_gpu_recovery && - amdgpu_device_ip_check_soft_reset(adev)) { - amdgpu_device_ip_pre_soft_reset(adev); - r = amdgpu_device_ip_soft_reset(adev); - amdgpu_device_ip_post_soft_reset(adev); - if (r || amdgpu_device_ip_check_soft_reset(adev)) { - dev_info(adev->dev, "soft reset failed, will fallback to full reset!\n"); - need_full_reset = true; - } - } + need_full_reset = true; if (!test_bit(AMDGPU_SKIP_COREDUMP, &reset_context->flags)) { dev_info(tmp_adev->dev, "Dumping IP State\n"); @@ -5626,8 +5458,7 @@ static void amdgpu_device_halt_activities(struct amdgpu_device *adev, drm_client_dev_suspend(adev_to_drm(tmp_adev)); /* disable ras on ALL IPs */ - if (!need_emergency_restart && !amdgpu_reset_in_dpc(adev) && - amdgpu_device_ip_need_full_reset(tmp_adev)) + if (!need_emergency_restart && !amdgpu_reset_in_dpc(adev)) amdgpu_ras_suspend(tmp_adev); amdgpu_userq_pre_reset(tmp_adev); diff --git a/drivers/gpu/drm/amd/amdgpu/cik.c b/drivers/gpu/drm/amd/amdgpu/cik.c index 29954c7d61b0..77e120a72815 100644 --- a/drivers/gpu/drm/amd/amdgpu/cik.c +++ b/drivers/gpu/drm/amd/amdgpu/cik.c @@ -1876,12 +1876,6 @@ static void cik_invalidate_hdp(struct amdgpu_device *adev, } } -static bool cik_need_full_reset(struct amdgpu_device *adev) -{ - /* change this when we support soft reset */ - return true; -} - static void cik_get_pcie_usage(struct amdgpu_device *adev, uint64_t *count0, uint64_t *count1) { @@ -1971,7 +1965,6 @@ static const struct amdgpu_asic_funcs cik_asic_funcs = .get_config_memsize = &cik_get_config_memsize, .flush_hdp = &cik_flush_hdp, .invalidate_hdp = &cik_invalidate_hdp, - .need_full_reset = &cik_need_full_reset, .init_doorbell_index = &legacy_doorbell_index_init, .get_pcie_usage = &cik_get_pcie_usage, .need_reset_on_init = &cik_need_reset_on_init, diff --git a/drivers/gpu/drm/amd/amdgpu/nv.c b/drivers/gpu/drm/amd/amdgpu/nv.c index 72edf5326b05..77557ee3ca16 100644 --- a/drivers/gpu/drm/amd/amdgpu/nv.c +++ b/drivers/gpu/drm/amd/amdgpu/nv.c @@ -507,11 +507,6 @@ void nv_set_virt_ops(struct amdgpu_device *adev) adev->virt.ops = &xgpu_nv_virt_ops; } -static bool nv_need_full_reset(struct amdgpu_device *adev) -{ - return true; -} - static bool nv_need_reset_on_init(struct amdgpu_device *adev) { u32 sol_reg; @@ -595,7 +590,6 @@ static const struct amdgpu_asic_funcs nv_asic_funcs = { .set_vce_clocks = &nv_set_vce_clocks, .get_config_memsize = &nv_get_config_memsize, .init_doorbell_index = &nv_init_doorbell_index, - .need_full_reset = &nv_need_full_reset, .need_reset_on_init = &nv_need_reset_on_init, .get_pcie_replay_count = &amdgpu_nbio_get_pcie_replay_count, .supports_baco = &amdgpu_dpm_is_baco_supported, diff --git a/drivers/gpu/drm/amd/amdgpu/si.c b/drivers/gpu/drm/amd/amdgpu/si.c index c26cb3e8bff6..b104469c38ec 100644 --- a/drivers/gpu/drm/amd/amdgpu/si.c +++ b/drivers/gpu/drm/amd/amdgpu/si.c @@ -1509,12 +1509,6 @@ static void si_invalidate_hdp(struct amdgpu_device *adev, } } -static bool si_need_full_reset(struct amdgpu_device *adev) -{ - /* change this when we support soft reset */ - return true; -} - static bool si_need_reset_on_init(struct amdgpu_device *adev) { return false; @@ -2019,7 +2013,6 @@ static const struct amdgpu_asic_funcs si_asic_funcs = .get_config_memsize = &si_get_config_memsize, .flush_hdp = &si_flush_hdp, .invalidate_hdp = &si_invalidate_hdp, - .need_full_reset = &si_need_full_reset, .get_pcie_usage = &si_get_pcie_usage, .need_reset_on_init = &si_need_reset_on_init, .get_pcie_replay_count = &si_get_pcie_replay_count, diff --git a/drivers/gpu/drm/amd/amdgpu/soc15.c b/drivers/gpu/drm/amd/amdgpu/soc15.c index 87b398dd0769..ed3fd58b78d0 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc15.c +++ b/drivers/gpu/drm/amd/amdgpu/soc15.c @@ -721,12 +721,6 @@ void soc15_set_virt_ops(struct amdgpu_device *adev) soc15_reg_base_init(adev); } -static bool soc15_need_full_reset(struct amdgpu_device *adev) -{ - /* change this when we implement soft reset */ - return true; -} - static void soc15_get_pcie_usage(struct amdgpu_device *adev, uint64_t *count0, uint64_t *count1) { @@ -878,7 +872,6 @@ static const struct amdgpu_asic_funcs soc15_asic_funcs = .set_uvd_clocks = &soc15_set_uvd_clocks, .set_vce_clocks = &soc15_set_vce_clocks, .get_config_memsize = &soc15_get_config_memsize, - .need_full_reset = &soc15_need_full_reset, .init_doorbell_index = &vega10_doorbell_index_init, .get_pcie_usage = &soc15_get_pcie_usage, .need_reset_on_init = &soc15_need_reset_on_init, @@ -899,7 +892,6 @@ static const struct amdgpu_asic_funcs vega20_asic_funcs = .set_uvd_clocks = &soc15_set_uvd_clocks, .set_vce_clocks = &soc15_set_vce_clocks, .get_config_memsize = &soc15_get_config_memsize, - .need_full_reset = &soc15_need_full_reset, .init_doorbell_index = &vega20_doorbell_index_init, .get_pcie_usage = &vega20_get_pcie_usage, .need_reset_on_init = &soc15_need_reset_on_init, @@ -920,7 +912,6 @@ static const struct amdgpu_asic_funcs aqua_vanjaram_asic_funcs = .set_uvd_clocks = &soc15_set_uvd_clocks, .set_vce_clocks = &soc15_set_vce_clocks, .get_config_memsize = &soc15_get_config_memsize, - .need_full_reset = &soc15_need_full_reset, .init_doorbell_index = &aqua_vanjaram_doorbell_index_init, .need_reset_on_init = &soc15_need_reset_on_init, .get_pcie_replay_count = &amdgpu_nbio_get_pcie_replay_count, diff --git a/drivers/gpu/drm/amd/amdgpu/soc21.c b/drivers/gpu/drm/amd/amdgpu/soc21.c index 963659deeaff..223702e5c220 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc21.c +++ b/drivers/gpu/drm/amd/amdgpu/soc21.c @@ -461,17 +461,6 @@ const struct amdgpu_ip_block_version soc21_common_ip_block = { .funcs = &soc21_common_ip_funcs, }; -static bool soc21_need_full_reset(struct amdgpu_device *adev) -{ - switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { - case IP_VERSION(11, 0, 0): - case IP_VERSION(11, 0, 2): - case IP_VERSION(11, 0, 3): - default: - return true; - } -} - static bool soc21_need_reset_on_init(struct amdgpu_device *adev) { u32 sol_reg; @@ -550,7 +539,6 @@ static const struct amdgpu_asic_funcs soc21_asic_funcs = { .set_vce_clocks = &soc21_set_vce_clocks, .get_config_memsize = &soc21_get_config_memsize, .init_doorbell_index = &soc21_init_doorbell_index, - .need_full_reset = &soc21_need_full_reset, .need_reset_on_init = &soc21_need_reset_on_init, .get_pcie_replay_count = &amdgpu_nbio_get_pcie_replay_count, .supports_baco = &amdgpu_dpm_is_baco_supported, diff --git a/drivers/gpu/drm/amd/amdgpu/soc24.c b/drivers/gpu/drm/amd/amdgpu/soc24.c index 9dce30d2bb8d..e5e3a460e486 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc24.c +++ b/drivers/gpu/drm/amd/amdgpu/soc24.c @@ -238,16 +238,6 @@ const struct amdgpu_ip_block_version soc24_common_ip_block = { .funcs = &soc24_common_ip_funcs, }; -static bool soc24_need_full_reset(struct amdgpu_device *adev) -{ - switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { - case IP_VERSION(12, 0, 0): - case IP_VERSION(12, 0, 1): - default: - return true; - } -} - static bool soc24_need_reset_on_init(struct amdgpu_device *adev) { u32 sol_reg; @@ -330,7 +320,6 @@ static const struct amdgpu_asic_funcs soc24_asic_funcs = { .get_xclk = &soc24_get_xclk, .get_config_memsize = &soc24_get_config_memsize, .init_doorbell_index = &soc24_init_doorbell_index, - .need_full_reset = &soc24_need_full_reset, .need_reset_on_init = &soc24_need_reset_on_init, .get_pcie_replay_count = &soc24_get_pcie_replay_count, .supports_baco = &amdgpu_dpm_is_baco_supported, diff --git a/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c b/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c index f3f3fac435d1..a9039fb1a77b 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/soc_v1_0.c @@ -223,15 +223,6 @@ static int soc_v1_0_read_register(struct amdgpu_device *adev, return -EINVAL; } -static bool soc_v1_0_need_full_reset(struct amdgpu_device *adev) -{ - switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { - case IP_VERSION(12, 1, 0): - default: - return true; - } -} - static bool soc_v1_0_need_reset_on_init(struct amdgpu_device *adev) { @@ -271,7 +262,6 @@ static const struct amdgpu_asic_funcs soc_v1_0_asic_funcs = { .read_register = &soc_v1_0_read_register, .get_config_memsize = &soc_v1_0_get_config_memsize, .get_xclk = &soc_v1_0_get_xclk, - .need_full_reset = &soc_v1_0_need_full_reset, .init_doorbell_index = &soc_v1_0_doorbell_index_init, .need_reset_on_init = &soc_v1_0_need_reset_on_init, .encode_ext_smn_addressing = &soc_v1_0_encode_ext_smn_addressing, diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index a256320b92f3..5715b6b596af 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -1328,27 +1328,6 @@ static void vi_invalidate_hdp(struct amdgpu_device *adev, } } -static bool vi_need_full_reset(struct amdgpu_device *adev) -{ - switch (adev->asic_type) { - case CHIP_CARRIZO: - case CHIP_STONEY: - /* CZ has hang issues with full reset at the moment */ - return false; - case CHIP_FIJI: - case CHIP_TONGA: - /* XXX: soft reset should work on fiji and tonga */ - return true; - case CHIP_POLARIS10: - case CHIP_POLARIS11: - case CHIP_POLARIS12: - case CHIP_TOPAZ: - default: - /* change this when we support soft reset */ - return true; - } -} - static void vi_get_pcie_usage(struct amdgpu_device *adev, uint64_t *count0, uint64_t *count1) { @@ -1437,7 +1416,6 @@ static const struct amdgpu_asic_funcs vi_asic_funcs = .get_config_memsize = &vi_get_config_memsize, .flush_hdp = &vi_flush_hdp, .invalidate_hdp = &vi_invalidate_hdp, - .need_full_reset = &vi_need_full_reset, .init_doorbell_index = &legacy_doorbell_index_init, .get_pcie_usage = &vi_get_pcie_usage, .need_reset_on_init = &vi_need_reset_on_init, -- cgit v1.2.3 From 2172847e9787aba1735939bf743d8017df23b52d Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:14 +0200 Subject: drm/amdgpu: Delete GMC 8 soft reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We should only reset the memory controller during ASIC reset and only when it's absolutely necessary. Otherwise, resetting the memory controller typically just breaks everything and on dGPUs may also clear the contents of VRAM (it's unclear if it really does, but it's likely). Specifically for GMC 8, the memory controller is reset as part of the ASIC reset and otherwise should be left alone. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.h | 1 - drivers/gpu/drm/amd/amdgpu/gmc_v8_0.c | 125 -------------------------------- 2 files changed, 126 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.h index ddb0d500e0fa..3ca187f5ade8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.h @@ -286,7 +286,6 @@ struct amdgpu_gmc { struct amdgpu_irq_src vm_fault; uint32_t vram_type; uint8_t vram_vendor; - uint32_t srbm_soft_reset; bool prt_warning; uint32_t sdpif_register; /* apertures */ diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v8_0.c index c2a41fa3a396..64ebedc595b5 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v8_0.c @@ -167,44 +167,6 @@ static void gmc_v8_0_init_golden_registers(struct amdgpu_device *adev) } } -static void gmc_v8_0_mc_stop(struct amdgpu_device *adev) -{ - u32 blackout; - struct amdgpu_ip_block *ip_block; - - ip_block = amdgpu_device_ip_get_ip_block(adev, AMD_IP_BLOCK_TYPE_GMC); - if (!ip_block) - return; - - gmc_v8_0_wait_for_idle(ip_block); - - blackout = RREG32(mmMC_SHARED_BLACKOUT_CNTL); - if (REG_GET_FIELD(blackout, MC_SHARED_BLACKOUT_CNTL, BLACKOUT_MODE) != 1) { - /* Block CPU access */ - WREG32(mmBIF_FB_EN, 0); - /* blackout the MC */ - blackout = REG_SET_FIELD(blackout, - MC_SHARED_BLACKOUT_CNTL, BLACKOUT_MODE, 1); - WREG32(mmMC_SHARED_BLACKOUT_CNTL, blackout); - } - /* wait for the MC to settle */ - udelay(100); -} - -static void gmc_v8_0_mc_resume(struct amdgpu_device *adev) -{ - u32 tmp; - - /* unblackout the MC */ - tmp = RREG32(mmMC_SHARED_BLACKOUT_CNTL); - tmp = REG_SET_FIELD(tmp, MC_SHARED_BLACKOUT_CNTL, BLACKOUT_MODE, 0); - WREG32(mmMC_SHARED_BLACKOUT_CNTL, tmp); - /* allow CPU access */ - tmp = REG_SET_FIELD(0, BIF_FB_EN, FB_READ_EN, 1); - tmp = REG_SET_FIELD(tmp, BIF_FB_EN, FB_WRITE_EN, 1); - WREG32(mmBIF_FB_EN, tmp); -} - /** * gmc_v8_0_init_microcode - load ucode images from disk * @@ -1293,89 +1255,6 @@ static int gmc_v8_0_wait_for_idle(struct amdgpu_ip_block *ip_block) } -static bool gmc_v8_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - u32 srbm_soft_reset = 0; - struct amdgpu_device *adev = ip_block->adev; - u32 tmp = RREG32(mmSRBM_STATUS); - - if (tmp & SRBM_STATUS__VMC_BUSY_MASK) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_VMC, 1); - - if (tmp & (SRBM_STATUS__MCB_BUSY_MASK | SRBM_STATUS__MCB_NON_DISPLAY_BUSY_MASK | - SRBM_STATUS__MCC_BUSY_MASK | SRBM_STATUS__MCD_BUSY_MASK)) { - if (!(adev->flags & AMD_IS_APU)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_MC, 1); - } - - if (srbm_soft_reset) { - adev->gmc.srbm_soft_reset = srbm_soft_reset; - return true; - } - - adev->gmc.srbm_soft_reset = 0; - - return false; -} - -static int gmc_v8_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->gmc.srbm_soft_reset) - return 0; - - gmc_v8_0_mc_stop(adev); - if (gmc_v8_0_wait_for_idle(ip_block)) - dev_warn(adev->dev, "Wait for GMC idle timed out !\n"); - - return 0; -} - -static int gmc_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset; - - if (!adev->gmc.srbm_soft_reset) - return 0; - srbm_soft_reset = adev->gmc.srbm_soft_reset; - - if (srbm_soft_reset) { - u32 tmp; - - tmp = RREG32(mmSRBM_SOFT_RESET); - tmp |= srbm_soft_reset; - dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - udelay(50); - - tmp &= ~srbm_soft_reset; - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - /* Wait a little for things to settle down */ - udelay(50); - } - - return 0; -} - -static int gmc_v8_0_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->gmc.srbm_soft_reset) - return 0; - - gmc_v8_0_mc_resume(adev); - return 0; -} - static int gmc_v8_0_vm_fault_interrupt_state(struct amdgpu_device *adev, struct amdgpu_irq_src *src, unsigned int type, @@ -1715,10 +1594,6 @@ static const struct amd_ip_funcs gmc_v8_0_ip_funcs = { .resume = gmc_v8_0_resume, .is_idle = gmc_v8_0_is_idle, .wait_for_idle = gmc_v8_0_wait_for_idle, - .check_soft_reset = gmc_v8_0_check_soft_reset, - .pre_soft_reset = gmc_v8_0_pre_soft_reset, - .soft_reset = gmc_v8_0_soft_reset, - .post_soft_reset = gmc_v8_0_post_soft_reset, .set_clockgating_state = gmc_v8_0_set_clockgating_state, .set_powergating_state = gmc_v8_0_set_powergating_state, .get_clockgating_state = gmc_v8_0_get_clockgating_state, -- cgit v1.2.3 From 64e31a35eb1a4e21932f8fe658f2a7a9de6fbdb0 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:15 +0200 Subject: drm/amdgpu: Delete soft reset code from legacy display driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was basically dead code, not used or called from anywhere. Now that DC is the default display driver for all ASICs, it is unlikely that anyone wants to develop this further. Display hang related work should be focused on DC. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/dce_v10_0.c | 66 ---------------------------------- drivers/gpu/drm/amd/amdgpu/dce_v6_0.c | 57 ----------------------------- drivers/gpu/drm/amd/amdgpu/dce_v8_0.c | 57 ----------------------------- 3 files changed, 180 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/dce_v10_0.c b/drivers/gpu/drm/amd/amdgpu/dce_v10_0.c index c8f465158e71..f2977fe6d824 100644 --- a/drivers/gpu/drm/amd/amdgpu/dce_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/dce_v10_0.c @@ -410,36 +410,6 @@ static u32 dce_v10_0_hpd_get_gpio_reg(struct amdgpu_device *adev) return mmDC_GPIO_HPD_A; } -static bool dce_v10_0_is_display_hung(struct amdgpu_device *adev) -{ - u32 crtc_hung = 0; - u32 crtc_status[6]; - u32 i, j, tmp; - - for (i = 0; i < adev->mode_info.num_crtc; i++) { - tmp = RREG32(mmCRTC_CONTROL + crtc_offsets[i]); - if (REG_GET_FIELD(tmp, CRTC_CONTROL, CRTC_MASTER_EN)) { - crtc_status[i] = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]); - crtc_hung |= (1 << i); - } - } - - for (j = 0; j < 10; j++) { - for (i = 0; i < adev->mode_info.num_crtc; i++) { - if (crtc_hung & (1 << i)) { - tmp = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]); - if (tmp != crtc_status[i]) - crtc_hung &= ~(1 << i); - } - } - if (crtc_hung == 0) - return false; - udelay(100); - } - - return true; -} - static void dce_v10_0_set_vga_render_state(struct amdgpu_device *adev, bool render) { @@ -2956,40 +2926,6 @@ static bool dce_v10_0_is_idle(struct amdgpu_ip_block *ip_block) return true; } -static bool dce_v10_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - return dce_v10_0_is_display_hung(adev); -} - -static int dce_v10_0_soft_reset(struct amdgpu_ip_block *ip_block) -{ - u32 srbm_soft_reset = 0, tmp; - struct amdgpu_device *adev = ip_block->adev; - - if (dce_v10_0_is_display_hung(adev)) - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_DC_MASK; - - if (srbm_soft_reset) { - tmp = RREG32(mmSRBM_SOFT_RESET); - tmp |= srbm_soft_reset; - dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - udelay(50); - - tmp &= ~srbm_soft_reset; - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - /* Wait a little for things to settle down */ - udelay(50); - } - return 0; -} - static void dce_v10_0_set_crtc_vblank_interrupt_state(struct amdgpu_device *adev, int crtc, enum amdgpu_interrupt_state state) @@ -3332,8 +3268,6 @@ static const struct amd_ip_funcs dce_v10_0_ip_funcs = { .suspend = dce_v10_0_suspend, .resume = dce_v10_0_resume, .is_idle = dce_v10_0_is_idle, - .check_soft_reset = dce_v10_0_check_soft_reset, - .soft_reset = dce_v10_0_soft_reset, .set_clockgating_state = dce_v10_0_set_clockgating_state, .set_powergating_state = dce_v10_0_set_powergating_state, }; diff --git a/drivers/gpu/drm/amd/amdgpu/dce_v6_0.c b/drivers/gpu/drm/amd/amdgpu/dce_v6_0.c index 58d0da5c2a74..c68de0fe1d7d 100644 --- a/drivers/gpu/drm/amd/amdgpu/dce_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/dce_v6_0.c @@ -378,35 +378,6 @@ static u32 dce_v6_0_hpd_get_gpio_reg(struct amdgpu_device *adev) return mmDC_GPIO_HPD_A; } -static bool dce_v6_0_is_display_hung(struct amdgpu_device *adev) -{ - u32 crtc_hung = 0; - u32 crtc_status[6]; - u32 i, j, tmp; - - for (i = 0; i < adev->mode_info.num_crtc; i++) { - if (RREG32(mmCRTC_CONTROL + crtc_offsets[i]) & CRTC_CONTROL__CRTC_MASTER_EN_MASK) { - crtc_status[i] = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]); - crtc_hung |= (1 << i); - } - } - - for (j = 0; j < 10; j++) { - for (i = 0; i < adev->mode_info.num_crtc; i++) { - if (crtc_hung & (1 << i)) { - tmp = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]); - if (tmp != crtc_status[i]) - crtc_hung &= ~(1 << i); - } - } - if (crtc_hung == 0) - return false; - udelay(100); - } - - return true; -} - static void dce_v6_0_set_vga_render_state(struct amdgpu_device *adev, bool render) { @@ -2901,33 +2872,6 @@ static bool dce_v6_0_is_idle(struct amdgpu_ip_block *ip_block) return true; } -static int dce_v6_0_soft_reset(struct amdgpu_ip_block *ip_block) -{ - u32 srbm_soft_reset = 0, tmp; - struct amdgpu_device *adev = ip_block->adev; - - if (dce_v6_0_is_display_hung(adev)) - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_DC_MASK; - - if (srbm_soft_reset) { - tmp = RREG32(mmSRBM_SOFT_RESET); - tmp |= srbm_soft_reset; - dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - udelay(50); - - tmp &= ~srbm_soft_reset; - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - /* Wait a little for things to settle down */ - udelay(50); - } - return 0; -} - static void dce_v6_0_set_crtc_vblank_interrupt_state(struct amdgpu_device *adev, int crtc, enum amdgpu_interrupt_state state) @@ -3224,7 +3168,6 @@ static const struct amd_ip_funcs dce_v6_0_ip_funcs = { .suspend = dce_v6_0_suspend, .resume = dce_v6_0_resume, .is_idle = dce_v6_0_is_idle, - .soft_reset = dce_v6_0_soft_reset, .set_clockgating_state = dce_v6_0_set_clockgating_state, .set_powergating_state = dce_v6_0_set_powergating_state, }; diff --git a/drivers/gpu/drm/amd/amdgpu/dce_v8_0.c b/drivers/gpu/drm/amd/amdgpu/dce_v8_0.c index 6d19f6d94d25..c3906270f25e 100644 --- a/drivers/gpu/drm/amd/amdgpu/dce_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/dce_v8_0.c @@ -362,35 +362,6 @@ static u32 dce_v8_0_hpd_get_gpio_reg(struct amdgpu_device *adev) return mmDC_GPIO_HPD_A; } -static bool dce_v8_0_is_display_hung(struct amdgpu_device *adev) -{ - u32 crtc_hung = 0; - u32 crtc_status[6]; - u32 i, j, tmp; - - for (i = 0; i < adev->mode_info.num_crtc; i++) { - if (RREG32(mmCRTC_CONTROL + crtc_offsets[i]) & CRTC_CONTROL__CRTC_MASTER_EN_MASK) { - crtc_status[i] = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]); - crtc_hung |= (1 << i); - } - } - - for (j = 0; j < 10; j++) { - for (i = 0; i < adev->mode_info.num_crtc; i++) { - if (crtc_hung & (1 << i)) { - tmp = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]); - if (tmp != crtc_status[i]) - crtc_hung &= ~(1 << i); - } - } - if (crtc_hung == 0) - return false; - udelay(100); - } - - return true; -} - static void dce_v8_0_set_vga_render_state(struct amdgpu_device *adev, bool render) { @@ -2873,33 +2844,6 @@ static bool dce_v8_0_is_idle(struct amdgpu_ip_block *ip_block) return true; } -static int dce_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) -{ - u32 srbm_soft_reset = 0, tmp; - struct amdgpu_device *adev = ip_block->adev; - - if (dce_v8_0_is_display_hung(adev)) - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_DC_MASK; - - if (srbm_soft_reset) { - tmp = RREG32(mmSRBM_SOFT_RESET); - tmp |= srbm_soft_reset; - dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - udelay(50); - - tmp &= ~srbm_soft_reset; - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - /* Wait a little for things to settle down */ - udelay(50); - } - return 0; -} - static void dce_v8_0_set_crtc_vblank_interrupt_state(struct amdgpu_device *adev, int crtc, enum amdgpu_interrupt_state state) @@ -3241,7 +3185,6 @@ static const struct amd_ip_funcs dce_v8_0_ip_funcs = { .suspend = dce_v8_0_suspend, .resume = dce_v8_0_resume, .is_idle = dce_v8_0_is_idle, - .soft_reset = dce_v8_0_soft_reset, .set_clockgating_state = dce_v8_0_set_clockgating_state, .set_powergating_state = dce_v8_0_set_powergating_state, }; -- cgit v1.2.3 From 947e46eb2fb9f66da0d659c7e4f28fc18e05ada2 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:16 +0200 Subject: drm/amdgpu: Delete check_soft_reset() from amd_ip_funcs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function is not called from anywhere anymore and every implementation was bogus. Some implementations checked busy flags of the IP blocks, which are not really indicative of whether the block is hung and needs to be reset. For example the blocks could be busy just normally executing submissions, and not need to be reset. Other implementations checked IB tests, which is actually more useful, but could still just indicate that an IP block is executing submissions normally. It is also unnecessary because the GPU recovery code path already knows which ring is hung so we know exactly what we need to reset. Just delete check_soft_reset() entirely. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 25 --------- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 63 ----------------------- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 1 - drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c | 1 - drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c | 22 -------- drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 18 ------- drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c | 18 ------- drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c | 18 ------- drivers/gpu/drm/amd/amdgpu/tonga_ih.c | 20 ------- drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c | 20 ------- drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 42 --------------- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 1 - drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c | 1 - drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 --- drivers/gpu/drm/amd/include/amd_shared.h | 1 - drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 1 - 16 files changed, 258 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 6346f16c4e61..4315a6b6c1be 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -5259,30 +5259,6 @@ static int gfx_v11_0_soft_reset(struct amdgpu_ip_block *ip_block) return gfx_v11_0_cp_resume(adev); } -static bool gfx_v11_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - int i, r; - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->gfx.num_gfx_rings; i++) { - ring = &adev->gfx.gfx_ring[i]; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - ring = &adev->gfx.compute_ring[i]; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - static int gfx_v11_0_post_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -7015,7 +6991,6 @@ static const struct amd_ip_funcs gfx_v11_0_ip_funcs = { .is_idle = gfx_v11_0_is_idle, .wait_for_idle = gfx_v11_0_wait_for_idle, .soft_reset = gfx_v11_0_soft_reset, - .check_soft_reset = gfx_v11_0_check_soft_reset, .post_soft_reset = gfx_v11_0_post_soft_reset, .set_clockgating_state = gfx_v11_0_set_clockgating_state, .set_powergating_state = gfx_v11_0_set_powergating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 130196859ff3..dd1823bd89ad 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4891,68 +4891,6 @@ static int gfx_v8_0_resume(struct amdgpu_ip_block *ip_block) return gfx_v8_0_hw_init(ip_block); } -static bool gfx_v8_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 grbm_soft_reset = 0, srbm_soft_reset = 0; - u32 tmp; - - /* GRBM_STATUS */ - tmp = RREG32(mmGRBM_STATUS); - if (tmp & (GRBM_STATUS__PA_BUSY_MASK | GRBM_STATUS__SC_BUSY_MASK | - GRBM_STATUS__BCI_BUSY_MASK | GRBM_STATUS__SX_BUSY_MASK | - GRBM_STATUS__TA_BUSY_MASK | GRBM_STATUS__VGT_BUSY_MASK | - GRBM_STATUS__DB_BUSY_MASK | GRBM_STATUS__CB_BUSY_MASK | - GRBM_STATUS__GDS_BUSY_MASK | GRBM_STATUS__SPI_BUSY_MASK | - GRBM_STATUS__IA_BUSY_MASK | GRBM_STATUS__IA_BUSY_NO_DMA_MASK | - GRBM_STATUS__CP_BUSY_MASK | GRBM_STATUS__CP_COHERENCY_BUSY_MASK)) { - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, - GRBM_SOFT_RESET, SOFT_RESET_CP, 1); - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, - GRBM_SOFT_RESET, SOFT_RESET_GFX, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_GRBM, 1); - } - - /* GRBM_STATUS2 */ - tmp = RREG32(mmGRBM_STATUS2); - if (REG_GET_FIELD(tmp, GRBM_STATUS2, RLC_BUSY)) - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, - GRBM_SOFT_RESET, SOFT_RESET_RLC, 1); - - if (REG_GET_FIELD(tmp, GRBM_STATUS2, CPF_BUSY) || - REG_GET_FIELD(tmp, GRBM_STATUS2, CPC_BUSY) || - REG_GET_FIELD(tmp, GRBM_STATUS2, CPG_BUSY)) { - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, - SOFT_RESET_CPF, 1); - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, - SOFT_RESET_CPC, 1); - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, - SOFT_RESET_CPG, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, - SOFT_RESET_GRBM, 1); - } - - /* SRBM_STATUS */ - tmp = RREG32(mmSRBM_STATUS); - if (REG_GET_FIELD(tmp, SRBM_STATUS, GRBM_RQ_PENDING)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_GRBM, 1); - if (REG_GET_FIELD(tmp, SRBM_STATUS, SEM_BUSY)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_SEM, 1); - - if (grbm_soft_reset || srbm_soft_reset) { - adev->gfx.grbm_soft_reset = grbm_soft_reset; - adev->gfx.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->gfx.grbm_soft_reset = 0; - adev->gfx.srbm_soft_reset = 0; - return false; - } -} - static int gfx_v8_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -6862,7 +6800,6 @@ static const struct amd_ip_funcs gfx_v8_0_ip_funcs = { .resume = gfx_v8_0_resume, .is_idle = gfx_v8_0_is_idle, .wait_for_idle = gfx_v8_0_wait_for_idle, - .check_soft_reset = gfx_v8_0_check_soft_reset, .pre_soft_reset = gfx_v8_0_pre_soft_reset, .soft_reset = gfx_v8_0_soft_reset, .post_soft_reset = gfx_v8_0_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index e023ae958459..26a3f759ea94 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -888,7 +888,6 @@ static const struct amd_ip_funcs jpeg_v5_0_1_ip_funcs = { .resume = jpeg_v5_0_1_resume, .is_idle = jpeg_v5_0_1_is_idle, .wait_for_idle = jpeg_v5_0_1_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c index 7a4ecea6b39a..717eaf43c9a6 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c @@ -690,7 +690,6 @@ static const struct amd_ip_funcs jpeg_v5_0_2_ip_funcs = { .resume = jpeg_v5_0_2_resume, .is_idle = jpeg_v5_0_2_is_idle, .wait_for_idle = jpeg_v5_0_2_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c index 3fde9be74690..e77261a64cf8 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c @@ -1237,27 +1237,6 @@ static int sdma_v3_0_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } -static bool sdma_v3_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - u32 tmp = RREG32(mmSRBM_STATUS2); - - if ((tmp & SRBM_STATUS2__SDMA_BUSY_MASK) || - (tmp & SRBM_STATUS2__SDMA1_BUSY_MASK)) { - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_SDMA_MASK; - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_SDMA1_MASK; - } - - if (srbm_soft_reset) { - adev->sdma.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->sdma.srbm_soft_reset = 0; - return false; - } -} - static int sdma_v3_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1552,7 +1531,6 @@ static const struct amd_ip_funcs sdma_v3_0_ip_funcs = { .resume = sdma_v3_0_resume, .is_idle = sdma_v3_0_is_idle, .wait_for_idle = sdma_v3_0_wait_for_idle, - .check_soft_reset = sdma_v3_0_check_soft_reset, .pre_soft_reset = sdma_v3_0_pre_soft_reset, .post_soft_reset = sdma_v3_0_post_soft_reset, .soft_reset = sdma_v3_0_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c index d894b7599c18..c208c584f912 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c @@ -793,23 +793,6 @@ static int sdma_v6_0_soft_reset(struct amdgpu_ip_block *ip_block) return sdma_v6_0_start(adev); } -static bool sdma_v6_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - int i, r; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->sdma.num_instances; i++) { - ring = &adev->sdma.instance[i].ring; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - /** * sdma_v6_0_start - setup and start the async dma engines * @@ -1747,7 +1730,6 @@ const struct amd_ip_funcs sdma_v6_0_ip_funcs = { .is_idle = sdma_v6_0_is_idle, .wait_for_idle = sdma_v6_0_wait_for_idle, .soft_reset = sdma_v6_0_soft_reset, - .check_soft_reset = sdma_v6_0_check_soft_reset, .set_clockgating_state = sdma_v6_0_set_clockgating_state, .set_powergating_state = sdma_v6_0_set_powergating_state, .get_clockgating_state = sdma_v6_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c index f154b68dda70..9f232805cd76 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c @@ -784,23 +784,6 @@ static int sdma_v7_0_soft_reset(struct amdgpu_ip_block *ip_block) return sdma_v7_0_start(adev); } -static bool sdma_v7_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - int i, r; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->sdma.num_instances; i++) { - ring = &adev->sdma.instance[i].ring; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - static int sdma_v7_0_reset_queue(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -1679,7 +1662,6 @@ const struct amd_ip_funcs sdma_v7_0_ip_funcs = { .is_idle = sdma_v7_0_is_idle, .wait_for_idle = sdma_v7_0_wait_for_idle, .soft_reset = sdma_v7_0_soft_reset, - .check_soft_reset = sdma_v7_0_check_soft_reset, .set_clockgating_state = sdma_v7_0_set_clockgating_state, .set_powergating_state = sdma_v7_0_set_powergating_state, .get_clockgating_state = sdma_v7_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c index cd9668605a50..14186e0ddb2c 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c @@ -775,23 +775,6 @@ static int sdma_v7_1_soft_reset(struct amdgpu_ip_block *ip_block) return sdma_v7_1_inst_start(adev, inst_mask); } -static bool sdma_v7_1_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - int i, r; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->sdma.num_instances; i++) { - ring = &adev->sdma.instance[i].ring; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - static int sdma_v7_1_reset_queue(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -1644,7 +1627,6 @@ const struct amd_ip_funcs sdma_v7_1_ip_funcs = { .is_idle = sdma_v7_1_is_idle, .wait_for_idle = sdma_v7_1_wait_for_idle, .soft_reset = sdma_v7_1_soft_reset, - .check_soft_reset = sdma_v7_1_check_soft_reset, .set_clockgating_state = sdma_v7_1_set_clockgating_state, .set_powergating_state = sdma_v7_1_set_powergating_state, .get_clockgating_state = sdma_v7_1_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c index ee8038df17e3..671f5bf18a3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c @@ -390,25 +390,6 @@ static int tonga_ih_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } -static bool tonga_ih_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - u32 tmp = RREG32(mmSRBM_STATUS); - - if (tmp & SRBM_STATUS__IH_BUSY_MASK) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, - SOFT_RESET_IH, 1); - - if (srbm_soft_reset) { - adev->irq.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->irq.srbm_soft_reset = 0; - return false; - } -} - static int tonga_ih_pre_soft_reset(struct amdgpu_ip_block *ip_block) { if (!ip_block->adev->irq.srbm_soft_reset) @@ -481,7 +462,6 @@ static const struct amd_ip_funcs tonga_ih_ip_funcs = { .resume = tonga_ih_resume, .is_idle = tonga_ih_is_idle, .wait_for_idle = tonga_ih_wait_for_idle, - .check_soft_reset = tonga_ih_check_soft_reset, .pre_soft_reset = tonga_ih_pre_soft_reset, .soft_reset = tonga_ih_soft_reset, .post_soft_reset = tonga_ih_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c index ecd7ead7a60b..7a6b6277cadd 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c @@ -1165,25 +1165,6 @@ static int uvd_v6_0_wait_for_idle(struct amdgpu_ip_block *ip_block) } #define AMDGPU_UVD_STATUS_BUSY_MASK 0xfd -static bool uvd_v6_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - u32 tmp = RREG32(mmSRBM_STATUS); - - if (REG_GET_FIELD(tmp, SRBM_STATUS, UVD_RQ_PENDING) || - REG_GET_FIELD(tmp, SRBM_STATUS, UVD_BUSY) || - (RREG32(mmUVD_STATUS) & AMDGPU_UVD_STATUS_BUSY_MASK)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_UVD, 1); - - if (srbm_soft_reset) { - adev->uvd.inst->srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->uvd.inst->srbm_soft_reset = 0; - return false; - } -} static int uvd_v6_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) { @@ -1538,7 +1519,6 @@ static const struct amd_ip_funcs uvd_v6_0_ip_funcs = { .resume = uvd_v6_0_resume, .is_idle = uvd_v6_0_is_idle, .wait_for_idle = uvd_v6_0_wait_for_idle, - .check_soft_reset = uvd_v6_0_check_soft_reset, .pre_soft_reset = uvd_v6_0_pre_soft_reset, .soft_reset = uvd_v6_0_soft_reset, .post_soft_reset = uvd_v6_0_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c index c69f7d82060f..e01c4af46db1 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c @@ -631,47 +631,6 @@ static int vce_v3_0_wait_for_idle(struct amdgpu_ip_block *ip_block) #define AMDGPU_VCE_STATUS_BUSY_MASK (VCE_STATUS_VCPU_REPORT_AUTO_BUSY_MASK | \ VCE_STATUS_VCPU_REPORT_RB0_BUSY_MASK) -static bool vce_v3_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - - /* According to VCE team , we should use VCE_STATUS instead - * SRBM_STATUS.VCE_BUSY bit for busy status checking. - * GRBM_GFX_INDEX.INSTANCE_INDEX is used to specify which VCE - * instance's registers are accessed - * (0 for 1st instance, 10 for 2nd instance). - * - *VCE_STATUS - *|UENC|ACPI|AUTO ACTIVE|RB1 |RB0 |RB2 | |FW_LOADED|JOB | - *|----+----+-----------+----+----+----+----------+---------+----| - *|bit8|bit7| bit6 |bit5|bit4|bit3| bit2 | bit1 |bit0| - * - * VCE team suggest use bit 3--bit 6 for busy status check - */ - mutex_lock(&adev->grbm_idx_mutex); - WREG32(mmGRBM_GFX_INDEX, GET_VCE_INSTANCE(0)); - if (RREG32(mmVCE_STATUS) & AMDGPU_VCE_STATUS_BUSY_MASK) { - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE0, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE1, 1); - } - WREG32(mmGRBM_GFX_INDEX, GET_VCE_INSTANCE(1)); - if (RREG32(mmVCE_STATUS) & AMDGPU_VCE_STATUS_BUSY_MASK) { - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE0, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE1, 1); - } - WREG32(mmGRBM_GFX_INDEX, GET_VCE_INSTANCE(0)); - mutex_unlock(&adev->grbm_idx_mutex); - - if (srbm_soft_reset) { - adev->vce.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->vce.srbm_soft_reset = 0; - return false; - } -} - static int vce_v3_0_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -909,7 +868,6 @@ static const struct amd_ip_funcs vce_v3_0_ip_funcs = { .resume = vce_v3_0_resume, .is_idle = vce_v3_0_is_idle, .wait_for_idle = vce_v3_0_wait_for_idle, - .check_soft_reset = vce_v3_0_check_soft_reset, .pre_soft_reset = vce_v3_0_pre_soft_reset, .soft_reset = vce_v3_0_soft_reset, .post_soft_reset = vce_v3_0_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 95f55bab528a..0e1a309a3e3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1674,7 +1674,6 @@ static const struct amd_ip_funcs vcn_v5_0_1_ip_funcs = { .resume = vcn_v5_0_1_resume, .is_idle = vcn_v5_0_1_is_idle, .wait_for_idle = vcn_v5_0_1_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c index bbc172db91a1..1fb1dea3f129 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c @@ -1203,7 +1203,6 @@ static const struct amd_ip_funcs vcn_v5_0_2_ip_funcs = { .resume = vcn_v5_0_2_resume, .is_idle = vcn_v5_0_2_is_idle, .wait_for_idle = vcn_v5_0_2_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, 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 2e74ff94dcac..ec14a0f3a34b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -237,11 +237,6 @@ static int dm_wait_for_idle(struct amdgpu_ip_block *ip_block) return 0; } -static bool dm_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - return false; -} - static int dm_soft_reset(struct amdgpu_ip_block *ip_block) { /* XXX todo */ @@ -2201,7 +2196,6 @@ static const struct amd_ip_funcs amdgpu_dm_funcs = { .resume = dm_resume, .is_idle = dm_is_idle, .wait_for_idle = dm_wait_for_idle, - .check_soft_reset = dm_check_soft_reset, .soft_reset = dm_soft_reset, .set_clockgating_state = dm_set_clockgating_state, .set_powergating_state = dm_set_powergating_state, diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 3fd38323a88b..e698e4411eb0 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -471,7 +471,6 @@ struct amd_ip_funcs { void (*complete)(struct amdgpu_ip_block *ip_block); bool (*is_idle)(struct amdgpu_ip_block *ip_block); int (*wait_for_idle)(struct amdgpu_ip_block *ip_block); - bool (*check_soft_reset)(struct amdgpu_ip_block *ip_block); int (*pre_soft_reset)(struct amdgpu_ip_block *ip_block); int (*soft_reset)(struct amdgpu_ip_block *ip_block); int (*post_soft_reset)(struct amdgpu_ip_block *ip_block); diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 208a2fba6d40..5e73594efdf0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -2772,7 +2772,6 @@ const struct amd_ip_funcs smu_ip_funcs = { .suspend = smu_suspend, .resume = smu_resume, .is_idle = NULL, - .check_soft_reset = NULL, .wait_for_idle = NULL, .soft_reset = NULL, .set_clockgating_state = smu_set_clockgating_state, -- cgit v1.2.3 From 4d7c25208ca612b754f3bf39e9f16e725b828891 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:17:59 -0400 Subject: drm/amdgpu/gfx8: drop unecessary BUG_ON() There's no need to crash the kernel for this case. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index dd1823bd89ad..59728dfd8a7b 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -6194,9 +6194,6 @@ static void gfx_v8_0_ring_emit_fence_compute(struct amdgpu_ring *ring, static void gfx_v8_0_ring_emit_fence_kiq(struct amdgpu_ring *ring, u64 addr, u64 seq, unsigned int flags) { - /* we only allocate 32bit for each seq wb address */ - BUG_ON(flags & AMDGPU_FENCE_FLAG_64BIT); - /* write fence seq to the "addr" */ amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3)); amdgpu_ring_write(ring, (WRITE_DATA_ENGINE_SEL(0) | -- cgit v1.2.3 From ddb1149aa2be448d439833800b9f1c6f5ee7db5a Mon Sep 17 00:00:00 2001 From: Christian König Date: Wed, 6 May 2026 14:29:01 +0200 Subject: drm/amdgpu: move suballoc defines into own header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just some code cleanup, while at it remove outdated comment. No functional change. Signed-off-by: Christian König Acked-by: Felix Kuehling Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 32 +------------ drivers/gpu/drm/amd/amdgpu/amdgpu_object.h | 40 ---------------- drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h | 77 ++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 71 deletions(-) create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 3178e5f9b415..b68aea97c166 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -114,6 +114,7 @@ #include "amdgpu_userq.h" #include "amdgpu_eviction_fence.h" #include "amdgpu_ip.h" +#include "amdgpu_sa.h" #if defined(CONFIG_DRM_AMD_ISP) #include "amdgpu_isp.h" #endif @@ -387,37 +388,6 @@ struct amdgpu_clock { uint32_t max_pixel_clock; }; -/* sub-allocation manager, it has to be protected by another lock. - * By conception this is an helper for other part of the driver - * like the indirect buffer or semaphore, which both have their - * locking. - * - * Principe is simple, we keep a list of sub allocation in offset - * order (first entry has offset == 0, last entry has the highest - * offset). - * - * When allocating new object we first check if there is room at - * the end total_size - (last_object_offset + last_object_size) >= - * alloc_size. If so we allocate new object there. - * - * When there is not enough room at the end, we start waiting for - * each sub object until we reach object_offset+object_size >= - * alloc_size, this object then become the sub object we return. - * - * Alignment can't be bigger than page size. - * - * Hole are not considered for allocation to keep things simple. - * Assumption is that there won't be hole (all object on same - * alignment). - */ - -struct amdgpu_sa_manager { - struct drm_suballoc_manager base; - struct amdgpu_bo *bo; - uint64_t gpu_addr; - void *cpu_ptr; -}; - /* * IRQS. */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h index 4d68732d6223..ff11a0903499 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h @@ -312,46 +312,6 @@ uint32_t amdgpu_bo_mem_stats_placement(struct amdgpu_bo *bo); uint32_t amdgpu_bo_get_preferred_domain(struct amdgpu_device *adev, uint32_t domain); -/* - * sub allocation - */ -static inline struct amdgpu_sa_manager * -to_amdgpu_sa_manager(struct drm_suballoc_manager *manager) -{ - return container_of(manager, struct amdgpu_sa_manager, base); -} - -static inline uint64_t amdgpu_sa_bo_gpu_addr(struct drm_suballoc *sa_bo) -{ - return to_amdgpu_sa_manager(sa_bo->manager)->gpu_addr + - drm_suballoc_soffset(sa_bo); -} - -static inline void *amdgpu_sa_bo_cpu_addr(struct drm_suballoc *sa_bo) -{ - return to_amdgpu_sa_manager(sa_bo->manager)->cpu_ptr + - drm_suballoc_soffset(sa_bo); -} - -int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, - struct amdgpu_sa_manager *sa_manager, - unsigned size, u32 align, u32 domain); -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, - struct amdgpu_sa_manager *sa_manager); -int amdgpu_sa_bo_new(struct amdgpu_sa_manager *sa_manager, - struct drm_suballoc **sa_bo, - unsigned int size); -void amdgpu_sa_bo_free(struct drm_suballoc **sa_bo, - struct dma_fence *fence); -#if defined(CONFIG_DEBUG_FS) -void amdgpu_sa_bo_dump_debug_info(struct amdgpu_sa_manager *sa_manager, - struct seq_file *m); -u64 amdgpu_bo_print_info(int id, struct amdgpu_bo *bo, struct seq_file *m); -#endif -void amdgpu_debugfs_sa_init(struct amdgpu_device *adev); - bool amdgpu_bo_support_uswc(u64 bo_flags); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h new file mode 100644 index 000000000000..8c85c80fc119 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h @@ -0,0 +1,77 @@ +/* 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. + * + */ + +#ifndef AMDGPU_SA_H_ +#define AMDGPU_SA_H_ + +#include + +struct amdgpu_device; +struct amdgpu_bo; + +struct amdgpu_sa_manager { + struct drm_suballoc_manager base; + struct amdgpu_bo *bo; + uint64_t gpu_addr; + void *cpu_ptr; +}; + +static inline struct amdgpu_sa_manager * +to_amdgpu_sa_manager(struct drm_suballoc_manager *manager) +{ + return container_of(manager, struct amdgpu_sa_manager, base); +} + +static inline uint64_t amdgpu_sa_bo_gpu_addr(struct drm_suballoc *sa_bo) +{ + return to_amdgpu_sa_manager(sa_bo->manager)->gpu_addr + + drm_suballoc_soffset(sa_bo); +} + +static inline void *amdgpu_sa_bo_cpu_addr(struct drm_suballoc *sa_bo) +{ + return to_amdgpu_sa_manager(sa_bo->manager)->cpu_ptr + + drm_suballoc_soffset(sa_bo); +} + +int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager, + unsigned size, u32 align, u32 domain); +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, + struct amdgpu_sa_manager *sa_manager); +int amdgpu_sa_bo_new(struct amdgpu_sa_manager *sa_manager, + struct drm_suballoc **sa_bo, + unsigned int size); +void amdgpu_sa_bo_free(struct drm_suballoc **sa_bo, + struct dma_fence *fence); +#if defined(CONFIG_DEBUG_FS) +void amdgpu_sa_bo_dump_debug_info(struct amdgpu_sa_manager *sa_manager, + struct seq_file *m); +u64 amdgpu_bo_print_info(int id, struct amdgpu_bo *bo, struct seq_file *m); +#endif +void amdgpu_debugfs_sa_init(struct amdgpu_device *adev); + +#endif -- cgit v1.2.3 From 3d625815a779db6660a63e7103a2047a40844bc8 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Fri, 19 Jun 2026 14:55:18 +0530 Subject: drm/amdgpu: do not release the root bo after vm validate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure to not release the vm root bo after vm validation and to make that happen we moved the restore function within amdgpu_userq_vm_validate function. Also update the function name to reflect the intent. Suggested-by: Christian König Signed-off-by: Zhu Lingshan Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 4494a98026cb..cd168a51c165 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -886,16 +886,10 @@ int amdgpu_userq_ioctl(struct drm_device *dev, void *data, static int amdgpu_userq_restore_all(struct amdgpu_userq_mgr *uq_mgr) { - struct amdgpu_fpriv *fpriv = uq_mgr_to_fpriv(uq_mgr); - struct amdgpu_vm *vm = &fpriv->vm; struct amdgpu_usermode_queue *queue; unsigned long queue_id; int ret = 0, r; - - if (amdgpu_bo_reserve(vm->root.bo, false)) - return false; - mutex_lock(&uq_mgr->userq_mutex); /* Resume all the queues for this process */ xa_for_each(&uq_mgr->userq_xa, queue_id, queue) { @@ -911,10 +905,8 @@ amdgpu_userq_restore_all(struct amdgpu_userq_mgr *uq_mgr) r = amdgpu_userq_map_helper(queue); if (r) ret = r; - } mutex_unlock(&uq_mgr->userq_mutex); - amdgpu_bo_unreserve(vm->root.bo); if (ret) drm_file_err(uq_mgr->file, @@ -972,7 +964,7 @@ amdgpu_userq_bo_validate(struct amdgpu_device *adev, struct drm_exec *exec, /* Make sure the whole VM is ready to be used */ static int -amdgpu_userq_vm_validate(struct amdgpu_userq_mgr *uq_mgr) +amdgpu_userq_vm_validate_and_restore_queue(struct amdgpu_userq_mgr *uq_mgr) { struct amdgpu_fpriv *fpriv = uq_mgr_to_fpriv(uq_mgr); bool invalidated = false, new_addition = false; @@ -1098,8 +1090,12 @@ retry_lock: dma_fence_wait(vm->last_update, false); ret = amdgpu_evf_mgr_rearm(&fpriv->evf_mgr, &exec); - if (ret) + if (ret) { drm_file_err(uq_mgr->file, "Failed to replace eviction fence\n"); + goto unlock_all; + } + + ret = amdgpu_userq_restore_all(uq_mgr); unlock_all: drm_exec_fini(&exec); @@ -1125,14 +1121,12 @@ static void amdgpu_userq_restore_worker(struct work_struct *work) if (!dma_fence_is_signaled(ev_fence)) goto put_fence; - ret = amdgpu_userq_vm_validate(uq_mgr); + ret = amdgpu_userq_vm_validate_and_restore_queue(uq_mgr); if (ret) { drm_file_err(uq_mgr->file, "Failed to validate BOs to restore ret=%d\n", ret); goto put_fence; } - amdgpu_userq_restore_all(uq_mgr); - put_fence: dma_fence_put(ev_fence); } -- cgit v1.2.3 From b71604f8685b0eba07866f4e8dc30f93e1931054 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:14:59 -0400 Subject: drm/amdgpu/gfx9: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index f836621c46eb..9f81fd715418 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -1183,7 +1183,7 @@ static void gfx_v9_0_wait_reg_mem(struct amdgpu_ring *ring, int eng_sel, WAIT_REG_MEM_ENGINE(eng_sel))); if (mem_space) - BUG_ON(addr0 & 0x3); /* Dword align */ + WARN_ON(addr0 & 0x3); /* Dword align */ amdgpu_ring_write(ring, addr0); amdgpu_ring_write(ring, addr1); amdgpu_ring_write(ring, ref); @@ -5476,7 +5476,7 @@ static void gfx_v9_0_ring_emit_ib_gfx(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, header); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -5572,7 +5572,7 @@ static void gfx_v9_0_ring_emit_ib_compute(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2)); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -5613,9 +5613,9 @@ static void gfx_v9_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, * aligned if only send 32bit data low (discard data high) */ if (write64bit) - BUG_ON(addr & 0x7); + WARN_ON(addr & 0x7); else - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); -- cgit v1.2.3 From 5676593d08998d7a6d9e2d51d6b54b3820e3755c Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:42:35 -0400 Subject: drm/amdgpu/gfx9.4.3: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 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 e50a66e9ee96..5f5577f52a98 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -405,7 +405,7 @@ static void gfx_v9_4_3_wait_reg_mem(struct amdgpu_ring *ring, int eng_sel, WAIT_REG_MEM_ENGINE(eng_sel))); if (mem_space) - BUG_ON(addr0 & 0x3); /* Dword align */ + WARN_ON(addr0 & 0x3); /* Dword align */ amdgpu_ring_write(ring, addr0); amdgpu_ring_write(ring, addr1); amdgpu_ring_write(ring, ref); @@ -3029,7 +3029,7 @@ static void gfx_v9_4_3_ring_emit_ib_compute(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2)); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -3063,9 +3063,9 @@ static void gfx_v9_4_3_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, * aligned if only send 32bit data low (discard data high) */ if (write64bit) - BUG_ON(addr & 0x7); + WARN_ON(addr & 0x7); else - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -3125,9 +3125,6 @@ static void gfx_v9_4_3_ring_emit_fence_kiq(struct amdgpu_ring *ring, u64 addr, { struct amdgpu_device *adev = ring->adev; - /* we only allocate 32bit for each seq wb address */ - BUG_ON(flags & AMDGPU_FENCE_FLAG_64BIT); - /* write fence seq to the "addr" */ amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3)); amdgpu_ring_write(ring, (WRITE_DATA_ENGINE_SEL(0) | -- cgit v1.2.3 From ac6f00beb658239bced4aaed9efbb04a35348d48 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:19:52 -0400 Subject: drm/amdgpu/gfx10: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 76d4c33a6e65..ddf190672530 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4022,7 +4022,7 @@ static void gfx_v10_0_wait_reg_mem(struct amdgpu_ring *ring, int eng_sel, WAIT_REG_MEM_ENGINE(eng_sel))); if (mem_space) - BUG_ON(addr0 & 0x3); /* Dword align */ + WARN_ON(addr0 & 0x3); /* Dword align */ amdgpu_ring_write(ring, addr0); amdgpu_ring_write(ring, addr1); amdgpu_ring_write(ring, ref); @@ -8660,7 +8660,7 @@ static void gfx_v10_0_ring_emit_ib_gfx(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, header); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -8695,7 +8695,7 @@ static void gfx_v10_0_ring_emit_ib_compute(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2)); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -8728,9 +8728,9 @@ static void gfx_v10_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, * aligned if only send 32bit data low (discard data high) */ if (write64bit) - BUG_ON(addr & 0x7); + WARN_ON(addr & 0x7); else - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -8778,9 +8778,6 @@ static void gfx_v10_0_ring_emit_fence_kiq(struct amdgpu_ring *ring, u64 addr, { struct amdgpu_device *adev = ring->adev; - /* we only allocate 32bit for each seq wb address */ - BUG_ON(flags & AMDGPU_FENCE_FLAG_64BIT); - /* write fence seq to the "addr" */ amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3)); amdgpu_ring_write(ring, (WRITE_DATA_ENGINE_SEL(0) | -- cgit v1.2.3 From daa62107452d2451787c4248ca38fa2d1a0cbefd Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:20:55 -0400 Subject: drm/amdgpu/gfx11: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 13 +++++-------- 1 file changed, 5 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 4315a6b6c1be..d9bc929c1c3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -546,7 +546,7 @@ static void gfx_v11_0_wait_reg_mem(struct amdgpu_ring *ring, int eng_sel, WAIT_REG_MEM_ENGINE(eng_sel))); if (mem_space) - BUG_ON(addr0 & 0x3); /* Dword align */ + WARN_ON(addr0 & 0x3); /* Dword align */ amdgpu_ring_write(ring, addr0); amdgpu_ring_write(ring, addr1); amdgpu_ring_write(ring, ref); @@ -5980,7 +5980,7 @@ static void gfx_v11_0_ring_emit_ib_gfx(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, header); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -6015,7 +6015,7 @@ static void gfx_v11_0_ring_emit_ib_compute(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2)); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -6048,9 +6048,9 @@ static void gfx_v11_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, * aligned if only send 32bit data low (discard data high) */ if (write64bit) - BUG_ON(addr & 0x7); + WARN_ON(addr & 0x7); else - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -6104,9 +6104,6 @@ static void gfx_v11_0_ring_emit_fence_kiq(struct amdgpu_ring *ring, u64 addr, { struct amdgpu_device *adev = ring->adev; - /* we only allocate 32bit for each seq wb address */ - BUG_ON(flags & AMDGPU_FENCE_FLAG_64BIT); - /* write fence seq to the "addr" */ amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3)); amdgpu_ring_write(ring, (WRITE_DATA_ENGINE_SEL(0) | -- cgit v1.2.3 From f952076f76d62f783e8ba4995a7c400d39354ccf Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:21:58 -0400 Subject: drm/amdgpu/gfx12: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index f8280cc81a66..daecc4a5d90d 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -440,7 +440,7 @@ static void gfx_v12_0_wait_reg_mem(struct amdgpu_ring *ring, int eng_sel, WAIT_REG_MEM_ENGINE(eng_sel))); if (mem_space) - BUG_ON(addr0 & 0x3); /* Dword align */ + WARN_ON(addr0 & 0x3); /* Dword align */ amdgpu_ring_write(ring, addr0); amdgpu_ring_write(ring, addr1); amdgpu_ring_write(ring, ref); @@ -4500,7 +4500,7 @@ static void gfx_v12_0_ring_emit_ib_gfx(struct amdgpu_ring *ring, control |= ib->length_dw | (vmid << 24); amdgpu_ring_write(ring, header); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -4519,7 +4519,7 @@ static void gfx_v12_0_ring_emit_ib_compute(struct amdgpu_ring *ring, u32 control = INDIRECT_BUFFER_VALID | ib->length_dw | (vmid << 24); amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2)); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -4550,9 +4550,9 @@ static void gfx_v12_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, * aligned if only send 32bit data low (discard data high) */ if (write64bit) - BUG_ON(addr & 0x7); + WARN_ON(addr & 0x7); else - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -4600,9 +4600,6 @@ static void gfx_v12_0_ring_emit_fence_kiq(struct amdgpu_ring *ring, u64 addr, { struct amdgpu_device *adev = ring->adev; - /* we only allocate 32bit for each seq wb address */ - BUG_ON(flags & AMDGPU_FENCE_FLAG_64BIT); - /* write fence seq to the "addr" */ amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3)); amdgpu_ring_write(ring, (WRITE_DATA_ENGINE_SEL(0) | -- cgit v1.2.3 From e4d99e04b2e9b13b97d3b17804c735f62689db23 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:22:53 -0400 Subject: drm/amdgpu/gfx12.1: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index 30a38190f98a..aaa8f4212a15 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -248,7 +248,7 @@ static void gfx_v12_1_wait_reg_mem(struct amdgpu_ring *ring, int eng_sel, PACKET3_WAIT_REG_MEM__FUNCTION(3))); /* equal */ if (mem_space) - BUG_ON(addr0 & 0x3); /* Dword align */ + WARN_ON(addr0 & 0x3); /* Dword align */ amdgpu_ring_write(ring, addr0); amdgpu_ring_write(ring, addr1); amdgpu_ring_write(ring, ref); @@ -3437,7 +3437,7 @@ static void gfx_v12_1_ring_emit_ib_compute(struct amdgpu_ring *ring, } amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2)); - BUG_ON(ib->gpu_addr & 0x3); /* Dword align */ + WARN_ON(ib->gpu_addr & 0x3); /* Dword align */ amdgpu_ring_write(ring, #ifdef __BIG_ENDIAN (2 << 0) | @@ -3470,9 +3470,9 @@ static void gfx_v12_1_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, * aligned if only send 32bit data low (discard data high) */ if (write64bit) - BUG_ON(addr & 0x7); + WARN_ON(addr & 0x7); else - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -3519,9 +3519,6 @@ static void gfx_v12_1_ring_emit_fence_kiq(struct amdgpu_ring *ring, u64 addr, { struct amdgpu_device *adev = ring->adev; - /* we only allocate 32bit for each seq wb address */ - BUG_ON(flags & AMDGPU_FENCE_FLAG_64BIT); - /* write fence seq to the "addr" */ amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3)); amdgpu_ring_write(ring, (PACKET3_WRITE_DATA__DST_SEL(5) | PACKET3_WRITE_DATA__WR_CONFIRM(1))); -- cgit v1.2.3 From fa4f86a148271e325e95287630a3a15a9cd35fdc Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:44:11 -0400 Subject: drm/amdgpu/sdma4.4.2: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c index 777a70852883..a7685b516f19 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c @@ -457,7 +457,7 @@ static void sdma_v4_4_2_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 /* write the fence */ amdgpu_ring_write(ring, SDMA_PKT_HEADER_OP(SDMA_OP_FENCE)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -467,7 +467,7 @@ static void sdma_v4_4_2_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 addr += 4; amdgpu_ring_write(ring, SDMA_PKT_HEADER_OP(SDMA_OP_FENCE)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(seq)); -- cgit v1.2.3 From 8d144a0eb09537055841af48c9e7c2d4cd48e84d Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:26:28 -0400 Subject: drm/amdgpu/sdma5.0: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c index fa02907217e0..b809942b1eb7 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c @@ -527,7 +527,7 @@ static void sdma_v5_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* Ucached(UC) */ /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -538,7 +538,7 @@ static void sdma_v5_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(seq)); -- cgit v1.2.3 From ae658afc7f47f6147371ec42cc6b1a793dfdb5af Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:27:15 -0400 Subject: drm/amdgpu/sdma5.2: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c index f6ecbc524c9b..87c1e29fd298 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c @@ -377,7 +377,7 @@ static void sdma_v5_2_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* Ucached(UC) */ /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -388,7 +388,7 @@ static void sdma_v5_2_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(seq)); -- cgit v1.2.3 From c17a508a7d652da3728f8bbc481bfffe96d65a87 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:27:54 -0400 Subject: drm/amdgpu/sdma6.0: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c index c208c584f912..7a3f1a60b014 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c @@ -361,7 +361,7 @@ static void sdma_v6_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_COPY_LINEAR_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* Ucached(UC) */ /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -372,7 +372,7 @@ static void sdma_v6_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_COPY_LINEAR_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(seq)); -- cgit v1.2.3 From 9723a8bed3aa251a26bee4583bac9d8fb064dd44 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:28:29 -0400 Subject: drm/amdgpu/sdma7.0: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c index 9f232805cd76..84305b6800fe 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c @@ -363,7 +363,7 @@ static void sdma_v7_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_COPY_LINEAR_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* Ucached(UC) */ /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -374,7 +374,7 @@ static void sdma_v7_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_COPY_LINEAR_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(seq)); -- cgit v1.2.3 From c4f230b51cf2d3e7e8b1c800331f3dbed2a9e3f5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 18:29:00 -0400 Subject: drm/amdgpu/sdma7.1: replace BUG_ON() with WARN_ON() There's no need to crash the kernel for these cases. Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c index 14186e0ddb2c..322e6f4dd121 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c @@ -331,7 +331,7 @@ static void sdma_v7_1_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_COPY_LINEAR_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* Ucached(UC) */ /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, lower_32_bits(seq)); @@ -342,7 +342,7 @@ static void sdma_v7_1_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 se amdgpu_ring_write(ring, SDMA_PKT_COPY_LINEAR_HEADER_OP(SDMA_OP_FENCE) | SDMA_PKT_FENCE_HEADER_MTYPE(0x3)); /* zero in first two bits */ - BUG_ON(addr & 0x3); + WARN_ON(addr & 0x3); amdgpu_ring_write(ring, lower_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(addr)); amdgpu_ring_write(ring, upper_32_bits(seq)); -- cgit v1.2.3 From 6a5786e191fdce36c5db170e5209cf609e8f0087 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Fri, 12 Jun 2026 10:55:09 +0800 Subject: drm/amd/pm: make pp_features read-only when scpm is enabled SCPM owns power feature control when enabled. Make pp_features read-only during sysfs setup by clearing its write bits and store callback. Signed-off-by: Yang Wang Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index 538b6736e9f8..876793ed150d 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -2703,6 +2703,11 @@ static int default_attr_update(struct amdgpu_device *adev, struct amdgpu_device_ gc_ver != IP_VERSION(9, 4, 3)) || gc_ver < IP_VERSION(9, 0, 0)) *states = ATTR_STATE_UNSUPPORTED; + + if (adev->scpm_enabled) { + dev_attr->attr.mode &= ~S_IWUGO; + dev_attr->store = NULL; + } } else if (DEVICE_ATTR_IS(gpu_metrics)) { if (gc_ver < IP_VERSION(9, 1, 0)) *states = ATTR_STATE_UNSUPPORTED; -- cgit v1.2.3 From 01992b121fb652c753d37e0c1427a2d1a557d2b1 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Thu, 18 Jun 2026 12:54:14 +0800 Subject: drm/amd/pm: fix amdgpu_pm_info power display units amdgpu_pm_info displayed power sensor readings with the wrong fractional unit. It treated the low byte of the raw sensor value as the decimal part of watts, while that field represents milliwatts in the decoded value. As a result, debugfs could report misleading SoC power when the remainder was not already a two-digit centiwatt value. Example with query = 0x00000354: raw field value --------------------- query >> 8 3 W query & 0xff 84 mW decoded power 3084 mW output value --------------------- before 3.84 W after 3.08 W Fixes: f0b8f65b4825 ("drm/amd/amdgpu: fix the GPU power print error in pm info") Signed-off-by: Yang Wang Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index 876793ed150d..f5a5d72b4108 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -41,6 +41,8 @@ #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; @@ -3361,7 +3363,6 @@ static int amdgpu_hwmon_get_power(struct device *dev, enum amd_pp_sensors sensor) { struct amdgpu_device *adev = dev_get_drvdata(dev); - unsigned int uw; u32 query = 0; int r; @@ -3370,9 +3371,7 @@ static int amdgpu_hwmon_get_power(struct device *dev, return r; /* convert to microwatts */ - uw = (query >> 8) * 1000000 + (query & 0xff) * 1000; - - return uw; + return power_2_mwatt(query) * 1000; } static ssize_t amdgpu_hwmon_show_power_avg(struct device *dev, @@ -4888,7 +4887,7 @@ static int amdgpu_debugfs_pm_info_pp(struct seq_file *m, struct amdgpu_device *a { uint32_t mp1_ver = amdgpu_ip_version(adev, MP1_HWIP, 0); uint32_t gc_ver = amdgpu_ip_version(adev, GC_HWIP, 0); - uint32_t value; + uint32_t value, mwatt, centiwatt; uint64_t value64 = 0; uint32_t query = 0; int size; @@ -4913,17 +4912,21 @@ 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); + centiwatt = DIV_ROUND_CLOSEST(mwatt, 10); if (adev->flags & AMD_IS_APU) - seq_printf(m, "\t%u.%02u W (average SoC including CPU)\n", query >> 8, query & 0xff); + seq_printf(m, "\t%u.%02u W (average SoC including CPU)\n", centiwatt / 100, centiwatt % 100); else - seq_printf(m, "\t%u.%02u W (average SoC)\n", query >> 8, query & 0xff); + seq_printf(m, "\t%u.%02u W (average SoC)\n", centiwatt / 100, centiwatt % 100); } size = sizeof(uint32_t); if (!amdgpu_dpm_read_sensor(adev, AMDGPU_PP_SENSOR_GPU_INPUT_POWER, (void *)&query, &size)) { + mwatt = power_2_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", query >> 8, query & 0xff); + seq_printf(m, "\t%u.%02u W (current SoC including CPU)\n", centiwatt / 100, centiwatt % 100); else - seq_printf(m, "\t%u.%02u W (current SoC)\n", query >> 8, query & 0xff); + seq_printf(m, "\t%u.%02u W (current SoC)\n", centiwatt / 100, centiwatt % 100); } size = sizeof(value); seq_printf(m, "\n"); -- cgit v1.2.3 From b7500532e12b32d9ac54a4faacebb12baca091a4 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:17 +0200 Subject: drm/amdgpu: Delete pre/post_soft_reset() from amd_ip_funcs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions were largely redundant with the respective suspend() and resume() functions, the main difference being that they were less used and therefore less likely to be tested and correct. Move anything relevant from pre/post_soft_reset() that is not already done by suspend()/resume() into the soft_reset() functions. Note that future uses of soft_reset() will need to call the suspend() / resume() functions and the necessary clock and power gating functions. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 11 ++-- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 95 ++++---------------------------- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 2 - drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c | 2 - drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c | 40 -------------- drivers/gpu/drm/amd/amdgpu/tonga_ih.c | 20 ------- drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c | 25 --------- drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 27 --------- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 2 - drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c | 2 - drivers/gpu/drm/amd/include/amd_shared.h | 2 - 11 files changed, 15 insertions(+), 213 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index d9bc929c1c3a..4cd6e8bfd4c9 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -5256,14 +5256,12 @@ static int gfx_v11_0_soft_reset(struct amdgpu_ip_block *ip_block) amdgpu_gfx_rlc_exit_safe_mode(adev, 0); - return gfx_v11_0_cp_resume(adev); -} + r = gfx_v11_0_cp_resume(adev); + if (r) + return r; -static int gfx_v11_0_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; /** - * GFX soft reset will impact MES, need resume MES when do GFX soft reset + * GFX soft reset impacts MES, resume MES after GFX soft reset is finished */ return amdgpu_mes_resume(adev, 0); } @@ -6988,7 +6986,6 @@ static const struct amd_ip_funcs gfx_v11_0_ip_funcs = { .is_idle = gfx_v11_0_is_idle, .wait_for_idle = gfx_v11_0_wait_for_idle, .soft_reset = gfx_v11_0_soft_reset, - .post_soft_reset = gfx_v11_0_post_soft_reset, .set_clockgating_state = gfx_v11_0_set_clockgating_state, .set_powergating_state = gfx_v11_0_set_powergating_state, .get_clockgating_state = gfx_v11_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 59728dfd8a7b..8ae9ab0fb886 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4891,52 +4891,12 @@ static int gfx_v8_0_resume(struct amdgpu_ip_block *ip_block) return gfx_v8_0_hw_init(ip_block); } -static int gfx_v8_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 grbm_soft_reset = 0; - - if ((!adev->gfx.grbm_soft_reset) && - (!adev->gfx.srbm_soft_reset)) - return 0; - - grbm_soft_reset = adev->gfx.grbm_soft_reset; - - /* stop the rlc */ - adev->gfx.rlc.funcs->stop(adev); - - if (REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CP) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_GFX)) - /* Disable GFX parsing/prefetching */ - gfx_v8_0_cp_gfx_enable(adev, false); - - if (REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CP) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CPF) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CPC) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CPG)) { - int i; - - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; - - mutex_lock(&adev->srbm_mutex); - vi_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - gfx_v8_0_deactivate_hqd(adev, 2); - vi_srbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - } - /* Disable MEC parsing/prefetching */ - gfx_v8_0_cp_compute_enable(adev, false); - } - - return 0; -} - static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; u32 grbm_soft_reset = 0, srbm_soft_reset = 0; u32 tmp; + int i; if ((!adev->gfx.grbm_soft_reset) && (!adev->gfx.srbm_soft_reset)) @@ -4945,6 +4905,16 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) grbm_soft_reset = adev->gfx.grbm_soft_reset; srbm_soft_reset = adev->gfx.srbm_soft_reset; + for (i = 0; i < adev->gfx.num_compute_rings; i++) { + struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; + + mutex_lock(&adev->srbm_mutex); + vi_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); + gfx_v8_0_deactivate_hqd(adev, 2); + vi_srbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + } + if (grbm_soft_reset || srbm_soft_reset) { tmp = RREG32(mmGMCON_DEBUG); tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_STALL, 1); @@ -4994,47 +4964,6 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static int gfx_v8_0_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 grbm_soft_reset = 0; - - if ((!adev->gfx.grbm_soft_reset) && - (!adev->gfx.srbm_soft_reset)) - return 0; - - grbm_soft_reset = adev->gfx.grbm_soft_reset; - - if (REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CP) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CPF) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CPC) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CPG)) { - int i; - - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; - - mutex_lock(&adev->srbm_mutex); - vi_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - gfx_v8_0_deactivate_hqd(adev, 2); - vi_srbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - } - gfx_v8_0_kiq_resume(adev); - gfx_v8_0_kcq_resume(adev); - } - - if (REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_CP) || - REG_GET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, SOFT_RESET_GFX)) - gfx_v8_0_cp_gfx_resume(adev); - - gfx_v8_0_cp_test_all_rings(adev); - - adev->gfx.rlc.funcs->start(adev); - - return 0; -} - /** * gfx_v8_0_get_gpu_clock_counter - return GPU clock counter snapshot * @@ -6797,9 +6726,7 @@ static const struct amd_ip_funcs gfx_v8_0_ip_funcs = { .resume = gfx_v8_0_resume, .is_idle = gfx_v8_0_is_idle, .wait_for_idle = gfx_v8_0_wait_for_idle, - .pre_soft_reset = gfx_v8_0_pre_soft_reset, .soft_reset = gfx_v8_0_soft_reset, - .post_soft_reset = gfx_v8_0_post_soft_reset, .set_clockgating_state = gfx_v8_0_set_clockgating_state, .set_powergating_state = gfx_v8_0_set_powergating_state, .get_clockgating_state = gfx_v8_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index 26a3f759ea94..a562369d2d81 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -888,9 +888,7 @@ static const struct amd_ip_funcs jpeg_v5_0_1_ip_funcs = { .resume = jpeg_v5_0_1_resume, .is_idle = jpeg_v5_0_1_is_idle, .wait_for_idle = jpeg_v5_0_1_wait_for_idle, - .pre_soft_reset = NULL, .soft_reset = NULL, - .post_soft_reset = NULL, .set_clockgating_state = jpeg_v5_0_1_set_clockgating_state, .set_powergating_state = jpeg_v5_0_1_set_powergating_state, .dump_ip_state = amdgpu_jpeg_dump_ip_state, diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c index 717eaf43c9a6..ff02f72352a8 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c @@ -690,9 +690,7 @@ static const struct amd_ip_funcs jpeg_v5_0_2_ip_funcs = { .resume = jpeg_v5_0_2_resume, .is_idle = jpeg_v5_0_2_is_idle, .wait_for_idle = jpeg_v5_0_2_wait_for_idle, - .pre_soft_reset = NULL, .soft_reset = NULL, - .post_soft_reset = NULL, .set_clockgating_state = jpeg_v5_0_2_set_clockgating_state, .set_powergating_state = jpeg_v5_0_2_set_powergating_state, .dump_ip_state = amdgpu_jpeg_dump_ip_state, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c index e77261a64cf8..c2d098cd72ce 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c @@ -1237,44 +1237,6 @@ static int sdma_v3_0_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } -static int sdma_v3_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - - if (!adev->sdma.srbm_soft_reset) - return 0; - - srbm_soft_reset = adev->sdma.srbm_soft_reset; - - if (REG_GET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_SDMA) || - REG_GET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_SDMA1)) { - sdma_v3_0_ctx_switch_enable(adev, false); - sdma_v3_0_enable(adev, false); - } - - return 0; -} - -static int sdma_v3_0_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - - if (!adev->sdma.srbm_soft_reset) - return 0; - - srbm_soft_reset = adev->sdma.srbm_soft_reset; - - if (REG_GET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_SDMA) || - REG_GET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_SDMA1)) { - sdma_v3_0_gfx_resume(adev); - sdma_v3_0_rlc_resume(adev); - } - - return 0; -} - static int sdma_v3_0_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1531,8 +1493,6 @@ static const struct amd_ip_funcs sdma_v3_0_ip_funcs = { .resume = sdma_v3_0_resume, .is_idle = sdma_v3_0_is_idle, .wait_for_idle = sdma_v3_0_wait_for_idle, - .pre_soft_reset = sdma_v3_0_pre_soft_reset, - .post_soft_reset = sdma_v3_0_post_soft_reset, .soft_reset = sdma_v3_0_soft_reset, .set_clockgating_state = sdma_v3_0_set_clockgating_state, .set_powergating_state = sdma_v3_0_set_powergating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c index 671f5bf18a3a..a3e883f6f099 100644 --- a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c @@ -390,24 +390,6 @@ static int tonga_ih_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } -static int tonga_ih_pre_soft_reset(struct amdgpu_ip_block *ip_block) -{ - if (!ip_block->adev->irq.srbm_soft_reset) - return 0; - - return tonga_ih_hw_fini(ip_block); -} - -static int tonga_ih_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->irq.srbm_soft_reset) - return 0; - - return tonga_ih_hw_init(ip_block); -} - static int tonga_ih_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -462,9 +444,7 @@ static const struct amd_ip_funcs tonga_ih_ip_funcs = { .resume = tonga_ih_resume, .is_idle = tonga_ih_is_idle, .wait_for_idle = tonga_ih_wait_for_idle, - .pre_soft_reset = tonga_ih_pre_soft_reset, .soft_reset = tonga_ih_soft_reset, - .post_soft_reset = tonga_ih_post_soft_reset, .set_clockgating_state = tonga_ih_set_clockgating_state, .set_powergating_state = tonga_ih_set_powergating_state, }; diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c index 7a6b6277cadd..8bb9592b0981 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c @@ -1166,17 +1166,6 @@ static int uvd_v6_0_wait_for_idle(struct amdgpu_ip_block *ip_block) #define AMDGPU_UVD_STATUS_BUSY_MASK 0xfd -static int uvd_v6_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->uvd.inst->srbm_soft_reset) - return 0; - - uvd_v6_0_stop(adev); - return 0; -} - static int uvd_v6_0_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1208,18 +1197,6 @@ static int uvd_v6_0_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static int uvd_v6_0_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->uvd.inst->srbm_soft_reset) - return 0; - - mdelay(5); - - return uvd_v6_0_start(adev); -} - static int uvd_v6_0_set_interrupt_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned type, @@ -1519,9 +1496,7 @@ static const struct amd_ip_funcs uvd_v6_0_ip_funcs = { .resume = uvd_v6_0_resume, .is_idle = uvd_v6_0_is_idle, .wait_for_idle = uvd_v6_0_wait_for_idle, - .pre_soft_reset = uvd_v6_0_pre_soft_reset, .soft_reset = uvd_v6_0_soft_reset, - .post_soft_reset = uvd_v6_0_post_soft_reset, .set_clockgating_state = uvd_v6_0_set_clockgating_state, .set_powergating_state = uvd_v6_0_set_powergating_state, .get_clockgating_state = uvd_v6_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c index e01c4af46db1..9f4e88440c0a 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c @@ -662,31 +662,6 @@ static int vce_v3_0_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static int vce_v3_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->vce.srbm_soft_reset) - return 0; - - mdelay(5); - - return vce_v3_0_suspend(ip_block); -} - - -static int vce_v3_0_post_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - - if (!adev->vce.srbm_soft_reset) - return 0; - - mdelay(5); - - return vce_v3_0_resume(ip_block); -} - static int vce_v3_0_set_interrupt_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned type, @@ -868,9 +843,7 @@ static const struct amd_ip_funcs vce_v3_0_ip_funcs = { .resume = vce_v3_0_resume, .is_idle = vce_v3_0_is_idle, .wait_for_idle = vce_v3_0_wait_for_idle, - .pre_soft_reset = vce_v3_0_pre_soft_reset, .soft_reset = vce_v3_0_soft_reset, - .post_soft_reset = vce_v3_0_post_soft_reset, .set_clockgating_state = vce_v3_0_set_clockgating_state, .set_powergating_state = vce_v3_0_set_powergating_state, .get_clockgating_state = vce_v3_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 0e1a309a3e3a..9c23055cf5ce 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1674,9 +1674,7 @@ static const struct amd_ip_funcs vcn_v5_0_1_ip_funcs = { .resume = vcn_v5_0_1_resume, .is_idle = vcn_v5_0_1_is_idle, .wait_for_idle = vcn_v5_0_1_wait_for_idle, - .pre_soft_reset = NULL, .soft_reset = NULL, - .post_soft_reset = NULL, .set_clockgating_state = vcn_v5_0_1_set_clockgating_state, .set_powergating_state = vcn_set_powergating_state, .dump_ip_state = amdgpu_vcn_dump_ip_state, diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c index 1fb1dea3f129..b9f6ae75ea72 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c @@ -1203,9 +1203,7 @@ static const struct amd_ip_funcs vcn_v5_0_2_ip_funcs = { .resume = vcn_v5_0_2_resume, .is_idle = vcn_v5_0_2_is_idle, .wait_for_idle = vcn_v5_0_2_wait_for_idle, - .pre_soft_reset = NULL, .soft_reset = NULL, - .post_soft_reset = NULL, .set_clockgating_state = vcn_v5_0_2_set_clockgating_state, .set_powergating_state = vcn_set_powergating_state, }; diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index e698e4411eb0..10396018afb3 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -471,9 +471,7 @@ struct amd_ip_funcs { void (*complete)(struct amdgpu_ip_block *ip_block); bool (*is_idle)(struct amdgpu_ip_block *ip_block); int (*wait_for_idle)(struct amdgpu_ip_block *ip_block); - int (*pre_soft_reset)(struct amdgpu_ip_block *ip_block); int (*soft_reset)(struct amdgpu_ip_block *ip_block); - int (*post_soft_reset)(struct amdgpu_ip_block *ip_block); int (*set_clockgating_state)(struct amdgpu_ip_block *ip_block, enum amd_clockgating_state state); int (*set_powergating_state)(struct amdgpu_ip_block *ip_block, -- cgit v1.2.3 From 1fc76380c6ce5440e3cd02ddec76067f83969dc0 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:22 +0200 Subject: drm/amdgpu: Add IP block soft reset as a GPU recovery method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement IP block soft reset as a recovery method that fits into the current GPU recovery code as opposed to being hacked into the full GPU reset code path. This can gracefully handle GPU hangs when other reset methods are not available or have failed. It makes sure to minimize collateral damage (ie. affected non-guilty jobs) and does a backup and restore on all affected queues. Note that some of the new helpers may be useful for other reset types as well, which we can explore later. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 2 + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 6 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c | 154 ++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h | 5 + drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 11 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 171 +++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 5 + 7 files changed, 354 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index b68aea97c166..4c3e933ff6d5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -309,6 +309,7 @@ extern uint amdgpu_hdmi_hpd_debounce_delay_ms; #define AMDGPU_RESET_TYPE_SOFT_RECOVERY (1 << 1) /* soft recovery, eg. kill shaders */ #define AMDGPU_RESET_TYPE_PER_QUEUE (1 << 2) /* per queue */ #define AMDGPU_RESET_TYPE_PER_PIPE (1 << 3) /* per pipe */ +#define AMDGPU_RESET_TYPE_IP_BLOCK_SOFT_RESET (1 << 4) /* soft-resets an IP block */ /* max cursor sizes (in pixels) */ #define CIK_CURSOR_WIDTH 128 @@ -1104,6 +1105,7 @@ struct amdgpu_device { bool debug_disable_ce_logs; bool debug_enable_ce_cs; bool debug_hibernation_thaw_resume_gpu; + bool debug_disable_ip_block_soft_reset; /* Protection for the following isolation structure */ struct mutex enforce_isolation_mutex; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 157c0f260cc0..f5e8e4f455ee 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -147,6 +147,7 @@ enum AMDGPU_DEBUG_MASK { AMDGPU_DEBUG_DISABLE_RAS_CE_LOG = BIT(9), AMDGPU_DEBUG_ENABLE_CE_CS = BIT(10), AMDGPU_DEBUG_HIBERNATION_THAW_RESUME_GPU = BIT(11), + AMDGPU_DEBUG_DISABLE_IP_BLOCK_SOFT_RESET = BIT(12), }; unsigned int amdgpu_vram_limit = UINT_MAX; @@ -2296,6 +2297,11 @@ static void amdgpu_init_debug_options(struct amdgpu_device *adev) pr_info("debug: resume gpu in thaw() of hibernation\n"); adev->debug_hibernation_thaw_resume_gpu = true; } + + if (amdgpu_debug_mask & AMDGPU_DEBUG_DISABLE_IP_BLOCK_SOFT_RESET) { + pr_info("debug: IP block soft reset disabled\n"); + adev->debug_disable_ip_block_soft_reset = true; + } } static unsigned long amdgpu_fix_asic_type(struct pci_dev *pdev, unsigned long flags) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c index 6aa54156bbc9..65505bc50399 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c @@ -409,3 +409,157 @@ bool amdgpu_device_ip_is_valid(struct amdgpu_device *adev, return false; } + +/** + * amdgpu_ip_from_ring() - Find IP block type corresponding to ring type. + * + * @ring_type: The ring type whose IP block you are looking for. + */ +static enum amd_ip_block_type amdgpu_ip_from_ring(const enum amdgpu_ring_type ring_type) +{ + switch (ring_type) { + case AMDGPU_RING_TYPE_GFX: + case AMDGPU_RING_TYPE_COMPUTE: + return AMD_IP_BLOCK_TYPE_GFX; + + case AMDGPU_RING_TYPE_SDMA: + return AMD_IP_BLOCK_TYPE_SDMA; + + case AMDGPU_RING_TYPE_UVD: + case AMDGPU_RING_TYPE_UVD_ENC: + return AMD_IP_BLOCK_TYPE_UVD; + + case AMDGPU_RING_TYPE_VCE: + return AMD_IP_BLOCK_TYPE_VCE; + + case AMDGPU_RING_TYPE_VCN_DEC: + case AMDGPU_RING_TYPE_VCN_ENC: + return AMD_IP_BLOCK_TYPE_VCN; + + case AMDGPU_RING_TYPE_VCN_JPEG: + return AMD_IP_BLOCK_TYPE_JPEG; + + case AMDGPU_RING_TYPE_VPE: + return AMD_IP_BLOCK_TYPE_VPE; + + default: + return AMD_IP_BLOCK_TYPE_NUM; + } +} + +/** + * amdgpu_ring_mask_from_ip() - Find mask of ring types corresponding to an IP block type. + * + * @ip_type: The IP block type whose rings you are looking for. + */ +static u32 amdgpu_ring_mask_from_ip(const enum amd_ip_block_type ip_type) +{ + switch (ip_type) { + case AMD_IP_BLOCK_TYPE_GFX: + return BIT(AMDGPU_RING_TYPE_GFX) | BIT(AMDGPU_RING_TYPE_COMPUTE); + + case AMD_IP_BLOCK_TYPE_SDMA: + return BIT(AMDGPU_RING_TYPE_SDMA); + + case AMD_IP_BLOCK_TYPE_UVD: + return BIT(AMDGPU_RING_TYPE_UVD) | BIT(AMDGPU_RING_TYPE_UVD_ENC); + + case AMD_IP_BLOCK_TYPE_VCE: + return BIT(AMD_IP_BLOCK_TYPE_VCE); + + case AMD_IP_BLOCK_TYPE_VCN: + return BIT(AMDGPU_RING_TYPE_VCN_DEC) | BIT(AMDGPU_RING_TYPE_VCN_ENC); + + case AMD_IP_BLOCK_TYPE_JPEG: + return BIT(AMDGPU_RING_TYPE_VCN_JPEG); + + case AMD_IP_BLOCK_TYPE_VPE: + return BIT(AMDGPU_RING_TYPE_VPE); + + default: + return 0; + } +} + +/** + * amdgpu_filter_rings() - Filter rings according to a mask. + * + * @adev: amdgpu_device pointer + * @ring_type_mask: Mask of ring types you are looking for + * @out_rings: Array of rings which is going to be filled + * @out_num_rings: Number of rings which were filtered + */ +static void amdgpu_filter_rings(struct amdgpu_device *adev, const u32 ring_type_mask, + struct amdgpu_ring **out_rings, u32 *out_num_rings) +{ + u32 num_rings = 0; + int i; + + for (i = 0; i < adev->num_rings; ++i) { + if (BIT(adev->rings[i]->funcs->type) & ring_type_mask) + out_rings[num_rings++] = adev->rings[i]; + } + + *out_num_rings = num_rings; +} + +/** + * amdgpu_device_ip_soft_reset() - Perform a graceful soft reset on an IP block. + * + * @guilty_ring: The ring which is guilty of causing a reset. + * @guilty_fence: The fence which didn't signal. + * + * IP block soft reset is used when attempting to recover + * from a GPU hang in a situation where a more fine grained + * reset type isn't available or didn't work. This effectively + * resets all rings that belong to the same device IP block + * and re-initializes the device IP block. + * + * The reset is handled gracefully, meaning that we try to + * minimize collateral damage (ie. avoid rejecting non-guilty jobs) + * as well as back up and restore the contents of all rings + * so that the system can move on from the hang. + */ +int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, + struct amdgpu_fence *guilty_fence) +{ + struct amdgpu_device *adev = guilty_ring->adev; + struct amdgpu_ring *rings[AMDGPU_MAX_RINGS]; + struct amdgpu_ip_block *ip_block; + enum amd_ip_block_type ip_type; + u32 num_rings, ring_type_mask; + int r; + + ip_type = amdgpu_ip_from_ring(guilty_ring->funcs->type); + ip_block = amdgpu_device_ip_get_ip_block(adev, ip_type); + + if (!ip_block || !ip_block->version->funcs->soft_reset) { + dev_warn(adev->dev, "IP block soft reset not supported on %s\n", + ip_block->version->funcs->name); + return -EOPNOTSUPP; + } + + dev_err(adev->dev, "Starting %s IP block soft reset\n", + ip_block->version->funcs->name); + + ring_type_mask = amdgpu_ring_mask_from_ip(ip_type); + amdgpu_filter_rings(adev, ring_type_mask, rings, &num_rings); + + amdgpu_device_lock_reset_domain(adev->reset_domain); + amdgpu_multi_ring_reset_helper_begin(rings, num_rings, guilty_ring, guilty_fence); + + r = ip_block->version->funcs->soft_reset(ip_block); + + r = amdgpu_multi_ring_reset_helper_end(rings, num_rings, guilty_ring, r); + amdgpu_device_unlock_reset_domain(adev->reset_domain); + + if (r) { + dev_err(adev->dev, "Failed %s IP block soft reset: %d\n", + ip_block->version->funcs->name, r); + return r; + } + + dev_err(adev->dev, "Successful %s IP block soft reset\n", + ip_block->version->funcs->name); + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h index 590ad82f115e..18fd8631a092 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h @@ -85,6 +85,9 @@ enum amd_hw_ip_block_type { #define IP_VERSION_SUBREV(ver) ((ver) & 0xF) #define IP_VERSION_MAJ_MIN_REV(ver) ((ver) >> 8) +struct amdgpu_ring; +struct amdgpu_fence; + struct amdgpu_ip_map_info { /* Map of logical to actual dev instances/mask */ uint32_t dev_inst[MAX_HWIP][HWIP_MAX_INSTANCE]; @@ -151,5 +154,7 @@ bool amdgpu_device_ip_is_hw(struct amdgpu_device *adev, enum amd_ip_block_type block_type); bool amdgpu_device_ip_is_valid(struct amdgpu_device *adev, enum amd_ip_block_type block_type); +int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, + struct amdgpu_fence *guilty_fence); #endif /* __AMDGPU_IP_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index 8c40eb8cec51..cff73f1b5a72 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -151,6 +151,17 @@ static enum drm_gpu_sched_stat amdgpu_job_timedout(struct drm_sched_job *s_job) dev_err(adev->dev, "Ring %s reset failed\n", ring->sched.name); } + /* Attempt an IP block soft reset, if supported. */ + if (amdgpu_gpu_recovery && + amdgpu_ring_is_reset_type_supported(ring, AMDGPU_RESET_TYPE_IP_BLOCK_SOFT_RESET)) { + r = amdgpu_device_ip_soft_reset(ring, job->hw_fence); + if (!r) { + atomic_inc(&ring->adev->gpu_reset_counter); + drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, info); + goto exit; + } + } + if (dma_fence_get_status(&s_job->s_fence->finished) == 0) dma_fence_set_error(&s_job->s_fence->finished, -ETIME); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index b97fa35bac23..3f78aa6ed82f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -935,6 +935,177 @@ int amdgpu_ring_reset_helper_end(struct amdgpu_ring *ring, return 0; } +/** + * amdgpu_multi_ring_reset_helper_begin() - Prepare multiple rings for a reset. + * + * @rings: Pointer to an array of amdgpu rings that are affected. + * @num_rings: Number of rings in the array. + * @guilty_ring: The ring which is guilty of causing a reset. + * @guilty_fence: The fence which didn't signal on the guilty ring. + * + * Useful when performing a GPU reset method that affects + * multiple rings at the same time, such as an IP block soft + * reset. For example, a GFX IP block soft reset will affect + * every graphics and compute queue. + * + * This function should be called before such a reset. + * + * Prepare the affected rings before the reset, make sure to + * minimize collateral damage, and backup the contents of + * the rings. Then the caller can call the actual HW specific + * reset function. + * + * After the reset is complete, the caller should then call + * amdgpu_multi_ring_reset_helper_end() to restore the rings. + */ +void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_rings, + struct amdgpu_ring *guilty_ring, + struct amdgpu_fence *guilty_fence) +{ + struct amdgpu_device *adev = guilty_ring->adev; + struct amdgpu_fence *ring_guilty_fence; + struct amdgpu_ring *ring; + bool rings_busy; + int i; + u32 t; + + for (i = 0; i < num_rings; ++i) { + ring = rings[i]; + + /* Don't accept new submissions on the ring. */ + if (amdgpu_ring_sched_ready(ring) && !drm_sched_is_stopped(&ring->sched)) + drm_sched_wqueue_stop(&ring->sched); + + /* + * Clear the preempt condition to stop the ring + * from starting its next submission. This ensures + * that only the currently executing submission + * can be rejected because of the reset and helps + * minimize collateral damage. + */ + if (ring->funcs->init_cond_exec) + amdgpu_ring_set_preempt_cond_exec(ring, false); + } + + /* Flush HDP cache so the GPU can see the updated COND_EXEC values */ + amdgpu_device_flush_hdp(adev, NULL); + + /* + * Give some time for non-guilty rings to finish their + * current submission, to try to minimize collateral damage. + * + * Note that this just a best effort, but really there + * is no way to really know which ring is actually responsible + * because different rings may share resources, eg. a compute + * ring may hog shader engines, causing a graphics ring to hang. + */ + for (t = 0; t < adev->usec_timeout; t += 10000) { + rings_busy = false; + + /* Check if any of the non-guilty rings are busy */ + for (i = 0; i < num_rings; ++i) { + ring = rings[i]; + + if (ring == guilty_ring) + continue; + + rings_busy |= + atomic_read(&ring->fence_drv.last_seq) != + READ_ONCE(ring->fence_drv.sync_seq); + } + + if (!rings_busy) + break; + + mdelay(10); + } + + for (i = 0; i < num_rings; ++i) { + ring = rings[i]; + + /* + * Find guilty fences, ie. the fences that didn't signal + * on each ring. At this point there is no way to know + * which one is really responsible for the hang, and no + * way to save any of them, so we treat all of them as guilty. + */ + ring_guilty_fence = + ring == guilty_ring ? guilty_fence : + amdgpu_ring_find_guilty_fence(ring); + + /* + * Backup current contents of the ring. + * The helper takes care to only reemit unsignalled fences + * so we don't have to worry about that here. + */ + amdgpu_ring_reset_helper_begin(ring, ring_guilty_fence); + } +} + +/** + * amdgpu_multi_ring_reset_helper_end() - Prepare multiple rings for a reset. + * + * @rings: Pointer to an array of amdgpu rings that are affected. + * @num_rings: Number of rings in the array. + * @guilty_ring: The ring which is guilty of causing a reset. + * @ret: Return code from the reset function. + * + * After calling amdgpu_multi_ring_reset_helper_end() + * and executing the actual reset method, call this + * function to restore normal operation. + * + * In case the reset failed, this function should still + * be called to restore some state, but it won't attempt to + * fully restore the ring contents. + */ +int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings, + struct amdgpu_ring *guilty_ring, int ret) +{ + struct amdgpu_device *adev = guilty_ring->adev; + struct amdgpu_ring *ring; + int i, r; + + /* Set preempt condition, rings are now allowed to execute submissions */ + for (i = 0; i < num_rings; ++i) { + ring = rings[i]; + + if (ring->funcs->init_cond_exec) + amdgpu_ring_set_preempt_cond_exec(ring, true); + } + + /* Flush HDP cache so the GPU can see the updated COND_EXEC values */ + amdgpu_device_flush_hdp(adev, NULL); + + /* If the reset was unsuccessful, return without restoring anything. */ + if (ret) + return ret; + + /* Restore contents of all rings */ + for (i = 0; i < num_rings; ++i) { + ring = rings[i]; + + r = amdgpu_ring_reset_helper_end(ring, ring->guilty_fence); + if (r) { + dev_err(adev->dev, + "Failed to recover ring %s after soft reset\n", + ring->name); + return r; + } + } + + /* Accept submissions on all rings again */ + for (i = 0; i < num_rings; ++i) { + ring = rings[i]; + + if (!amdgpu_ring_sched_ready(ring)) + continue; + + drm_sched_wqueue_start(&ring->sched); + } + + return 0; +} + bool amdgpu_ring_is_reset_type_supported(struct amdgpu_ring *ring, u32 reset_type) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index 71cd9bb12f75..c272e0b028ad 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -595,6 +595,11 @@ void amdgpu_ring_reset_helper_begin(struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence); int amdgpu_ring_reset_helper_end(struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence); +void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_rings, + struct amdgpu_ring *guilty_ring, + struct amdgpu_fence *guilty_fence); +int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings, + struct amdgpu_ring *guilty_ring, int ret); bool amdgpu_ring_is_reset_type_supported(struct amdgpu_ring *ring, u32 reset_type); #endif -- cgit v1.2.3 From a25d644890c17115482d1d16ad0675fe0f14554e Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:23 +0200 Subject: drm/amdgpu/gfx8: Stop CP and RLC during reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only case when they may not go idle is when we are dealing with a GPU hang, in which case we should just forcibly disable these even when they aren't idle. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 8ae9ab0fb886..c383035073a4 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4868,14 +4868,12 @@ static int gfx_v8_0_hw_fini(struct amdgpu_ip_block *ip_block) } amdgpu_gfx_rlc_enter_safe_mode(adev, 0); - if (!gfx_v8_0_wait_for_idle(ip_block)) - gfx_v8_0_cp_enable(adev, false); - else + if (!amdgpu_in_reset(adev) && gfx_v8_0_wait_for_idle(ip_block)) pr_err("cp is busy, skip halt cp\n"); - if (!gfx_v8_0_wait_for_rlc_idle(adev)) - adev->gfx.rlc.funcs->stop(adev); - else - pr_err("rlc is busy, skip halt rlc\n"); + if (!amdgpu_in_reset(adev) && gfx_v8_0_wait_for_rlc_idle(adev)) + pr_err("rlc is busy\n"); + gfx_v8_0_cp_enable(adev, false); + adev->gfx.rlc.funcs->stop(adev); amdgpu_gfx_rlc_exit_safe_mode(adev, 0); return 0; -- cgit v1.2.3 From 1a92c648fb11684c01d7a2800fe42ae3f5062037 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:24 +0200 Subject: drm/amdgpu/gfx8: Return error when testing all rings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gfx_v8_0_cp_test_all_rings() function should return success only when all ring tests were successful. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index c383035073a4..e753b029a077 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4703,12 +4703,14 @@ static int gfx_v8_0_cp_test_all_rings(struct amdgpu_device *adev) if (r) return r; + r = 0; + for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; - amdgpu_ring_test_helper(ring); + r |= amdgpu_ring_test_helper(ring); } - return 0; + return r; } static int gfx_v8_0_cp_resume(struct amdgpu_device *adev) -- cgit v1.2.3 From 6e54e467973eb4a541a1ffe5c81ed54b9b59eda5 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:25 +0200 Subject: drm/amdgpu/gfx8: Support COND_EXEC on compute rings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is useful to minimize collateral damage during an IP block soft reset. We can clear the COND_EXEC condition so that only the currently executing submission is at risk. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index e753b029a077..9bb50c147042 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -6787,10 +6787,12 @@ static const struct amdgpu_ring_funcs gfx_v8_0_ring_funcs_compute = { .get_wptr = gfx_v8_0_ring_get_wptr_compute, .set_wptr = gfx_v8_0_ring_set_wptr_compute, .emit_frame_size = + 5 + /* gfx_v8_0_ring_emit_init_cond_exec (from amdgpu_ib_schedule) */ 20 + /* gfx_v8_0_ring_emit_gds_switch */ 7 + /* gfx_v8_0_ring_emit_hdp_flush */ 5 + /* hdp_invalidate */ 7 + /* gfx_v8_0_ring_emit_pipeline_sync */ + 5 + /* gfx_v8_0_ring_emit_init_cond_exec (from amdgpu_vm_flush) */ VI_FLUSH_GPU_TLB_NUM_WREG * 5 + 7 + /* gfx_v8_0_ring_emit_vm_flush */ 7 + 7 + 7 + /* gfx_v8_0_ring_emit_fence_compute x3 for user fence, vm fence */ 7 + /* gfx_v8_0_emit_mem_sync_compute */ @@ -6811,6 +6813,7 @@ static const struct amdgpu_ring_funcs gfx_v8_0_ring_funcs_compute = { .soft_recovery = gfx_v8_0_ring_soft_recovery, .emit_mem_sync = gfx_v8_0_emit_mem_sync_compute, .emit_wave_limit = gfx_v8_0_emit_wave_limit, + .init_cond_exec = gfx_v8_0_ring_emit_init_cond_exec, }; static const struct amdgpu_ring_funcs gfx_v8_0_ring_funcs_kiq = { -- cgit v1.2.3 From 01f4b82944a05ac6e4c72a071e5f2a56676349af Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:26 +0200 Subject: drm/amdgpu/gfx8: Adjust EDC GPR workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the compute queue is unavailable to run the IB, return -EBUSY instead of silently failing. Make sure the IB is always executed during reset: Set preempt condition (may be cleared during reset), and flush HDP cache so the GPU sees the updated value. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 9 ++++++++- 1 file changed, 8 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 9bb50c147042..7643077ad318 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -1487,7 +1487,14 @@ static int gfx_v8_0_do_edc_gpr_workarounds(struct amdgpu_device *adev) /* bail if the compute ring is not ready */ if (!ring->sched.ready) - return 0; + return -EBUSY; + + if (amdgpu_in_reset(adev)) { + /* Set preempt condition to execute IB */ + amdgpu_ring_set_preempt_cond_exec(ring, true); + /* Flush HDP cache so the GPU can see the updated COND_EXEC value */ + amdgpu_device_flush_hdp(adev, NULL); + } tmp = RREG32(mmGB_EDC_MODE); WREG32(mmGB_EDC_MODE, 0); -- cgit v1.2.3 From 459813d418e05ed9848502486e8cb1119a93bfca Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:27 +0200 Subject: drm/amdgpu/gfx8: Fixup IP block soft reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always reset everything in the GFX block at once as opposed to trying to figure out which blocks need to be reset based on their busy flags. This makes the reset more robust and predictable. Increase delays when waiting for the GRBM and SRBM soft reset to complete. Call IP block suspend/resume to ensure correct operation now that we no longer have pre/post_soft_reset(). Call clock/powergating functions, otherwise power consumption will increase after the GFX IP block is soft reset. Return correct error code to signal failure in case not all rings are functional after the IP block is soft reset. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 2 -- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 43 ++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index 381fc17274b9..aefd4f03b443 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -485,8 +485,6 @@ struct amdgpu_gfx { const struct amdgpu_gfx_funcs *funcs; /* reset mask */ - uint32_t grbm_soft_reset; - uint32_t srbm_soft_reset; uint32_t gfx_supported_reset; uint32_t compute_supported_reset; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 7643077ad318..88dcadc53d91 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4904,13 +4904,19 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) u32 grbm_soft_reset = 0, srbm_soft_reset = 0; u32 tmp; int i; + int r; - if ((!adev->gfx.grbm_soft_reset) && - (!adev->gfx.srbm_soft_reset)) - return 0; + grbm_soft_reset = + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_RLC, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_GFX, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CP, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CPF, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CPC, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CPG, 1); - grbm_soft_reset = adev->gfx.grbm_soft_reset; - srbm_soft_reset = adev->gfx.srbm_soft_reset; + srbm_soft_reset = + REG_SET_FIELD(0, SRBM_SOFT_RESET, SOFT_RESET_GRBM, 1) | + REG_SET_FIELD(0, SRBM_SOFT_RESET, SOFT_RESET_SEM, 1); for (i = 0; i < adev->gfx.num_compute_rings; i++) { struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; @@ -4920,14 +4926,21 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) gfx_v8_0_deactivate_hqd(adev, 2); vi_srbm_select(adev, 0, 0, 0, 0); mutex_unlock(&adev->srbm_mutex); + + udelay(50); } + ip_block->version->funcs->set_clockgating_state(ip_block, AMD_CG_STATE_UNGATE); + ip_block->version->funcs->set_powergating_state(ip_block, AMD_PG_STATE_UNGATE); + ip_block->version->funcs->suspend(ip_block); + if (grbm_soft_reset || srbm_soft_reset) { tmp = RREG32(mmGMCON_DEBUG); tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_STALL, 1); tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_CLEAR, 1); WREG32(mmGMCON_DEBUG, tmp); - udelay(50); + + udelay(100); } if (grbm_soft_reset) { @@ -4937,11 +4950,13 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) WREG32(mmGRBM_SOFT_RESET, tmp); tmp = RREG32(mmGRBM_SOFT_RESET); - udelay(50); + udelay(100); tmp &= ~grbm_soft_reset; WREG32(mmGRBM_SOFT_RESET, tmp); tmp = RREG32(mmGRBM_SOFT_RESET); + + udelay(100); } if (srbm_soft_reset) { @@ -4951,11 +4966,13 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) WREG32(mmSRBM_SOFT_RESET, tmp); tmp = RREG32(mmSRBM_SOFT_RESET); - udelay(50); + udelay(100); tmp &= ~srbm_soft_reset; WREG32(mmSRBM_SOFT_RESET, tmp); tmp = RREG32(mmSRBM_SOFT_RESET); + + udelay(100); } if (grbm_soft_reset || srbm_soft_reset) { @@ -4966,7 +4983,15 @@ static int gfx_v8_0_soft_reset(struct amdgpu_ip_block *ip_block) } /* Wait a little for things to settle down */ - udelay(50); + udelay(100); + + r = ip_block->version->funcs->resume(ip_block); + r |= ip_block->version->funcs->late_init(ip_block); + if (r) + return r; + + ip_block->version->funcs->set_clockgating_state(ip_block, AMD_CG_STATE_GATE); + ip_block->version->funcs->set_powergating_state(ip_block, AMD_PG_STATE_GATE); return 0; } -- cgit v1.2.3 From 3ad38c26019b80ee44727dd167328152ba668f6f Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:28 +0200 Subject: drm/amdgpu/gfx8: Enable IP block soft reset as a GPU recovery method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable IP block soft reset as a GPU recovery method for GFX8 graphics and compute rings. Tested with the "hard_reset_cp_wait" test case from the Hang Test Suite created by Natalie Vock and Konstantin Seurer. This Vulkan testcase waits for an event that never occurs, effectively a WAIT_REG_MEM packet that intentionally hangs. IP block soft reset can resolve that hang and allow the rest of the system to move on and keep functioning without needing a full ASIC reset. Tested on the following chips: Polaris 10 (Radeon RX 570) Polaris 11 (Radeon RX 560) Polaris 12 (Radeon RX 550) Fiji (Radeon R9 Nano) Tonga (Radeon R9 380X) Carrizo (A8-9600) Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 88dcadc53d91..bee2ff6865f9 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -2035,6 +2035,11 @@ static int gfx_v8_0_sw_init(struct amdgpu_ip_block *ip_block) adev->gfx.compute_supported_reset = amdgpu_get_soft_full_reset_mask(&adev->gfx.compute_ring[0]); + if (!amdgpu_sriov_vf(adev) && !adev->debug_disable_ip_block_soft_reset) { + adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_IP_BLOCK_SOFT_RESET; + adev->gfx.gfx_supported_reset |= AMDGPU_RESET_TYPE_IP_BLOCK_SOFT_RESET; + } + return 0; } -- cgit v1.2.3 From 323a09e56c1d549ce47d4f110de77b0051b4a8bf Mon Sep 17 00:00:00 2001 From: Leorize Date: Mon, 18 May 2026 20:06:19 -0700 Subject: drm/amd/display: set MSA MISC1 bit 6 when using VSC SDP for DCE 11.x When BT.2020 colorimetry is selected, the driver sends information using VSC SDP but does not set "ignore MSA colorimetry" bit on older GPUs with DCE-based IPs. This causes certain sinks to prefer colorimetry information in DP MSA, resulting in terrible color rendering ("dull" colors) when HDR is enabled. This commit wires up the MISC1 bit 6 for GPUs with DCE 11.x based IPs to correctly configure sinks to ignore colorimetry information in MSA, resolving the color rendering issue. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/4849 Assisted-by: oh-my-pi:GPT-5.5 Signed-off-by: Leorize Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.c | 15 ++++++++++++++- drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.h | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.c b/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.c index ed407e779c12..2c3a20d35fe9 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.c @@ -271,7 +271,6 @@ static void dce110_stream_encoder_dp_set_stream_attribute( bool use_vsc_sdp_for_colorimetry, uint32_t enable_sdp_splitting) { - (void)use_vsc_sdp_for_colorimetry; (void)enable_sdp_splitting; uint32_t h_active_start; uint32_t v_active_start; @@ -334,6 +333,16 @@ static void dce110_stream_encoder_dp_set_stream_attribute( if (REG(DP_MSA_MISC)) misc1 = REG_READ(DP_MSA_MISC); + /* For YCbCr420 and BT2020 Colorimetry Formats, VSC SDP shall be used. + * When MISC1, bit 6, is Set to 1, a Source device uses a VSC SDP to indicate the + * Pixel Encoding/Colorimetry Format and that a Sink device shall ignore MISC1, bit 7, + * and MISC0, bits 7:1 (MISC1, bit 7, and MISC0, bits 7:1, become "don't care"). + */ + if (use_vsc_sdp_for_colorimetry) + misc1 = misc1 | 0x40; + else + misc1 = misc1 & ~0x40; + /* set color depth */ switch (hw_crtc_timing.display_color_depth) { @@ -499,6 +508,10 @@ static void dce110_stream_encoder_dp_set_stream_attribute( hw_crtc_timing.h_addressable + hw_crtc_timing.h_border_right, DP_MSA_VHEIGHT, hw_crtc_timing.v_border_top + hw_crtc_timing.v_addressable + hw_crtc_timing.v_border_bottom); + } else { + /* DCE-only path */ + if (REG(DP_MSA_MISC)) + REG_WRITE(DP_MSA_MISC, misc1); /* MSA_MISC1 */ } } diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.h b/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.h index 342c0afe6a94..88d6044904d1 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.h +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_stream_encoder.h @@ -96,7 +96,8 @@ #define SE_COMMON_REG_LIST(id)\ SE_COMMON_REG_LIST_DCE_BASE(id), \ - SRI(AFMT_CNTL, DIG, id) + SRI(AFMT_CNTL, DIG, id), \ + SRI(DP_MSA_MISC, DP, id) #define SE_DCN_REG_LIST(id)\ SE_COMMON_REG_LIST_BASE(id),\ -- cgit v1.2.3 From 230753e46a4a9d04dad6a9b5dbaeb7fd52add7d0 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 17 Jun 2026 14:42:02 +0530 Subject: drm/amdgpu: Guard reads in pcie state readout Internal US/DS switch may not be exposed in passthrough. Guard the upstream port reads to avoid a NULL dereference. Signed-off-by: Lijo Lazar Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c | 59 +++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c b/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c index 1c11cc280599..cddfe4015f53 100644 --- a/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c +++ b/drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c @@ -591,6 +591,29 @@ static struct aqua_reg_list pcie_reg_addrs[] = { { smreg_0x1A380088, 6, DW_ADDR_INCR }, }; +/* + * Return the GPU's internal US switch port, or NULL if it is not visible + * (e.g. passthrough) or the EP is parented under an unrelated bridge. + */ +static struct pci_dev *aqua_vanjaram_get_us_pdev(struct amdgpu_device *adev) +{ + struct pci_dev *ds_pdev, *us_pdev; + + ds_pdev = pci_upstream_bridge(adev->pdev); + if (!ds_pdev || ds_pdev->vendor != PCI_VENDOR_ID_ATI || + pci_pcie_type(ds_pdev) != PCI_EXP_TYPE_DOWNSTREAM) + return NULL; + + us_pdev = pci_upstream_bridge(ds_pdev); + if (!us_pdev || + (us_pdev->vendor != PCI_VENDOR_ID_ATI && + us_pdev->vendor != PCI_VENDOR_ID_AMD) || + pci_pcie_type(us_pdev) != PCI_EXP_TYPE_UPSTREAM) + return NULL; + + return us_pdev; +} + static ssize_t aqua_vanjaram_read_pcie_state(struct amdgpu_device *adev, void *buf, size_t max_size) { @@ -598,7 +621,7 @@ static ssize_t aqua_vanjaram_read_pcie_state(struct amdgpu_device *adev, uint32_t start_addr, incrx, num_regs, szbuf; struct amdgpu_regs_pcie_v1_0 *pcie_regs; struct amdgpu_smn_reg_data *reg_data; - struct pci_dev *us_pdev, *ds_pdev; + struct pci_dev *us_pdev; int aer_cap, r, n; if (!buf || !max_size) @@ -630,25 +653,27 @@ static ssize_t aqua_vanjaram_read_pcie_state(struct amdgpu_device *adev, } } - ds_pdev = pci_upstream_bridge(adev->pdev); - us_pdev = pci_upstream_bridge(ds_pdev); + us_pdev = aqua_vanjaram_get_us_pdev(adev); + if (us_pdev) { + pcie_capability_read_word(us_pdev, PCI_EXP_DEVSTA, + &pcie_regs->device_status); + pcie_capability_read_word(us_pdev, PCI_EXP_LNKSTA, + &pcie_regs->link_status); + + aer_cap = pci_find_ext_capability(us_pdev, PCI_EXT_CAP_ID_ERR); + if (aer_cap) { + pci_read_config_dword(us_pdev, + aer_cap + PCI_ERR_COR_STATUS, + &pcie_regs->pcie_corr_err_status); + pci_read_config_dword(us_pdev, + aer_cap + PCI_ERR_UNCOR_STATUS, + &pcie_regs->pcie_uncorr_err_status); + } - pcie_capability_read_word(us_pdev, PCI_EXP_DEVSTA, - &pcie_regs->device_status); - pcie_capability_read_word(us_pdev, PCI_EXP_LNKSTA, - &pcie_regs->link_status); - - aer_cap = pci_find_ext_capability(us_pdev, PCI_EXT_CAP_ID_ERR); - if (aer_cap) { - pci_read_config_dword(us_pdev, aer_cap + PCI_ERR_COR_STATUS, - &pcie_regs->pcie_corr_err_status); - pci_read_config_dword(us_pdev, aer_cap + PCI_ERR_UNCOR_STATUS, - &pcie_regs->pcie_uncorr_err_status); + pci_read_config_dword(us_pdev, PCI_PRIMARY_BUS, + &pcie_regs->sub_bus_number_latency); } - pci_read_config_dword(us_pdev, PCI_PRIMARY_BUS, - &pcie_regs->sub_bus_number_latency); - pcie_reg_state->common_header.structure_size = szbuf; pcie_reg_state->common_header.format_revision = 1; pcie_reg_state->common_header.content_revision = 0; -- cgit v1.2.3 From be88697602238c3dcb47bbc2db1eef923f63e78b Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Tue, 16 Jun 2026 10:14:58 +0530 Subject: drm/amdgpu: bounds check ATOM IIO table parsing atom_index_iio() parsed the IIO bytecode without bounds checks, allowing out-of-bounds reads on a malformed VBIOS. Pass the BIOS size into amdgpu_atom_parse() and bound the parse loops by it. Signed-off-by: Lijo Lazar Assisted-by: Claude Code Reviewed-by: Alex Deucher Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c | 2 +- drivers/gpu/drm/amd/amdgpu/atom.c | 25 ++++++++++++++++++++----- drivers/gpu/drm/amd/amdgpu/atom.h | 3 ++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c index acd22bff1882..27c0dc8f6137 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c @@ -1923,7 +1923,7 @@ int amdgpu_atombios_init(struct amdgpu_device *adev) atom_card_info->pll_read = cail_pll_read; atom_card_info->pll_write = cail_pll_write; - adev->mode_info.atom_context = amdgpu_atom_parse(atom_card_info, adev->bios); + adev->mode_info.atom_context = amdgpu_atom_parse(atom_card_info, adev->bios, adev->bios_size); if (!adev->mode_info.atom_context) { amdgpu_atombios_fini(adev); return -ENOMEM; diff --git a/drivers/gpu/drm/amd/amdgpu/atom.c b/drivers/gpu/drm/amd/amdgpu/atom.c index ca5d091549e1..c3824934ac7d 100644 --- a/drivers/gpu/drm/amd/amdgpu/atom.c +++ b/drivers/gpu/drm/amd/amdgpu/atom.c @@ -1327,11 +1327,25 @@ static void atom_index_iio(struct atom_context *ctx, int base) ctx->iio = kzalloc(2 * 256, GFP_KERNEL); if (!ctx->iio) return; - while (CU8(base) == ATOM_IIO_START) { - ctx->iio[CU8(base + 1)] = base + 2; + while (base + 1 < ctx->bios_size && CU8(base) == ATOM_IIO_START) { + uint8_t index = CU8(base + 1); + int start = base + 2; base += 2; - while (CU8(base) != ATOM_IIO_END) - base += atom_iio_len[CU8(base)]; + while (base < ctx->bios_size && CU8(base) != ATOM_IIO_END) { + uint8_t op = CU8(base); + + /* + * Unknown opcode: its length is unknown so the byte + * stream cannot be resynced reliably. + */ + if (op >= ARRAY_SIZE(atom_iio_len)) + return; + base += atom_iio_len[op]; + } + if (base >= ctx->bios_size) + return; + /* Only index well-formed methods, others stay 0 */ + ctx->iio[index] = start; base += 3; } } @@ -1553,7 +1567,7 @@ static inline void atom_print_vbios_info(struct atom_context *ctx) drm_info(ctx->card->dev, "ATOM BIOS: %s\n", vbios_info); } -struct atom_context *amdgpu_atom_parse(struct card_info *card, void *bios) +struct atom_context *amdgpu_atom_parse(struct card_info *card, void *bios, uint32_t bios_size) { int base; struct atom_context *ctx = @@ -1567,6 +1581,7 @@ struct atom_context *amdgpu_atom_parse(struct card_info *card, void *bios) ctx->card = card; ctx->bios = bios; + ctx->bios_size = bios_size; if (CU16(0) != ATOM_BIOS_MAGIC) { pr_info("Invalid BIOS magic\n"); diff --git a/drivers/gpu/drm/amd/amdgpu/atom.h b/drivers/gpu/drm/amd/amdgpu/atom.h index bb3d9eb7eb6b..4687c019cbe3 100644 --- a/drivers/gpu/drm/amd/amdgpu/atom.h +++ b/drivers/gpu/drm/amd/amdgpu/atom.h @@ -133,6 +133,7 @@ struct atom_context { struct card_info *card; struct mutex mutex; void *bios; + uint32_t bios_size; uint32_t cmd_table, data_table; uint16_t *iio; @@ -160,7 +161,7 @@ struct atom_context { extern int amdgpu_atom_debug; -struct atom_context *amdgpu_atom_parse(struct card_info *card, void *bios); +struct atom_context *amdgpu_atom_parse(struct card_info *card, void *bios, uint32_t bios_size); int amdgpu_atom_execute_table(struct atom_context *ctx, int index, uint32_t *params, int params_size); int amdgpu_atom_asic_init(struct atom_context *ctx); void amdgpu_atom_destroy(struct atom_context *ctx); -- cgit v1.2.3 From 4e9c8a9c322427055c4892183d266ba391af1bc8 Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Fri, 12 Jun 2026 13:03:35 -0400 Subject: drm/amdkfd: drop struct kfd_signal_page wrapper struct kfd_signal_page now only wraps a single uint64_t *kernel_address pointer. Drop the wrapper struct (and the page_slots() helper) and store the signal page pointer directly in kfd_process::signal_page. Since the signal page is the GTT BO mapping provided by user mode and is not owned by the events code, no separate allocation/free is needed for it, so shutdown_signal_page() goes away as well. No functional change intended. Signed-off-by: Yongqiang Sun Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_events.c | 48 +++++---------------------------- drivers/gpu/drm/amd/amdkfd/kfd_events.h | 1 - drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 9 ++++++- 3 files changed, 14 insertions(+), 44 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index cf10e0902f18..43a04365a8c4 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -46,22 +46,6 @@ struct kfd_event_waiter { bool event_age_enabled; /* set to true when last_event_age is non-zero */ }; -/* - * Each signal event needs a 64-bit signal slot where the signaler will write - * a 1 before sending an interrupt. (This is needed because some interrupts - * do not contain enough spare data bits to identify an event.) - * We get whole pages and map them to the process VA. - * Individual signal events use their event_id as slot index. - */ -struct kfd_signal_page { - uint64_t *kernel_address; -}; - -static uint64_t *page_slots(struct kfd_signal_page *page) -{ - return page->kernel_address; -} - static int allocate_event_notification_slot(struct kfd_process *p, struct kfd_event *ev, const int *restore_id) @@ -93,7 +77,7 @@ static int allocate_event_notification_slot(struct kfd_process *p, return id; ev->event_id = id; - page_slots(p->signal_page)[id] = UNSIGNALED_EVENT_SLOT; + p->signal_page[id] = UNSIGNALED_EVENT_SLOT; return 0; } @@ -139,7 +123,7 @@ static struct kfd_event *lookup_signaled_event_by_partial_id( */ if (bits > 31 || (1U << bits) >= KFD_SIGNAL_EVENT_LIMIT) { if (signal_mailbox_updated && - page_slots(p->signal_page)[id] == UNSIGNALED_EVENT_SLOT) + p->signal_page[id] == UNSIGNALED_EVENT_SLOT) return NULL; return idr_find(&p->event_idr, id); @@ -149,7 +133,7 @@ static struct kfd_event *lookup_signaled_event_by_partial_id( * and find the first one that has signaled. */ for (ev = NULL; id < KFD_SIGNAL_EVENT_LIMIT && !ev; id += 1U << bits) { - if (page_slots(p->signal_page)[id] == UNSIGNALED_EVENT_SLOT) + if (p->signal_page[id] == UNSIGNALED_EVENT_SLOT) continue; ev = idr_find(&p->event_idr, id); @@ -261,21 +245,9 @@ static void destroy_events(struct kfd_process *p) mutex_destroy(&p->event_mutex); } -/* - * We assume that the process is being destroyed and there is no need to - * unmap the pages or keep bookkeeping data in order. - */ -static void shutdown_signal_page(struct kfd_process *p) -{ - struct kfd_signal_page *page = p->signal_page; - - kfree(page); -} - void kfd_event_free_process(struct kfd_process *p) { destroy_events(p); - shutdown_signal_page(p); } static bool event_can_be_gpu_signaled(const struct kfd_event *ev) @@ -292,8 +264,6 @@ static bool event_can_be_cpu_signaled(const struct kfd_event *ev) static int kfd_event_page_set(struct kfd_process *p, void *kernel_address, uint64_t size, uint64_t user_handle) { - struct kfd_signal_page *page; - if (p->signal_page) return -EBUSY; @@ -303,17 +273,11 @@ static int kfd_event_page_set(struct kfd_process *p, void *kernel_address, return -EINVAL; } - page = kzalloc_obj(*page); - if (!page) - return -ENOMEM; - /* Initialize all events to unsignaled */ memset(kernel_address, (uint8_t) UNSIGNALED_EVENT_SLOT, KFD_SIGNAL_EVENT_LIMIT * 8); - page->kernel_address = kernel_address; - - p->signal_page = page; + p->signal_page = kernel_address; p->signal_mapped_size = size; p->signal_handle = user_handle; return 0; @@ -680,7 +644,7 @@ unlock_rcu: static void acknowledge_signal(struct kfd_process *p, struct kfd_event *ev) { - WRITE_ONCE(page_slots(p->signal_page)[ev->event_id], UNSIGNALED_EVENT_SLOT); + WRITE_ONCE(p->signal_page[ev->event_id], UNSIGNALED_EVENT_SLOT); } static void set_event_from_interrupt(struct kfd_process *p, @@ -723,7 +687,7 @@ void kfd_signal_event_interrupt(u32 pasid, uint32_t partial_id, * in the interrupt payload was invalid and do an * exhaustive search of signaled events. */ - uint64_t *slots = page_slots(p->signal_page); + uint64_t *slots = p->signal_page; uint32_t id; if (valid_id_bits) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.h b/drivers/gpu/drm/amd/amdkfd/kfd_events.h index 88e3797bfc42..827a2c7d7721 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.h @@ -49,7 +49,6 @@ #define UNSIGNALED_EVENT_SLOT ((uint64_t)-1) struct kfd_event_waiter; -struct signal_page; struct kfd_event { u32 event_id; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index 7b623e3f5efd..90f010cbe54e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -952,7 +952,14 @@ struct kfd_process { struct idr event_idr; /* Event page */ u64 signal_handle; - struct kfd_signal_page *signal_page; + /* + * Each signal event needs a 64-bit signal slot where the signaler will + * write a 1 before sending an interrupt. (This is needed because some + * interrupts do not contain enough spare data bits to identify an + * event.) The signal page is allocated in user mode and mapped to the + * kernel; individual signal events use their event_id as slot index. + */ + uint64_t *signal_page; size_t signal_mapped_size; size_t signal_event_count; bool signal_event_limit_reached; -- cgit v1.2.3 From b78bd145e42c831f7d3a4ba612ebb89060efa720 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Fri, 19 Jun 2026 12:37:01 -0400 Subject: drm/amdkfd: avoid PTL confused warning message PTL is a special feature for gfxv9.4.4, but the warning is always appearing on other ASICs when rocprof is running, it causes confusion, so move hw_supported check earlier to avoid it. Signed-off-by: Eric Huang Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index fcdb4e222167..38c6cb1f49a6 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1783,9 +1783,6 @@ 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) - return -EOPNOTSUPP; - if (!pdd->dev->kfd2kgd || !pdd->dev->kfd2kgd->ptl_ctrl) return -EOPNOTSUPP; @@ -1804,6 +1801,9 @@ int kfd_ptl_disable_request(struct kfd_process_device *pdd, struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret = 0; + if (!ptl->hw_supported) + return -EOPNOTSUPP; + mutex_lock(&ptl->mutex); if (pdd->ptl_disable_req) @@ -1833,6 +1833,9 @@ int kfd_ptl_disable_release(struct kfd_process_device *pdd, struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret = 0; + if (!ptl->hw_supported) + return -EOPNOTSUPP; + mutex_lock(&ptl->mutex); if (!pdd->ptl_disable_req) -- cgit v1.2.3 From 3dc4d68ee26a7ac12069ff0562ad8935106c79b6 Mon Sep 17 00:00:00 2001 From: Matthew Jacob Date: Fri, 19 Jun 2026 11:45:46 -0700 Subject: drm/amdgpu: Support some Barco AMD based graphics adapters These adapters typically are only supported by Barco on the Windows platform. However, with these changes in the linux driver, multiple monitor support should work correctly. Signed-off-by: Matthew Jacob Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index f5e8e4f455ee..87885326f68b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -1940,6 +1940,7 @@ static const struct pci_device_id pciidlist[] = { {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}, @@ -2009,6 +2010,7 @@ static const struct pci_device_id pciidlist[] = { {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}, /* 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}, @@ -2037,6 +2039,7 @@ static const struct pci_device_id pciidlist[] = { {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}, @@ -2050,6 +2053,7 @@ static const struct pci_device_id pciidlist[] = { {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}, -- cgit v1.2.3 From 3e41d26c70b0a459d041cc19482a226c4b7423cb Mon Sep 17 00:00:00 2001 From: Boyuan Zhang Date: Tue, 12 May 2026 10:29:36 -0400 Subject: drm/amdgpu: fix division by zero with invalid uvd dimensions When width or height is less than 16, width_in_mb or height_in_mb becomes 0, leading to fs_in_mb being 0. This causes a division by zero when calculating num_dpb_buffer in H264 and H264 Perf decode paths. Add validation to reject frames with width < 16 or height < 16 before performing any calculations that depend on these values. V2: Format change - move up all vaiable definitions. V3: Use warn_once to avoid spam. Signed-off-by: Boyuan Zhang Reviewed-by: Leo Liu Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 480bf88def46..23383ac5323f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -655,6 +655,14 @@ static int amdgpu_uvd_cs_msg_decode(struct amdgpu_device *adev, uint32_t *msg, unsigned int image_size, tmp, min_dpb_size, num_dpb_buffer; unsigned int min_ctx_size = ~0; + /* Reject invalid dimensions to prevent division by zero */ + if (width < 16 || height < 16) { + dev_WARN_ONCE(adev->dev, 1, + "Invalid UVD decoding dimensions (%dx%d)!\n", + width, height); + return -EINVAL; + } + image_size = width * height; image_size += image_size / 2; image_size = ALIGN(image_size, 1024); -- cgit v1.2.3 From dbb02b4755f8c1f3773263f2d779872c1c0c073a Mon Sep 17 00:00:00 2001 From: Boyuan Zhang Date: Thu, 21 May 2026 09:59:37 -0400 Subject: drm/amdgpu/vcn4: avoid rereading IB param length Reuse the parameter length returned by vcn_v4_0_enc_find_ib_param() instead of rereading it from the IB. This avoids a potential TOCTOU issue if the IB contents change between reads. Signed-off-by: Boyuan Zhang Reviewed-by: David Rosca Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c index 4389f8e9e40c..0cce78b205a8 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c @@ -1927,14 +1927,17 @@ out: #define RENCODE_IB_PARAM_SESSION_INIT 0x00000003 /* return the offset in ib if id is found, -1 otherwise */ -static int vcn_v4_0_enc_find_ib_param(struct amdgpu_ib *ib, uint32_t id, int start) +static int vcn_v4_0_enc_find_ib_param(struct amdgpu_ib *ib, uint32_t id, int start, uint32_t *length) { int i; uint32_t len; for (i = start; (len = amdgpu_ib_get_value(ib, i)) >= 8; i += len / 4) { - if (amdgpu_ib_get_value(ib, i + 1) == id) + if (amdgpu_ib_get_value(ib, i + 1) == id) { + if (length) + *length = len; return i; + } } return -1; } @@ -1944,14 +1947,14 @@ static int vcn_v4_0_ring_patch_cs_in_place(struct amdgpu_cs_parser *p, struct amdgpu_ib *ib) { struct amdgpu_ring *ring = amdgpu_job_ring(job); - uint32_t val; + uint32_t val, len; int idx = 0, sidx; /* The first instance can decode anything */ if (!ring->me) return 0; - while ((idx = vcn_v4_0_enc_find_ib_param(ib, RADEON_VCN_ENGINE_INFO, idx)) >= 0) { + while ((idx = vcn_v4_0_enc_find_ib_param(ib, RADEON_VCN_ENGINE_INFO, idx, &len)) >= 0) { val = amdgpu_ib_get_value(ib, idx + 2); /* RADEON_VCN_ENGINE_TYPE */ if (val == RADEON_VCN_ENGINE_TYPE_DECODE) { uint32_t valid_buf_flag = amdgpu_ib_get_value(ib, idx + 6); @@ -1964,12 +1967,12 @@ static int vcn_v4_0_ring_patch_cs_in_place(struct amdgpu_cs_parser *p, amdgpu_ib_get_value(ib, idx + 8); return vcn_v4_0_dec_msg(p, job, msg_buffer_addr); } else if (val == RADEON_VCN_ENGINE_TYPE_ENCODE) { - sidx = vcn_v4_0_enc_find_ib_param(ib, RENCODE_IB_PARAM_SESSION_INIT, idx); + sidx = vcn_v4_0_enc_find_ib_param(ib, RENCODE_IB_PARAM_SESSION_INIT, idx, NULL); if (sidx >= 0 && amdgpu_ib_get_value(ib, sidx + 2) == RENCODE_ENCODE_STANDARD_AV1) return vcn_v4_0_limit_sched(p, job); } - idx += amdgpu_ib_get_value(ib, idx) / 4; + idx += len / 4; } return 0; } -- cgit v1.2.3 From cbe408dba581755ad1279a487ec786d8927d778d Mon Sep 17 00:00:00 2001 From: Boyuan Zhang Date: Mon, 25 May 2026 11:34:27 -0400 Subject: drm/amdgpu/vce: fix integer overflow in image size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a security vulnerability where malicious VCE command streams with oversized dimensions (e.g. 65536×65536) cause 32-bit integer overflow, wrapping the calculated buffer size to 0. This bypasses validation and allows GPU firmware to perform out-of-bound memory access. The fix uses 64-bit arithmetic to detect overflow and rejects invalid dimensions before they reach the hardware. V2: remove redundant check V3: modify max height value V4: remove size64 Signed-off-by: Boyuan Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c index efdebd9c0a1f..eef3c9853a5c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c @@ -877,9 +877,20 @@ int amdgpu_vce_ring_parse_cs(struct amdgpu_cs_parser *p, goto out; } - *size = amdgpu_ib_get_value(ib, idx + 8) * - amdgpu_ib_get_value(ib, idx + 10) * - 8 * 3 / 2; + uint32_t width, height; + width = amdgpu_ib_get_value(ib, idx + 8); + height = amdgpu_ib_get_value(ib, idx + 10); + + if (width == 0 || height == 0 || + width > 4096 || height > 2304) { + DRM_ERROR("invalid VCE image size: %ux%u\n", + width, height); + r = -EINVAL; + goto out; + } + + *size = width * height * 8 * 3 / 2; + break; case 0x04000001: /* config extension */ -- cgit v1.2.3 From c0cae35661868af207077a4306bc42c7c972947c Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Tue, 16 Jun 2026 17:18:59 -0500 Subject: drm/amdkfd: Guard m->cp_hqd_eop_control setting by q->eop_ring_buffer_size To avoid wraparound if the value is 0. Signed-off-by: Xiaogang Chen Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 4 ++-- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 4 ++-- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 4 ++-- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 4 ++-- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c index 8e8ec266ca46..e034da638c07 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c @@ -203,8 +203,8 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * more than (EOP entry count - 1) so a queue size of 0x800 dwords * is safe, giving a maximum field value of 0xA. */ - m->cp_hqd_eop_control = min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1); + m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, + ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c index fff137e00b5e..350fcbbba4b2 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c @@ -241,8 +241,8 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * more than (EOP entry count - 1) so a queue size of 0x800 dwords * is safe, giving a maximum field value of 0xA. */ - m->cp_hqd_eop_control = min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1); + m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, + ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 8c815f129614..7c387fa90076 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -216,8 +216,8 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * more than (EOP entry count - 1) so a queue size of 0x800 dwords * is safe, giving a maximum field value of 0xA. */ - m->cp_hqd_eop_control = min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1); + m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, + ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 475589b924e9..431a940f91f3 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -294,8 +294,8 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * more than (EOP entry count - 1) so a queue size of 0x800 dwords * is safe, giving a maximum field value of 0xA. */ - m->cp_hqd_eop_control = min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1); + m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, + ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c index c86779af323b..60b87a500698 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c @@ -214,8 +214,8 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, * more than (EOP entry count - 1) so a queue size of 0x800 dwords * is safe, giving a maximum field value of 0xA. */ - m->cp_hqd_eop_control |= min(0xA, - order_base_2(q->eop_ring_buffer_size / 4) - 1); + m->cp_hqd_eop_control |= q->eop_ring_buffer_size ? min(0xA, + order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = -- cgit v1.2.3 From 41eb81a30665ece270d677b4ac92cb82047e69bd Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Sat, 20 Jun 2026 23:06:34 +0800 Subject: drm/amdgpu/mes12: drop queue state on RESET_QUEUES unmap Set remove_queue_after_reset=1 (MES >= 0x5a) so MES drops its internal state instead of re-unmapping an already MMIO-reset queue, which can timeout into a GPU reset. Suggested-by: Shaoyun Liu Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index d80a983b1b6c..20f4fd57b1da 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -749,6 +749,17 @@ static int mes_v12_0_unmap_legacy_queue(struct amdgpu_mes *mes, mes_remove_queue_pkt.unmap_legacy_queue = 1; mes_remove_queue_pkt.queue_type = convert_to_mes_queue_type(input->queue_type); + /* + * A reset-time unmap: the queue was already reset via MMIO while + * gangs are suspended and it is on the MES hung/fail list. Tell + * MES to just drop its internal state for it. Without this flag + * MES asks CP to unmap the already-reset (still wedged) queue + * again, which times out and forces a GPU reset. + */ + if (input->action == RESET_QUEUES && + (mes->sched_version & AMDGPU_MES_VERSION_MASK) >= 0x5a) + mes_remove_queue_pkt.remove_queue_after_reset = 1; + } if (mes->adev->enable_uni_mes) { -- cgit v1.2.3 From a36daf95cc8dfa47dd8087b65be62107390a0e36 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Sat, 20 Jun 2026 23:06:35 +0800 Subject: drm/amdkfd: flush MES queue on reset-time queue removal Pass flush_mes_queue=true in reset_queue_mes() to match the GFX post-reset drop semantics. Suggested-by: Shaoyun Liu Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3b1a5a2a37ca..ce28a7c77704 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -427,7 +427,7 @@ static int reset_queue_mes(struct device_queue_manager *dqm, struct queue *q, if (r) return r; /* Proceed remove_queue with reset=true */ - remove_queue_mes_on_reset_option(dqm, q, &pdd->qpd, true, false); + remove_queue_mes_on_reset_option(dqm, q, &pdd->qpd, true, true); set_queue_as_reset(dqm, q, &pdd->qpd); return 0; } -- cgit v1.2.3 From 395e142b43dd2e9bc79de05339eb152fd260c39a Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 22 Jun 2026 10:40:11 +0800 Subject: drm/amdgpu/userq: add reset helper and identify guilty user queue If we get an interrupt for a bad user queue (bad opcode, etc.), add a helper to handle the reset for user queues. v2: squash in fixes v3: - schedule the reset via amdgpu_userq_start_hang_detect_work() instead of open-coding mod_delayed_work() - drop the per-queue guilty flag; always reset the queue the hang detect work belongs to, matching the non-compute reset path Co-developed-by: Alex Deucher Signed-off-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 22 +++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 11 +++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index cd168a51c165..fb4cc6bfb5ac 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -142,7 +142,8 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) int r; if (queue->queue_type == AMDGPU_HW_IP_COMPUTE) - r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, NULL, NULL); + r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, + queue, NULL, NULL); else r = userq_funcs->reset(queue); if (r) @@ -690,6 +691,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) } queue->doorbell_index = index; + queue->doorbell_offset = (u32)args->in.doorbell_offset; trace_amdgpu_userq_create_start(queue); r = uq_funcs->mqd_create(queue, &args->in); if (r) { @@ -1131,6 +1133,24 @@ put_fence: dma_fence_put(ev_fence); } +void amdgpu_userq_process_reset_irq(struct amdgpu_device *adev, + u32 pasid, u32 doorbell_offset) +{ + struct xarray *xa = &adev->userq_doorbell_xa; + struct amdgpu_usermode_queue *queue; + unsigned long flags, idx; + + xa_lock_irqsave(xa, flags); + xa_for_each(xa, idx, queue) { + if (queue->vm && queue->vm->pasid == pasid && + queue->doorbell_offset == doorbell_offset) { + amdgpu_userq_start_hang_detect_work(queue); + break; + } + } + xa_unlock_irqrestore(xa, flags); +} + static int amdgpu_userq_evict_all(struct amdgpu_userq_mgr *uq_mgr) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 7a5f8ed794b8..61e5f8a06eb2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -53,6 +53,7 @@ struct amdgpu_usermode_queue { enum amdgpu_userq_state state; uint64_t doorbell_handle; uint64_t doorbell_index; + u32 doorbell_offset; uint64_t flags; struct amdgpu_mqd_prop *userq_prop; struct amdgpu_userq_mgr *userq_mgr; @@ -178,6 +179,16 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost); void amdgpu_userq_start_hang_detect_work(struct amdgpu_usermode_queue *queue); void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell); +/* + * CP packs the per-process doorbell_id of the queue in + * CTXID0[9:0] on priv-fault (same encoding KFD uses via + * KFD_CTXID0_DOORBELL_ID_MASK) + */ +#define AMDGPU_CTXID0_DOORBELL_ID_MASK 0x3ff + +void amdgpu_userq_process_reset_irq(struct amdgpu_device *adev, + u32 pasid, u32 doorbell_offset); + int amdgpu_userq_input_va_validate(struct amdgpu_device *adev, struct amdgpu_usermode_queue *queue, u64 addr, u64 expected_size, u64 *va_out); -- cgit v1.2.3 From 23d9db57f35d88f29d52c381df47b02ed0b4a0a1 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 9 Jun 2026 10:00:48 +0800 Subject: drm/amdgpu/gfx11: handle error interrupts for userqs Call the new userq reset helper, and dispatch KQs first by ring_id before falling back to the user-queue lookup. v2: squash in fixes Co-developed-by: Alex Deucher Signed-off-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 4cd6e8bfd4c9..30cead1f69d8 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6654,22 +6654,29 @@ static int gfx_v11_0_set_priv_inst_fault_state(struct amdgpu_device *adev, static void gfx_v11_0_handle_priv_fault(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { - u8 me_id, pipe_id, queue_id; - struct amdgpu_ring *ring; - int i; - - me_id = (entry->ring_id & 0x0c) >> 2; - pipe_id = (entry->ring_id & 0x03) >> 0; - queue_id = (entry->ring_id & 0x70) >> 4; + u32 doorbell_offset = entry->src_data[0] & AMDGPU_CTXID0_DOORBELL_ID_MASK; + /* + * Try KQ first by ring_id (HW slot is authoritative). The + * KMD compute_hqd_mask contract guarantees KCQ and user queues + * never share a HW slot. + */ if (!adev->gfx.disable_kq) { + u8 me_id = (entry->ring_id & 0x0c) >> 2; + u8 pipe_id = (entry->ring_id & 0x03) >> 0; + u8 queue_id = (entry->ring_id & 0x70) >> 4; + struct amdgpu_ring *ring; + int i; + switch (me_id) { case 0: for (i = 0; i < adev->gfx.num_gfx_rings; i++) { ring = &adev->gfx.gfx_ring[i]; if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) + ring->queue == queue_id) { drm_sched_fault(&ring->sched); + return; + } } break; case 1: @@ -6677,8 +6684,10 @@ static void gfx_v11_0_handle_priv_fault(struct amdgpu_device *adev, for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) + ring->queue == queue_id) { drm_sched_fault(&ring->sched); + return; + } } break; default: @@ -6686,6 +6695,11 @@ static void gfx_v11_0_handle_priv_fault(struct amdgpu_device *adev, break; } } + + /* No KQ matched: HW slot is a MES-scheduled user queue. */ + if (adev->enable_mes && doorbell_offset) + amdgpu_userq_process_reset_irq(adev, entry->pasid, + doorbell_offset); } static int gfx_v11_0_priv_reg_irq(struct amdgpu_device *adev, -- cgit v1.2.3 From 36b6c723d82c07dbbeae95d5883d4ecf0a643727 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Sat, 20 Jun 2026 23:06:35 +0800 Subject: drm/amdgpu: defer KCQ remap until after MES resume in reset flow Split amdgpu_gfx_mes_reset_queue_start() into reset+unmap now and queue reinit later, and do the remap only after amdgpu_mes_resume(). Avoids re-adding legacy queues while MES gangs are still suspended. Suggested-by: Shaoyun Liu Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 70 +++++++++++++++++++++++++-------- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 1 + 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 982b41606d48..a5b835d0c166 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -1989,10 +1989,24 @@ static ssize_t amdgpu_gfx_get_compute_reset_mask(struct device *dev, return amdgpu_show_reset_mask(buf, adev->gfx.compute_supported_reset); } +static int amdgpu_gfx_mes_reset_queue_reinit(struct amdgpu_ring *ring) +{ + struct amdgpu_device *adev = ring->adev; + int r; + + amdgpu_gfx_mqd_reset_restore(ring); + + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); + if (r) + dev_err(adev->dev, "failed to remap kgq\n"); + + return r; +} + static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence, - bool use_mmio) + bool use_mmio, bool *need_reinit) { struct amdgpu_device *adev = ring->adev; bool reinit_queue; @@ -2007,6 +2021,9 @@ static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, else reinit_queue = use_mmio; + if (need_reinit) + *need_reinit = false; + amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); @@ -2018,13 +2035,9 @@ static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, RESET_QUEUES, 0, 0, 0); if (r) return r; - amdgpu_gfx_mqd_reset_restore(ring); - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) { - dev_err(adev->dev, "failed to remap kgq\n"); - return r; - } + if (need_reinit) + *need_reinit = true; } return 0; } @@ -2034,12 +2047,19 @@ int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence, bool use_mmio) { + bool need_reinit; int r; + /* Single-queue reset (no suspend/resume): re-add the queue inline. */ r = amdgpu_gfx_mes_reset_queue_start(ring, vmid, timedout_fence, - use_mmio); + use_mmio, &need_reinit); if (r) return r; + if (need_reinit) { + r = amdgpu_gfx_mes_reset_queue_reinit(ring); + if (r) + return r; + } return amdgpu_ring_reset_helper_end(ring, timedout_fence); } @@ -2239,7 +2259,8 @@ static int amdgpu_gfx_reset_mes_kcq(struct amdgpu_device *adev, struct amdgpu_ring *guilty_ring, unsigned int db, struct amdgpu_ring **out_ring, - struct amdgpu_fence **out_fence) + struct amdgpu_fence **out_fence, + bool *out_reinit) { bool use_mmio = adev->gfx.mec.use_mmio_for_reset; struct amdgpu_fence *fence; @@ -2248,14 +2269,16 @@ static int amdgpu_gfx_reset_mes_kcq(struct amdgpu_device *adev, *out_ring = NULL; *out_fence = NULL; + *out_reinit = false; for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; if (ring == guilty_ring) continue; if (ring->doorbell_index == db) { fence = amdgpu_ring_find_guilty_fence(ring); + /* reset + unmap now; re-add (map) is deferred to after resume */ r = amdgpu_gfx_mes_reset_queue_start(ring, 0, fence, - use_mmio); + use_mmio, out_reinit); if (r) return r; *out_ring = ring; @@ -2306,12 +2329,16 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, fence_reset: /* reset the queue this came from if specified */ if (ring) { + bool reinit = false; + + /* reset + unmap now; re-add (map) is deferred to after resume */ r = amdgpu_gfx_mes_reset_queue_start(ring, 0, guilty_fence, - use_mmio); + use_mmio, &reinit); if (r) goto out; deferred_end[n_deferred].ring = ring; deferred_end[n_deferred].fence = guilty_fence; + deferred_end[n_deferred].reinit = reinit; n_deferred++; } if (uq) { @@ -2322,6 +2349,7 @@ fence_reset: for (i = 0; i < num_hung; i++) { struct amdgpu_ring *hr = NULL; struct amdgpu_fence *hf = NULL; + bool hr_reinit = false; pipe = hqd_info[i].pipe_index; queue = hqd_info[i].queue_index; @@ -2330,12 +2358,13 @@ fence_reset: /* reset any KCQs */ r = amdgpu_gfx_reset_mes_kcq(adev, ring, adev->gfx.mec.mes_hung_db_array[i], - &hr, &hf); + &hr, &hf, &hr_reinit); if (r) goto out; if (hr) { deferred_end[n_deferred].ring = hr; deferred_end[n_deferred].fence = hf; + deferred_end[n_deferred].reinit = hr_reinit; n_deferred++; } /* reset any KFD queues */ @@ -2372,12 +2401,21 @@ out: /* resume all will enable the non-hung queues */ amdgpu_mes_resume(adev, 0); - /* Now CP is running again — replay backed-up commands and ring - * doorbells on each reset queue. + /* Now CP is running again — for queues that were unmapped during the + * reset, re-add (map) them only now that MES is resumed and back to a + * normal state, then replay backed-up commands and ring doorbells on + * each reset queue. */ for (i = 0; i < n_deferred; i++) { - int er = amdgpu_ring_reset_helper_end(deferred_end[i].ring, - deferred_end[i].fence); + int er; + + if (deferred_end[i].reinit) { + er = amdgpu_gfx_mes_reset_queue_reinit(deferred_end[i].ring); + if (er && !r) + r = er; + } + er = amdgpu_ring_reset_helper_end(deferred_end[i].ring, + deferred_end[i].fence); if (er && !r) r = er; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index aefd4f03b443..9432107c96a1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -550,6 +550,7 @@ struct amdgpu_gfx { struct amdgpu_gfx_deferred_entry { struct amdgpu_ring *ring; struct amdgpu_fence *fence; + bool reinit; }; struct amdgpu_gfx_ras_reg_entry { -- cgit v1.2.3 From 98c692c5c41faff8da512bc2969e52d40abfd42a Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 27 May 2026 14:39:47 -0400 Subject: drm/amd/display: Add dcn42b_soc_and_ip_translator [why] DCN42B was not using its own max_ip_caps table. Need to create a separate soc_and_ip_translator in order to not reuse the DCN42 one. [how] Separate DCN42B into its own soc_and_ip_translator.c file to handle this. Reviewed-by: Dillon Varone Signed-off-by: Matthew Stewart Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 38 ++++++++++++++++++++ .../amd/display/dc/soc_and_ip_translator/Makefile | 3 ++ .../dcn42/dcn42_soc_and_ip_translator.c | 18 ++++------ .../dcn42/dcn42_soc_and_ip_translator.h | 1 + .../dcn42b/dcn42b_soc_and_ip_translator.c | 42 ++++++++++++++++++++++ .../dcn42b/dcn42b_soc_and_ip_translator.h | 17 +++++++++ .../soc_and_ip_translator/soc_and_ip_translator.c | 5 ++- 7 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.c create mode 100644 drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.h 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 eae4a37b0984..60ef56419846 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 @@ -224,4 +224,42 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = { .max_fclk_for_uclk_dpm_khz = 2200 * 1000, }; +static const struct dml2_ip_capabilities dml2_dcn42b_max_ip_caps = { + .pipe_count = 4, + .otg_count = 3, + .num_dsc = 3, + .max_num_dp2p0_streams = 3, + .max_num_hdmi_frl_outputs = 0, + .max_num_dp2p0_outputs = 2, + .rob_buffer_size_kbytes = 64, + .config_return_buffer_size_in_kbytes = 1792, + .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, + .max_flip_time_us = 110, + .max_flip_time_lines = 50, + .hostvm_mode = 0, + .subvp_drr_scheduling_margin_us = 100, + .subvp_prefetch_end_to_mall_start_us = 15, + .subvp_fw_processing_delay = 15, + .max_vactive_det_fill_delay_us = 400, + + .fams2 = { + .max_allow_delay_us = 100 * 1000, + .scheduling_delay_us = 550, + .vertical_interrupt_ack_delay_us = 40, + .allow_programming_delay_us = 18, + .min_allow_width_us = 20, + .subvp_df_throttle_delay_us = 100, + .subvp_programming_delay_us = 200, + .subvp_prefetch_to_mall_delay_us = 18, + .drr_programming_delay_us = 35, + + .lock_timeout_us = 5000, + .recovery_timeout_us = 5000, + .flip_programming_delay_us = 300, + }, +}; + #endif diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/Makefile b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/Makefile index d168fb1eacf7..8a9bb0aef9b7 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/Makefile +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/Makefile @@ -9,13 +9,16 @@ soc_and_ip_translator_rcflags := $(CC_FLAGS_NO_FPU) CFLAGS_$(AMDDALPATH)/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.o := $(soc_and_ip_translator_ccflags) CFLAGS_$(AMDDALPATH)/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.o := $(soc_and_ip_translator_ccflags) +CFLAGS_$(AMDDALPATH)/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.o := $(soc_and_ip_translator_ccflags) CFLAGS_REMOVE_$(AMDDALPATH)/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.o := $(soc_and_ip_translator_rcflags) CFLAGS_REMOVE_$(AMDDALPATH)/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.o := $(soc_and_ip_translator_rcflags) +CFLAGS_REMOVE_$(AMDDALPATH)/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.o := $(soc_and_ip_translator_rcflags) soc_and_ip_translator := soc_and_ip_translator.o soc_and_ip_translator += dcn401/dcn401_soc_and_ip_translator.o soc_and_ip_translator += dcn42/dcn42_soc_and_ip_translator.o +soc_and_ip_translator += dcn42b/dcn42b_soc_and_ip_translator.o AMD_DAL_soc_and_ip_translator := $(addprefix $(AMDDALPATH)/dc/soc_and_ip_translator/, $(soc_and_ip_translator)) diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.c index ae2c6a2f3f75..c6c1b19b7370 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.c @@ -5,22 +5,16 @@ #include "dcn42_soc_and_ip_translator.h" #include "../dcn401/dcn401_soc_and_ip_translator.h" #include "bounding_boxes/dcn42_soc_bb.h" -#include "bounding_boxes/dcn42b_soc_bb.h" /* soc_and_ip_translator component used to get up-to-date values for bounding box. * Bounding box values are stored in several locations and locations can vary with DCN revision. * This component provides an interface to get DCN-specific bounding box values. */ -static void get_default_soc_bb(struct dml2_soc_bb *soc_bb, const struct dc *dc) +static void get_default_soc_bb(struct dml2_soc_bb *soc_bb) { - if (dc->ctx->dce_version == DCN_VERSION_4_2B) { - memcpy(soc_bb, &dml2_socbb_dcn42b, sizeof(struct dml2_soc_bb)); - memcpy(&soc_bb->qos_parameters, &dml_dcn42b_variant_a_soc_qos_params, sizeof(struct dml2_soc_qos_parameters)); - } else { - memcpy(soc_bb, &dml2_socbb_dcn42, sizeof(struct dml2_soc_bb)); - memcpy(&soc_bb->qos_parameters, &dml_dcn42_variant_a_soc_qos_params, sizeof(struct dml2_soc_qos_parameters)); - } + memcpy(soc_bb, &dml2_socbb_dcn42, sizeof(struct dml2_soc_bb)); + memcpy(&soc_bb->qos_parameters, &dml_dcn42_variant_a_soc_qos_params, sizeof(struct dml2_soc_qos_parameters)); } /* @@ -165,7 +159,7 @@ static void dcn42_update_soc_bb_with_values_from_clk_mgr(struct dml2_soc_bb *soc } } -static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) +void dcn42_apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) { (void)config; /* Individual modification can be overwritten even if it was obtained by a previous function. @@ -181,9 +175,9 @@ static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc void dcn42_get_soc_bb(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) { //get default soc_bb with static values - get_default_soc_bb(soc_bb, dc); + get_default_soc_bb(soc_bb); //update soc_bb values with more accurate values - apply_soc_bb_updates(soc_bb, dc, config); + dcn42_apply_soc_bb_updates(soc_bb, dc, config); } static void dcn42_get_ip_caps(struct dml2_ip_capabilities *ip_caps) diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.h b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.h index 1dded5426152..8ac90655f276 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.h +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.h @@ -13,5 +13,6 @@ void dcn42_construct_soc_and_ip_translator(struct soc_and_ip_translator *soc_and_ip_translator); void dcn42_get_soc_bb(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config); +void dcn42_apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config); #endif /* _DCN42_SOC_AND_IP_TRANSLATOR_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.c new file mode 100644 index 000000000000..50669f458e23 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +// +// Copyright 2026 Advanced Micro Devices, Inc. + +#include "../dcn42/dcn42_soc_and_ip_translator.h" +#include "dcn42b_soc_and_ip_translator.h" +#include "../dcn401/dcn401_soc_and_ip_translator.h" +#include "bounding_boxes/dcn42b_soc_bb.h" + +/* soc_and_ip_translator component used to get up-to-date values for bounding box. + * Bounding box values are stored in several locations and locations can vary with DCN revision. + * This component provides an interface to get DCN-specific bounding box values. + */ + +static void get_default_soc_bb(struct dml2_soc_bb *soc_bb) +{ + memcpy(soc_bb, &dml2_socbb_dcn42b, sizeof(struct dml2_soc_bb)); + memcpy(&soc_bb->qos_parameters, &dml_dcn42b_variant_a_soc_qos_params, sizeof(struct dml2_soc_qos_parameters)); +} + +void dcn42b_get_soc_bb(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) +{ + //get default soc_bb with static values + get_default_soc_bb(soc_bb); + //update soc_bb values with more accurate values + dcn42_apply_soc_bb_updates(soc_bb, dc, config); +} + +static void dcn42b_get_ip_caps(struct dml2_ip_capabilities *ip_caps) +{ + *ip_caps = dml2_dcn42b_max_ip_caps; +} + +static struct soc_and_ip_translator_funcs dcn42b_translator_funcs = { + .get_soc_bb = dcn42b_get_soc_bb, + .get_ip_caps = dcn42b_get_ip_caps, +}; + +void dcn42b_construct_soc_and_ip_translator(struct soc_and_ip_translator *soc_and_ip_translator) +{ + soc_and_ip_translator->translator_funcs = &dcn42b_translator_funcs; +} diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.h b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.h new file mode 100644 index 000000000000..0d4ea613431a --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// +// Copyright 2026 Advanced Micro Devices, Inc. + +#ifndef _DCN42B_SOC_AND_IP_TRANSLATOR_H_ +#define _DCN42B_SOC_AND_IP_TRANSLATOR_H_ + +#include "core_types.h" +#include "dc.h" +#include "clk_mgr.h" +#include "dml_top_soc_parameter_types.h" +#include "soc_and_ip_translator.h" + +void dcn42b_construct_soc_and_ip_translator(struct soc_and_ip_translator *soc_and_ip_translator); +void dcn42b_get_soc_bb(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config); + +#endif /* _DCN42B_SOC_AND_IP_TRANSLATOR_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/soc_and_ip_translator.c index f99afb22d7da..1e3ee25732fa 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/soc_and_ip_translator.c @@ -5,6 +5,7 @@ #include "soc_and_ip_translator.h" #include "soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.h" #include "soc_and_ip_translator/dcn42/dcn42_soc_and_ip_translator.h" +#include "soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.h" static void dc_construct_soc_and_ip_translator(struct soc_and_ip_translator *soc_and_ip_translator, enum dce_version dc_version) @@ -14,9 +15,11 @@ static void dc_construct_soc_and_ip_translator(struct soc_and_ip_translator *soc dcn401_construct_soc_and_ip_translator(soc_and_ip_translator); break; case DCN_VERSION_4_2: - case DCN_VERSION_4_2B: dcn42_construct_soc_and_ip_translator(soc_and_ip_translator); break; + case DCN_VERSION_4_2B: + dcn42b_construct_soc_and_ip_translator(soc_and_ip_translator); + break; default: break; } -- cgit v1.2.3 From 68737239e8913b09a87ffad4a26926db91ba03b0 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Fri, 5 Jun 2026 16:21:29 -0400 Subject: drm/amd/display: Enable PSR and Replay on DCN4 variant and fix AUX instance [Why] Enable PSR and Panel Replay on a DCN4 variant for display power savings. On links without native I2C (no DDC pin), the AUX channel must use aux_hw_inst to avoid NULL pointer access during PSR and Replay setup. [How] Enable PSR and Replay in the DCN4 variant panel config defaults. Add no_ddc_pin check in dp_setup_panel_replay(), edp_setup_freesync_replay(), and fsft_send_msg_to_fw() to use link->aux_hw_inst when dp_connector_no_native_i2c and no_ddc_pin are set. Reviewed-by: Matthew Stewart Signed-off-by: Gabe Teeger Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/link/protocols/link_dp_panel_replay.c | 6 +++++- .../amd/display/dc/link/protocols/link_edp_panel_control.c | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c index d87f87a02d63..465b9e53d311 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c @@ -119,7 +119,11 @@ static bool dp_setup_panel_replay(struct dc_link *link, const struct dc_stream_s if (!dp_pr_get_panel_inst(dc, link, &panel_inst)) return false; - replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; + if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { + replay_context.aux_inst = (enum channel_id) link->aux_hw_inst; + } else { + replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; + } replay_context.digbe_inst = link->link_enc->transmitter; replay_context.digfe_inst = link->link_enc->preferred_engine; diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c index 80a372ceaa51..1fda6e226e23 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c @@ -788,10 +788,11 @@ bool edp_setup_psr(struct dc_link *link, } } - if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) + if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { psr_context->channel = (enum channel_id)link->aux_hw_inst; - else + } else { psr_context->channel = link->ddc->ddc_pin->hw_info.ddc_channel; + } psr_context->transmitterId = link->link_enc->transmitter; psr_context->engineId = link->link_enc->preferred_engine; @@ -1024,7 +1025,11 @@ bool edp_setup_freesync_replay(struct dc_link *link, const struct dc_stream_stat if (!dp_pr_get_panel_inst(dc, link, &panel_inst)) return false; - replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; + if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { + replay_context.aux_inst = (enum channel_id) link->aux_hw_inst; + } else { + replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; + } replay_context.digbe_inst = link->link_enc->transmitter; replay_context.digfe_inst = link->link_enc->preferred_engine; -- cgit v1.2.3 From 241ad982e181fce13de86be721181b41d6506e73 Mon Sep 17 00:00:00 2001 From: Piotr Maziarz Date: Fri, 22 May 2026 16:27:11 +0200 Subject: drm/amd/display: Fix 4018 warning [Why] It is required by Security Guidance for All Software Components. [How] Change variable type to unsigned in dc\dml\dcn314\display_mode_vba_31.c and dc\dml\dcn31\display_mode_vba_314.c. Explicit cast to unsigned in dc\link\protocols\link_hdmi_frl.c. Move warning from UNSOLVED set to SOLVED set. Reviewed-by: Dillon Varone Signed-off-by: Piotr Maziarz Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 11 +++++------ .../gpu/drm/amd/display/dc/dml/dcn31/display_mode_vba_31.c | 2 +- .../gpu/drm/amd/display/dc/dml/dcn314/display_mode_vba_314.c | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) 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 5f6cc1b1f788..b21d41df0fab 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -4412,14 +4412,13 @@ enum dc_status dc_validate_with_context(struct dc *dc, struct dc_stream_state *unchanged_streams[MAX_PIPES] = { 0 }; struct dc_stream_state *del_streams[MAX_PIPES] = { 0 }; struct dc_stream_state *add_streams[MAX_PIPES] = { 0 }; - int old_stream_count = context->stream_count; + unsigned int old_stream_count = context->stream_count; enum dc_status res = DC_ERROR_UNEXPECTED; - int unchanged_streams_count = 0; - int del_streams_count = 0; - int add_streams_count = 0; + unsigned int unchanged_streams_count = 0; + unsigned int del_streams_count = 0; + unsigned int add_streams_count = 0; bool found = false; - int i, j; - unsigned int k; + unsigned int i, j, k; DC_LOGGER_INIT(dc->ctx->logger); diff --git a/drivers/gpu/drm/amd/display/dc/dml/dcn31/display_mode_vba_31.c b/drivers/gpu/drm/amd/display/dc/dml/dcn31/display_mode_vba_31.c index bd14ebea1111..8064c4b3fd25 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/dcn31/display_mode_vba_31.c +++ b/drivers/gpu/drm/amd/display/dc/dml/dcn31/display_mode_vba_31.c @@ -5337,7 +5337,7 @@ void dml31_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_l for (j = 0; j <= 1; ++j) { double VMDataOnlyReturnBWPerState; double HostVMInefficiencyFactor = 1; - int NextPrefetchModeState = MinPrefetchMode; + unsigned int NextPrefetchModeState = MinPrefetchMode; bool UnboundedRequestEnabledThisState = false; unsigned int CompressedBufferSizeInkByteThisState = 0; double dummy; diff --git a/drivers/gpu/drm/amd/display/dc/dml/dcn314/display_mode_vba_314.c b/drivers/gpu/drm/amd/display/dc/dml/dcn314/display_mode_vba_314.c index 2ea5cf37f273..bf2dde26b98b 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/dcn314/display_mode_vba_314.c +++ b/drivers/gpu/drm/amd/display/dc/dml/dcn314/display_mode_vba_314.c @@ -5421,7 +5421,7 @@ void dml314_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_ for (j = 0; j <= 1; ++j) { double VMDataOnlyReturnBWPerState; double HostVMInefficiencyFactor = 1; - int NextPrefetchModeState = MinPrefetchMode; + unsigned int NextPrefetchModeState = MinPrefetchMode; bool UnboundedRequestEnabledThisState = false; unsigned int CompressedBufferSizeInkByteThisState = 0; double dummy; -- cgit v1.2.3 From adda7c46500a57b84bd9cbc9d94d8e8ab71ad724 Mon Sep 17 00:00:00 2001 From: Lohita Mudimela Date: Mon, 25 May 2026 14:24:06 +0530 Subject: drm/amd/display: Integrate power_helpers.c functionality into power.c. [Why] Reduces file fragmentation in the power module by consolidating power_helpers.c . The helper file contained minimal functionality (single utility function and shared includes) that didn't warrant a separate compilation unit, leading to increased build complexity and maintenance overhead. [How] Consolidated power_helpers.c content into the internal module implementation. Moved macro outside platform-specific conditional block for wider availability. Reviewed-by: Josip Pavic Signed-off-by: Lohita Mudimela Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/modules/power/Makefile | 2 +- drivers/gpu/drm/amd/display/modules/power/power.c | 5 +++ .../drm/amd/display/modules/power/power_helpers.c | 39 ---------------------- 3 files changed, 6 insertions(+), 40 deletions(-) delete mode 100644 drivers/gpu/drm/amd/display/modules/power/power_helpers.c diff --git a/drivers/gpu/drm/amd/display/modules/power/Makefile b/drivers/gpu/drm/amd/display/modules/power/Makefile index 3000f392bdbc..0746f671eb4d 100644 --- a/drivers/gpu/drm/amd/display/modules/power/Makefile +++ b/drivers/gpu/drm/amd/display/modules/power/Makefile @@ -23,7 +23,7 @@ # Makefile for the 'power' sub-module of DAL. # -MOD_POWER = power_helpers.o power.o power_abm.o power_psr.o power_replay.o +MOD_POWER = power.o power_abm.o power_psr.o power_replay.o AMD_DAL_MOD_POWER = $(addprefix $(AMDDALPATH)/modules/power/,$(MOD_POWER)) #$(info ************ DAL POWER MODULE MAKEFILE ************) diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index 5659a38b3366..af6b162a337d 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -501,3 +501,8 @@ bool mod_power_notify_mode_change(struct mod_power *mod_power, return true; } + +bool mod_power_only_edp(const struct dc_state *context, const struct dc_stream_state *stream) +{ + return context && context->stream_count == 1 && dc_is_embedded_signal(stream->signal); +} diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.c b/drivers/gpu/drm/amd/display/modules/power/power_helpers.c deleted file mode 100644 index bf0c5901b4ee..000000000000 --- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.c +++ /dev/null @@ -1,39 +0,0 @@ -/* Copyright 2018 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 "power_helpers.h" -#include "dc/inc/hw/dmcu.h" -#include "dc/inc/hw/abm.h" -#include "dc.h" -#include "core_types.h" -#include "dmub_cmd.h" - -#define DIV_ROUNDUP(a, b) (((a)+((b)/2))/(b)) -#define bswap16_based_on_endian(big_endian, value) \ - ((big_endian) ? cpu_to_be16(value) : cpu_to_le16(value)) - -bool mod_power_only_edp(const struct dc_state *context, const struct dc_stream_state *stream) -{ - return context && context->stream_count == 1 && dc_is_embedded_signal(stream->signal); -} -- cgit v1.2.3 From d613cf73a97c396a2a8ba2badd48cc27af38a265 Mon Sep 17 00:00:00 2001 From: Michael Strauss Date: Thu, 19 Feb 2026 11:15:24 -0500 Subject: drm/amd/display: Add 12bpc Color Ramp Support [WHY] 12bpc color ramp pattern was never implemented. [HOW] Add correct DPG_RAMP_CONTROL programming to match DP color ramp spec. Reviewed-by: George Shen Signed-off-by: Michael Strauss Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c | 33 ++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c b/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c index 83730bbe26a8..50b6973ef123 100644 --- a/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c +++ b/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c @@ -149,6 +149,9 @@ void opp2_set_disp_pattern_generator( case TEST_PATTERN_COLOR_FORMAT_BPC_10: dst_bpc = 10; break; + case TEST_PATTERN_COLOR_FORMAT_BPC_12: + dst_bpc = 12; + break; default: dst_bpc = 8; break; @@ -192,22 +195,25 @@ void opp2_set_disp_pattern_generator( case CONTROLLER_DP_TEST_PATTERN_COLORRAMP: { - mode = (bit_depth == - TEST_PATTERN_COLOR_FORMAT_BPC_10 ? - TEST_PATTERN_MODE_DUALRAMP_RGB : - TEST_PATTERN_MODE_SINGLERAMP_RGB); - switch (bit_depth) { case TEST_PATTERN_COLOR_FORMAT_BPC_6: + mode = TEST_PATTERN_MODE_SINGLERAMP_RGB; dst_bpc = 6; break; case TEST_PATTERN_COLOR_FORMAT_BPC_8: + mode = TEST_PATTERN_MODE_SINGLERAMP_RGB; dst_bpc = 8; break; case TEST_PATTERN_COLOR_FORMAT_BPC_10: + mode = TEST_PATTERN_MODE_DUALRAMP_RGB; dst_bpc = 10; break; + case TEST_PATTERN_COLOR_FORMAT_BPC_12: + mode = TEST_PATTERN_MODE_DUALRAMP_RGB; + dst_bpc = 12; + break; default: + mode = TEST_PATTERN_MODE_SINGLERAMP_RGB; dst_bpc = 8; break; } @@ -244,9 +250,20 @@ void opp2_set_disp_pattern_generator( case TEST_PATTERN_COLOR_FORMAT_BPC_10: { REG_SET_3(DPG_RAMP_CONTROL, 0, - DPG_RAMP0_OFFSET, 384 << 6, - DPG_INC0, inc_base, - DPG_INC1, inc_base + 2); + DPG_RAMP0_OFFSET, 384 << inc_base, // 384 start point + DPG_INC0, inc_base, // step size of 1 + DPG_INC1, inc_base + 2); // step size of 4 (1 << 2) + REG_UPDATE_2(DPG_CONTROL, + DPG_VRES, 5, + DPG_HRES, 8); + } + break; + case TEST_PATTERN_COLOR_FORMAT_BPC_12: + { + REG_SET_3(DPG_RAMP_CONTROL, 0, + DPG_RAMP0_OFFSET, 1920 << inc_base, // 1920 start point + DPG_INC0, inc_base, // step size of 1 + DPG_INC1, inc_base + 4); // step size of 16 (1 << 4) REG_UPDATE_2(DPG_CONTROL, DPG_VRES, 5, DPG_HRES, 8); -- cgit v1.2.3 From c953b39f94873df5b11110b3b7a1042171f5f000 Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Thu, 11 Jun 2026 10:18:24 -0400 Subject: drm/amd/display: Reintroduce "Force validation link training on all ASICs" [Why & How] 'skip_frl_pretraining' was introduced and enabled along w/ HDMI 2.1 initial upstream, but is causing HDMI validation link training to be s kipped on short hotplugs and compliance issues. Remove this behaviour to force link training on all hotplugs for all ASICs. Reviewed-by: Relja (Reggie) Vojvodic Reviewed-by: Sun peng (Leo) Li Signed-off-by: Ivan Lipski Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 1 - drivers/gpu/drm/amd/display/dc/dc_types.h | 1 + drivers/gpu/drm/amd/display/dc/link/link_detection.c | 2 +- drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c | 1 - drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c | 1 - 14 files changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 2de0f9cf8264..b21fdea5fca3 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -591,7 +591,6 @@ struct dc_config { bool enable_mipi_converter_optimization; bool enable_frl; bool force_hdmi21_frl_enc_enable; - bool skip_frl_pretraining; bool use_default_clock_table; bool force_bios_enable_lttpr; uint8_t force_bios_fixed_vs; diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index db6a89d938b6..90dd1ae7e953 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -183,6 +183,7 @@ struct dc_panel_patch { unsigned int force_frl; unsigned int vsdb_rcc_wa; unsigned int delay_hdmi_link_training; + unsigned int skip_frl_pre_training; unsigned int skip_avmute; unsigned int skip_audio_sab_check; unsigned int mst_start_top_delay; diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index a3212fd151d1..24b191d39777 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -933,7 +933,7 @@ static bool should_verify_link_capability_destructively(struct dc_link *link, destrictive = true; if (is_hdmi_frl_in_use(link)) { destrictive = false; - } else if (link->dc->config.skip_frl_pretraining) { + } else if (link->local_sink->edid_caps.panel_patch.skip_frl_pre_training) { for (i = 0; i < MAX_PIPES; i++) { if (pipes[i].stream != NULL && pipes[i].stream->link == link) { 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 01770df63d0e..d11ab57afcdd 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 @@ -2479,7 +2479,6 @@ static bool dcn30_resource_construct( dc->caps.post_blend_color_processing = true; dc->caps.force_dp_tps4_for_cp2520 = true; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.extended_aux_timeout_support = true; dc->caps.dmcub_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 58add1071bc1..ae8918a4ad3e 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 @@ -1378,7 +1378,6 @@ static bool dcn302_resource_construct( dc->caps.post_blend_color_processing = true; dc->caps.force_dp_tps4_for_cp2520 = true; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.extended_aux_timeout_support = true; dc->caps.dmcub_support = true; dc->caps.max_v_total = (1 << 15) - 1; 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 6cb297cd08fd..75e6f4e46f60 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 @@ -1322,7 +1322,6 @@ static bool dcn303_resource_construct( dc->caps.post_blend_color_processing = true; dc->caps.force_dp_tps4_for_cp2520 = true; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.extended_aux_timeout_support = true; dc->caps.dmcub_support = true; dc->caps.max_v_total = (1 << 15) - 1; 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 7c6a6872688b..02bf6f1f3100 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 @@ -2077,7 +2077,6 @@ static bool dcn31_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; dc->caps.edp_dsc_support = true; 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 3b2e57c6970f..ca458f30e45c 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 @@ -2053,7 +2053,6 @@ static bool dcn315_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; dc->caps.edp_dsc_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 924b167bcd74..560a53de22fc 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 @@ -1927,7 +1927,6 @@ static bool dcn316_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; dc->caps.edp_dsc_support = true; 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 a11110e304fc..004c5690f876 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 @@ -2405,7 +2405,6 @@ static bool dcn32_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; dc->caps.edp_dsc_support = 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 d1dbcc8ddb71..53fd32249310 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 @@ -1897,7 +1897,6 @@ static bool dcn321_resource_construct( dc->caps.post_blend_color_processing = true; dc->caps.force_dp_tps4_for_cp2520 = true; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; dc->caps.edp_dsc_support = 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 a5ed62db1de8..efed9317f3ff 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 @@ -2028,7 +2028,6 @@ static bool dcn35_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; 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 9c1d65c2d4ab..079b4f735ab3 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 @@ -2001,7 +2001,6 @@ static bool dcn351_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; 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 8041e035f226..a293e05f8085 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 @@ -1998,7 +1998,6 @@ static bool dcn36_resource_construct( if (dc->config.forceHBR2CP2520) dc->caps.force_dp_tps4_for_cp2520 = false; dc->caps.hdmi_hpo = true; - dc->config.skip_frl_pretraining = true; dc->caps.dp_hpo = true; dc->caps.dp_hdmi21_pcon_support = true; -- cgit v1.2.3 From 64142f9d51aff32f4130d916cb8f044a072ad27d Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Thu, 4 Jun 2026 11:36:09 -0400 Subject: drm/amd/display: Fix DCN42 null registers & register masks [why] The register lists used on DCN42 variants are different. Some reused codepaths are trying to access registers not used. [how] Add DISPCLK_FREQ_CHANGECNTL, HUBPREQ_DEBUG, and HDMISTREAMCLK_CNTL to the register lists. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h index 2076565b1caa..d45e3af77aad 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h @@ -46,6 +46,7 @@ DCCG_SF(DISPCLK_FREQ_CHANGE_CNTL, DCCG_FIFO_ERRDET_STATE, mask_sh),\ DCCG_SF(DISPCLK_FREQ_CHANGE_CNTL, DCCG_FIFO_ERRDET_OVR_EN, mask_sh),\ DCCG_SF(DISPCLK_FREQ_CHANGE_CNTL, DISPCLK_CHG_FWD_CORR_DISABLE, mask_sh),\ + DCCG_SF(DISPCLK_FREQ_CHANGE_CNTL, RESYNC_FIFO_LEVEL_ADJUST_EN, mask_sh),\ DCCG_SF(DPPCLK0_DTO_PARAM, DPPCLK0_DTO_PHASE, mask_sh),\ DCCG_SF(DPPCLK0_DTO_PARAM, DPPCLK0_DTO_MODULO, mask_sh),\ DCCG_SF(HDMICHARCLK0_CLOCK_CNTL, HDMICHARCLK0_EN, mask_sh),\ @@ -239,8 +240,7 @@ DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_CLOCK_ENABLE, mask_sh),\ DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_FE_EN, mask_sh),\ - DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_FE_SRC_SEL, mask_sh),\ - DCCG_SF(DISPCLK_FREQ_CHANGE_CNTL, RESYNC_FIFO_LEVEL_ADJUST_EN, mask_sh) + DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_FE_SRC_SEL, mask_sh) void dccg42_otg_add_pixel(struct dccg *dccg, -- cgit v1.2.3 From 46fda8bda6f93a38db8ea8cca6d84eae304deff3 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Mon, 8 Jun 2026 11:22:02 -0400 Subject: drm/amd/display: Rewrite dccg42_init [why] DCN42 reuses dccg42_init, which causes problems due to undefined masks. [how] - Read res_pool to determine the quantities of the respective resources - Remove the physymclk root_clock_optimization check, as it seems like it shouldn't do anything (defaults to disabled already). Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 29 +++++++--------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index adc453c81831..8989761c6078 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -269,37 +269,26 @@ void dccg42_trigger_dio_fifo_resync(struct dccg *dccg) static void dccg42_init(struct dccg *dccg) { - int otg_inst; - struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); + unsigned int i; + struct resource_pool *res_pool = dccg->ctx->dc->res_pool; /* Set HPO stream encoder to use refclk to avoid case where PHY is * disabled and SYMCLK32 for HPO SE is sourced from PHYD32CLK which * will cause DCN to hang. */ - for (otg_inst = 0; otg_inst < 4; otg_inst++) - dccg35_disable_symclk32_se(dccg, otg_inst); + for (i = 0; i < res_pool->hpo_dp_stream_enc_count; i++) + dccg35_disable_symclk32_se(dccg, i); if (dccg->ctx->dc->debug.root_clock_optimization.bits.symclk32_le) { - dccg401_disable_symclk32_le(dccg, 0); - dccg401_disable_symclk32_le(dccg, 1); - dccg401_disable_symclk32_le(dccg, 2); - dccg401_disable_symclk32_le(dccg, 3); + for (i = 0; i < res_pool->hpo_dp_link_enc_count; i++) + dccg401_disable_symclk32_le(dccg, i); } if (dccg->ctx->dc->debug.root_clock_optimization.bits.dpstream) { - dccg401_disable_dpstreamclk(dccg, 0); - dccg401_disable_dpstreamclk(dccg, 1); - dccg401_disable_dpstreamclk(dccg, 2); - dccg401_disable_dpstreamclk(dccg, 3); - } - if (!dccg->ctx->dc->debug.root_clock_optimization.bits.physymclk) { - REG_UPDATE_5(DCCG_GATE_DISABLE_CNTL2, - PHYASYMCLK_ROOT_GATE_DISABLE, 1, - PHYBSYMCLK_ROOT_GATE_DISABLE, 1, - PHYCSYMCLK_ROOT_GATE_DISABLE, 1, - PHYDSYMCLK_ROOT_GATE_DISABLE, 1, - PHYESYMCLK_ROOT_GATE_DISABLE, 1); + for (i = 0; i < res_pool->hpo_dp_stream_enc_count; i++) + dccg401_disable_dpstreamclk(dccg, i); } + dccg42_disable_hdmistreamclk(dccg); if (dccg->ctx->dc->debug.root_clock_optimization.bits.hdmichar) dccg42_disable_hdmicharclk(dccg, 0); -- cgit v1.2.3 From 9ba9a1486312dfbec99621eb5ae761739f2fd721 Mon Sep 17 00:00:00 2001 From: William Palacek Date: Mon, 25 May 2026 12:09:36 -0400 Subject: drm/amdkfd: use scnprintf/vscnprintf in kfd_smi_event_add snprintf() and vsnprintf() return the number of bytes that would have been written if the buffer were large enough, not the actual bytes written. If truncation occurs, the accumulated length can exceed the buffer size, causing kfifo_in() to read past the fifo_in[] stack buffer. Switch to scnprintf() and vscnprintf() which return the actual number of bytes written, excluding the null terminator. This prevents the potential buffer over-read when calculating the offset for subsequent writes. Signed-off-by: William Palacek Reviewed-by: Alysa Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c index e659cd50eb0b..6a7b4d959541 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c @@ -224,10 +224,10 @@ static void kfd_smi_event_add(struct task_struct *task, struct kfd_node *dev, pid = kfd_smi_task_to_pid(task); - len = snprintf(fifo_in, sizeof(fifo_in), "%x ", event); + len = scnprintf(fifo_in, sizeof(fifo_in), "%x ", event); va_start(args, fmt); - len += vsnprintf(fifo_in + len, sizeof(fifo_in) - len, fmt, args); + len += vscnprintf(fifo_in + len, sizeof(fifo_in) - len, fmt, args); va_end(args); add_event_to_kfifo(pid, dev, event, fifo_in, len); -- cgit v1.2.3 From dac8aa629a45e34027444f74d3b86b6f104b024c Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Fri, 5 Jun 2026 15:05:46 -0400 Subject: drm/amd/display: Remove DCCG registers not needed in DCN42 [why] Some resources that exist in the DCN block are not needed and shouldn't be used. [how] Remove defines from register lists. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h | 62 +++++++++++----------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h index d45e3af77aad..a2b17ed11bdb 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h @@ -57,34 +57,24 @@ DCCG_SF(PHYBSYMCLK_CLOCK_CNTL, PHYBSYMCLK_SRC_SEL, mask_sh),\ DCCG_SF(PHYCSYMCLK_CLOCK_CNTL, PHYCSYMCLK_EN, mask_sh),\ DCCG_SF(PHYCSYMCLK_CLOCK_CNTL, PHYCSYMCLK_SRC_SEL, mask_sh),\ - DCCG_SF(PHYDSYMCLK_CLOCK_CNTL, PHYDSYMCLK_EN, mask_sh),\ - DCCG_SF(PHYDSYMCLK_CLOCK_CNTL, PHYDSYMCLK_SRC_SEL, mask_sh),\ DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK0_EN, mask_sh),\ DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK1_EN, mask_sh),\ DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK2_EN, mask_sh),\ - DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK3_EN, mask_sh),\ DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK0_SRC_SEL, mask_sh),\ DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK1_SRC_SEL, mask_sh),\ DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK2_SRC_SEL, mask_sh),\ - DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK3_SRC_SEL, mask_sh),\ DCCG_SF(HDMISTREAMCLK_CNTL, HDMISTREAMCLK0_EN, mask_sh),\ DCCG_SF(HDMISTREAMCLK_CNTL, HDMISTREAMCLK0_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE0_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE1_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE2_SRC_SEL, mask_sh),\ - DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE3_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE0_EN, mask_sh),\ DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE1_EN, mask_sh),\ DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE2_EN, mask_sh),\ - DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE3_EN, mask_sh),\ DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE0_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE1_SRC_SEL, mask_sh),\ - DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE2_SRC_SEL, mask_sh),\ - DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE3_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE0_EN, mask_sh),\ DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE1_EN, mask_sh),\ - DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE2_EN, mask_sh),\ - DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE3_EN, mask_sh),\ DCCG_SFII(OTG, PIXEL_RATE_CNTL, PIPE, DTO_SRC_SEL, 0, mask_sh),\ DCCG_SFII(OTG, PIXEL_RATE_CNTL, PIPE, DTO_SRC_SEL, 1, mask_sh),\ DCCG_SFII(OTG, PIXEL_RATE_CNTL, PIPE, DTO_SRC_SEL, 2, mask_sh),\ @@ -122,7 +112,6 @@ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYASYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYBSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYCSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYDSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GLOBAL_FGCG_REP_CNTL, DCCG_GLOBAL_FGCG_REP_DIS, mask_sh),\ DCCG_SFII(OTG, PIXEL_RATE_CNTL, DP_DTO, ENABLE, 0, mask_sh),\ DCCG_SFII(OTG, PIXEL_RATE_CNTL, DP_DTO, ENABLE, 1, mask_sh),\ @@ -135,7 +124,6 @@ DCCG_SF(DSCCLK_DTO_CTRL, DSCCLK0_EN, mask_sh),\ DCCG_SF(DSCCLK_DTO_CTRL, DSCCLK1_EN, mask_sh),\ DCCG_SF(DSCCLK_DTO_CTRL, DSCCLK2_EN, mask_sh),\ - DCCG_SF(DSCCLK_DTO_CTRL, DSCCLK3_EN, mask_sh),\ DCCG_SF(DSCCLK0_DTO_PARAM, DSCCLK0_DTO_PHASE, mask_sh),\ DCCG_SF(DSCCLK0_DTO_PARAM, DSCCLK0_DTO_MODULO, mask_sh),\ DCCG_SF(DSCCLK1_DTO_PARAM, DSCCLK1_DTO_PHASE, mask_sh),\ @@ -148,36 +136,26 @@ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKA_FE_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKB_FE_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKC_FE_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKD_FE_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKA_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKB_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKC_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKD_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYASYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYBSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYCSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYDSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_SE0_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_SE1_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_SE2_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_SE3_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_LE0_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_LE1_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_LE2_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_LE3_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_SE0_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_SE1_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_SE2_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_SE3_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_LE0_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_LE1_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_LE2_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_LE3_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL4, HDMICHARCLK0_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL4, PHYA_REFCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL4, PHYB_REFCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL4, PHYC_REFCLK_ROOT_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL4, PHYD_REFCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DTBCLK_P0_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DTBCLK_P1_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DTBCLK_P2_GATE_DISABLE, mask_sh),\ @@ -185,19 +163,15 @@ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKA_FE_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKB_FE_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKC_FE_ROOT_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKD_FE_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKA_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKB_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKC_ROOT_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKD_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK0_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK1_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK2_ROOT_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK3_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK0_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK1_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK2_GATE_DISABLE, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK3_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL6, DSCCLK0_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL6, DSCCLK1_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL6, DSCCLK2_ROOT_GATE_DISABLE, mask_sh),\ @@ -209,26 +183,38 @@ DCCG_SF(SYMCLKA_CLOCK_ENABLE, SYMCLKA_CLOCK_ENABLE, mask_sh),\ DCCG_SF(SYMCLKB_CLOCK_ENABLE, SYMCLKB_CLOCK_ENABLE, mask_sh),\ DCCG_SF(SYMCLKC_CLOCK_ENABLE, SYMCLKC_CLOCK_ENABLE, mask_sh),\ - DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_CLOCK_ENABLE, mask_sh),\ DCCG_SF(SYMCLKA_CLOCK_ENABLE, SYMCLKA_FE_EN, mask_sh),\ DCCG_SF(SYMCLKB_CLOCK_ENABLE, SYMCLKB_FE_EN, mask_sh),\ DCCG_SF(SYMCLKC_CLOCK_ENABLE, SYMCLKC_FE_EN, mask_sh),\ - DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_FE_EN, mask_sh),\ DCCG_SF(SYMCLKA_CLOCK_ENABLE, SYMCLKA_FE_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLKB_CLOCK_ENABLE, SYMCLKB_FE_SRC_SEL, mask_sh),\ - DCCG_SF(SYMCLKC_CLOCK_ENABLE, SYMCLKC_FE_SRC_SEL, mask_sh),\ - DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_FE_SRC_SEL, mask_sh) + DCCG_SF(SYMCLKC_CLOCK_ENABLE, SYMCLKC_FE_SRC_SEL, mask_sh) #define DCCG_MASK_SH_LIST_DCN42(mask_sh) \ DCCG_MASK_SH_LIST_DCN42_COMMON(mask_sh),\ + DCCG_SF(PHYDSYMCLK_CLOCK_CNTL, PHYDSYMCLK_EN, mask_sh),\ + DCCG_SF(PHYDSYMCLK_CLOCK_CNTL, PHYDSYMCLK_SRC_SEL, mask_sh),\ DCCG_SF(PHYESYMCLK_CLOCK_CNTL, PHYESYMCLK_EN, mask_sh),\ DCCG_SF(PHYESYMCLK_CLOCK_CNTL, PHYESYMCLK_SRC_SEL, mask_sh),\ DCCG_SF(HDMISTREAMCLK0_DTO_PARAM, HDMISTREAMCLK0_DTO_PHASE, mask_sh),\ DCCG_SF(HDMISTREAMCLK0_DTO_PARAM, HDMISTREAMCLK0_DTO_MODULO, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYDSYMCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYESYMCLK_ROOT_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKD_FE_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL2, SYMCLKD_GATE_DISABLE, mask_sh),\ DCCG_SF(DSCCLK3_DTO_PARAM, DSCCLK3_DTO_PHASE, mask_sh),\ DCCG_SF(DSCCLK3_DTO_PARAM, DSCCLK3_DTO_MODULO, mask_sh),\ - DCCG_SF(DCCG_GATE_DISABLE_CNTL2, PHYESYMCLK_ROOT_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_SE3_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_LE2_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_ROOT_LE3_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_SE3_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_LE2_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL3, SYMCLK32_LE3_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKD_FE_ROOT_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKD_ROOT_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK3_ROOT_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL5, DPSTREAMCLK3_GATE_DISABLE, mask_sh),\ + DCCG_SF(DCCG_GATE_DISABLE_CNTL4, PHYD_REFCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL4, PHYE_REFCLK_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKE_FE_ROOT_GATE_DISABLE, mask_sh),\ DCCG_SF(DCCG_GATE_DISABLE_CNTL5, SYMCLKE_ROOT_GATE_DISABLE, mask_sh),\ @@ -237,10 +223,22 @@ DCCG_SF(SYMCLKB_CLOCK_ENABLE, SYMCLKB_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLKC_CLOCK_ENABLE, SYMCLKC_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_SRC_SEL, mask_sh),\ + DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_CLOCK_ENABLE, mask_sh),\ + DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_FE_EN, mask_sh),\ + DCCG_SF(SYMCLKD_CLOCK_ENABLE, SYMCLKD_FE_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_SRC_SEL, mask_sh),\ DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_CLOCK_ENABLE, mask_sh),\ DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_FE_EN, mask_sh),\ - DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_FE_SRC_SEL, mask_sh) + DCCG_SF(SYMCLKE_CLOCK_ENABLE, SYMCLKE_FE_SRC_SEL, mask_sh),\ + DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE3_SRC_SEL, mask_sh),\ + DCCG_SF(SYMCLK32_SE_CNTL, SYMCLK32_SE3_EN, mask_sh),\ + DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE2_SRC_SEL, mask_sh),\ + DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE3_SRC_SEL, mask_sh),\ + DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE2_EN, mask_sh),\ + DCCG_SF(SYMCLK32_LE_CNTL, SYMCLK32_LE3_EN, mask_sh),\ + DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK3_EN, mask_sh),\ + DCCG_SF(DPSTREAMCLK_CNTL, DPSTREAMCLK3_SRC_SEL, mask_sh),\ + DCCG_SF(DSCCLK_DTO_CTRL, DSCCLK3_EN, mask_sh) void dccg42_otg_add_pixel(struct dccg *dccg, -- cgit v1.2.3 From 65485e86e34e7189ee14f3c1285cf7c65a9e8edc Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Wed, 10 Jun 2026 12:49:01 -0400 Subject: drm/amd/display: drop redundant colorop type and TF checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DRM core builds the curve_1d_type enum property with only the supported TF values, so any curve_1d_type that reaches atomic_commit is already guaranteed to be in the supported set. The per-colorop type field is immutable — it cannot change between the loop that finds colorop_state and the if block that uses it, so re-checking colorop->type there is dead code. Remove the redundant checks: - colorop->type == DRM_COLOROP_1D_CURVE in the shaper TF if block - colorop->type == DRM_COLOROP_1D_LUT in the shaper LUT if block - colorop->type == DRM_COLOROP_1D_CURVE in the blend TF if block - colorop->type == DRM_COLOROP_1D_LUT in the blend LUT if block - BIT(colorop_state->curve_1d_type) & supported_blnd_tfs in the blend TF if block (already guaranteed by the loop filter) - BIT(colorop_state->curve_1d_type) & supported_blnd_tfs in the blend LUT if block (nonsensical: a 1D_LUT colorop has no curve_1d_type) No functional change. Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 69a3783e5223..60ca4356da9a 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 @@ -1660,7 +1660,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_CURVE) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Shaper TF colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); @@ -1687,7 +1687,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_LUT) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Shaper LUT colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; @@ -1833,8 +1833,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_CURVE && - (BIT(colorop_state->curve_1d_type) & amdgpu_dm_supported_blnd_tfs)) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Blend TF colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); @@ -1859,8 +1858,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_LUT && - (BIT(colorop_state->curve_1d_type) & amdgpu_dm_supported_blnd_tfs)) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Blend LUT colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; -- cgit v1.2.3 From 1e453f7e776bbbd4d7848f43fad1e98bb97be673 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Wed, 10 Jun 2026 12:49:58 -0400 Subject: drm/amd/display: split TF/LUT colorop state lookups into separate upfront phases In __set_dm_plane_colorop_shaper and __set_dm_plane_colorop_blend the single colorop_state variable was reused sequentially: first to capture the TF state, then (after mutating the colorop pointer) to capture the LUT state. Split into separate tf_state / lut_state pointers and introduce a dedicated lut_colorop local. Resolve both pointers upfront before any computation begins. This separates the concern of "find the states" from "use the states" and makes the code easier to follow. No functional change. Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 94 +++++++++++----------- 1 file changed, 48 insertions(+), 46 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 60ca4356da9a..9bcb73c95fef 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 @@ -1640,8 +1640,10 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, struct drm_colorop *colorop) { struct drm_colorop *old_colorop; - struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; + struct drm_colorop_state *new_colorop_state; + struct drm_colorop_state *tf_state = NULL, *lut_state = NULL; struct drm_atomic_commit *state = plane_state->state; + struct drm_colorop *lut_colorop; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func; const struct drm_color_lut32 *shaper_lut; @@ -1650,53 +1652,52 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, u32 shaper_size; int i = 0, ret = 0; - /* 1D Curve - SHAPER TF */ + /* 1D Curve - SHAPER TF: find state */ old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { if (new_colorop_state->colorop == old_colorop && (BIT(new_colorop_state->curve_1d_type) & amdgpu_dm_supported_shaper_tfs)) { - colorop_state = new_colorop_state; + tf_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Shaper TF colorop with ID: %d\n", colorop->base.id); - tf->type = TF_TYPE_DISTRIBUTED_POINTS; - tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); - tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - ret = __set_output_tf(tf, 0, 0, false); - if (ret) - return ret; - enabled = true; - } - - /* 1D LUT - SHAPER LUT */ - colorop = old_colorop->next; - if (!colorop) { + /* 1D LUT - SHAPER LUT: find state */ + lut_colorop = old_colorop->next; + if (!lut_colorop) { drm_dbg(dev, "no Shaper LUT colorop found\n"); return -EINVAL; } - old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { - if (new_colorop_state->colorop == old_colorop && + if (new_colorop_state->colorop == lut_colorop && new_colorop_state->colorop->type == DRM_COLOROP_1D_LUT) { - colorop_state = new_colorop_state; + lut_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Shaper LUT colorop with ID: %d\n", colorop->base.id); + if (tf_state && !tf_state->bypass) { + drm_dbg(dev, "Shaper TF colorop with ID: %d\n", old_colorop->base.id); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(tf_state->curve_1d_type); + tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + ret = __set_output_tf(tf, 0, 0, false); + if (ret) + return ret; + enabled = true; + } + + if (lut_state && !lut_state->bypass) { + drm_dbg(dev, "Shaper LUT colorop with ID: %d\n", lut_colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - shaper_lut = __extract_blob_lut32(colorop_state->data, &shaper_size); + shaper_lut = __extract_blob_lut32(lut_state->data, &shaper_size); shaper_size = shaper_lut != NULL ? shaper_size : 0; /* Custom LUT size must be the same as supported size */ - if (shaper_size == colorop->size) { + if (shaper_size == lut_colorop->size) { ret = __set_output_tf_32(tf, shaper_lut, shaper_size, false); if (ret) return ret; @@ -1812,8 +1813,10 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, struct drm_colorop *colorop) { struct drm_colorop *old_colorop; - struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; + struct drm_colorop_state *new_colorop_state; + struct drm_colorop_state *tf_state = NULL, *lut_state = NULL; struct drm_atomic_commit *state = plane_state->state; + struct drm_colorop *lut_colorop; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; struct dc_transfer_func *tf = &dc_plane_state->cm.blend_func; const struct drm_color_lut32 *blend_lut = NULL; @@ -1823,52 +1826,51 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, dc_plane_state->cm.flags.bits.blend_enable = 0; - /* 1D Curve - BLND TF */ + /* 1D Curve - BLND TF: find state */ old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { if (new_colorop_state->colorop == old_colorop && (BIT(new_colorop_state->curve_1d_type) & amdgpu_dm_supported_blnd_tfs)) { - colorop_state = new_colorop_state; + tf_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Blend TF colorop with ID: %d\n", colorop->base.id); - tf->type = TF_TYPE_DISTRIBUTED_POINTS; - tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); - tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - dc_plane_state->cm.flags.bits.blend_enable = 1; - __set_input_tf_32(NULL, tf, blend_lut, blend_size); - } - - /* 1D Curve - BLND LUT */ - colorop = old_colorop->next; - if (!colorop) { + /* 1D LUT - BLND LUT: find state */ + lut_colorop = old_colorop->next; + if (!lut_colorop) { drm_dbg(dev, "no Blend LUT colorop found\n"); return -EINVAL; } - old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { - if (new_colorop_state->colorop == old_colorop && + if (new_colorop_state->colorop == lut_colorop && new_colorop_state->colorop->type == DRM_COLOROP_1D_LUT) { - colorop_state = new_colorop_state; + lut_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Blend LUT colorop with ID: %d\n", colorop->base.id); + if (tf_state && !tf_state->bypass) { + drm_dbg(dev, "Blend TF colorop with ID: %d\n", old_colorop->base.id); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(tf_state->curve_1d_type); + tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + dc_plane_state->cm.flags.bits.blend_enable = 1; + __set_input_tf_32(NULL, tf, blend_lut, blend_size); + } + + if (lut_state && !lut_state->bypass) { + drm_dbg(dev, "Blend LUT colorop with ID: %d\n", lut_colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; dc_plane_state->cm.flags.bits.blend_enable = 1; - blend_lut = __extract_blob_lut32(colorop_state->data, &blend_size); + blend_lut = __extract_blob_lut32(lut_state->data, &blend_size); blend_size = blend_lut != NULL ? blend_size : 0; /* Custom LUT size must be the same as supported size */ - if (blend_size == colorop->size) + if (blend_size == lut_colorop->size) __set_input_tf_32(NULL, tf, blend_lut, blend_size); } -- cgit v1.2.3 From 0b2becfc28d0af6c2547c290c7d188eafaf0d23d Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Mon, 22 Jun 2026 13:35:14 +0530 Subject: drm/amdgpu: bounds check atom indirect io method Bound indirect io method execution by the BIOS size to avoid out-of-bounds reads. Signed-off-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/atom.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/atom.c b/drivers/gpu/drm/amd/amdgpu/atom.c index c3824934ac7d..23f5cd52f9fc 100644 --- a/drivers/gpu/drm/amd/amdgpu/atom.c +++ b/drivers/gpu/drm/amd/amdgpu/atom.c @@ -114,8 +114,10 @@ static uint32_t atom_iio_execute(struct atom_context *ctx, int base, uint32_t index, uint32_t data) { uint32_t temp = 0xCDCDCDCD; + int start = base; - while (1) + /* IIO opcodes read up to base+3; keep within the BIOS image */ + while (base + 3 < ctx->bios_size) switch (CU8(base)) { case ATOM_IIO_NOP: base++; @@ -180,6 +182,9 @@ static uint32_t atom_iio_execute(struct atom_context *ctx, int base, pr_info("Unknown IIO opcode\n"); return 0; } + + pr_info("IIO method starting at offset %d runs past BIOS image\n", start); + return 0; } static uint32_t atom_get_src_int(atom_exec_context *ctx, uint8_t attr, -- cgit v1.2.3 From 687bd7c811ca1ad44785399f02947521a885ca36 Mon Sep 17 00:00:00 2001 From: Karen Chen Date: Wed, 10 Jun 2026 13:40:13 -0400 Subject: drm/amd/display: Disable DPPCLK RCG to fix cursor disappearing [Why & How] DPP clock is gated when programming the cursor. This change disables DPPCLK RCG in dccg42_init before accessing DPP, ensuring cursor programming latches correctly. Assisted-by: Cursor Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Karen Chen Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index 8989761c6078..6cbc1f4ef411 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -292,6 +292,12 @@ static void dccg42_init(struct dccg *dccg) dccg42_disable_hdmistreamclk(dccg); if (dccg->ctx->dc->debug.root_clock_optimization.bits.hdmichar) dccg42_disable_hdmicharclk(dccg, 0); + + if (dccg->ctx->dc->debug.root_clock_optimization.bits.dpp) { + for (i = 0; i < res_pool->pipe_count; i++) { + dccg35_dpp_root_clock_control(dccg, i, true); + } + } } -- cgit v1.2.3 From 8af429f6efb0b809e6884e55b1728564fcb9ed8a Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Wed, 10 Jun 2026 09:22:47 -0400 Subject: drm/amd/display: Allow Per-DPM De-rates Instead Of A Single Global Value [Why] Currently only a singular de-rate is used for all DPM levels. The intent was to limit the bandwidth utilization at high DPMs so the display requirements are not competing with other engines for bandwidth. At lower DPMs, the de-rates could be more lenient so more bandwidth can be utilized without the need to increase the DPM level and result in potential power savings. i.e. DPM0 could be achieved on certain display configs instead of DPM1 if de-rates were a couple percentage points higher The system average de-rates can be adjusted as needed as only urgent de-rates are defined for the SOC. [How] Update QOS parameters to have a table of derates with a per-DPM granularity If the per-DPM value is provided, that will value be used. Otherwise use the global value if there is no DPM specific value. Reviewed-by: Jun Lei Signed-off-by: Austin Zheng Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dml2_0/dml21/inc/dml_top_soc_parameter_types.h | 13 +++++++ .../dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 41 +++++++++++++++------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_soc_parameter_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_soc_parameter_types.h index 6152155d6073..672b96a3da74 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_soc_parameter_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_soc_parameter_types.h @@ -71,8 +71,21 @@ enum dml2_qos_param_type { dml2_qos_param_type_dcn4x }; +//Indicies mapped to DPM level +// Unpopulated indicies should fallback to the global derate value. +struct dml2_soc_derate_values_per_dpm { + unsigned int dram_derate_percent_pixel[DML_MAX_CLK_TABLE_SIZE]; + unsigned int fclk_derate_percent[DML_MAX_CLK_TABLE_SIZE]; + unsigned int dcfclk_derate_percent[DML_MAX_CLK_TABLE_SIZE]; +}; + +struct dml2_soc_derates_per_dpm { + struct dml2_soc_derate_values_per_dpm system_active_derates_per_dpm; +}; + struct dml2_soc_qos_parameters { struct dml2_soc_derates derate_table; + struct dml2_soc_derates_per_dpm derate_table_per_dpm; struct { unsigned int base_latency_us; unsigned int scaling_factor_us; 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 f338e733318e..51a66e1be7a1 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 @@ -2701,7 +2701,8 @@ static double dml_get_return_bandwidth_available( bool is_hvm_only, double dcfclk_mhz, double fclk_mhz, - double dram_bw_mbps) + double dram_bw_mbps, + unsigned int uclk_dpm_level) { double return_bw_mbps = 0.; double ideal_sdp_bandwidth = (double)soc->return_bus_width_bytes * dcfclk_mhz; @@ -2722,9 +2723,16 @@ static double dml_get_return_bandwidth_available( derate_fabric_factor = soc->qos_parameters.derate_table.dcn_mall_prefetch_average.fclk_derate_percent / 100.0; derate_dram_factor = soc->qos_parameters.derate_table.dcn_mall_prefetch_average.dram_derate_percent_pixel / 100.0; } else { // just assume sys_active - derate_sdp_factor = soc->qos_parameters.derate_table.system_active_average.dcfclk_derate_percent / 100.0; - derate_fabric_factor = soc->qos_parameters.derate_table.system_active_average.fclk_derate_percent / 100.0; - derate_dram_factor = soc->qos_parameters.derate_table.system_active_average.dram_derate_percent_pixel / 100.0; + // use per dpm derates if the values are populated. Otherwise use global derates + derate_sdp_factor = soc->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[uclk_dpm_level] != 0 ? + soc->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[uclk_dpm_level] / 100.0 : + soc->qos_parameters.derate_table.system_active_average.dcfclk_derate_percent / 100.0; + derate_fabric_factor = soc->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[uclk_dpm_level] != 0 ? + soc->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[uclk_dpm_level] / 100.0 : + soc->qos_parameters.derate_table.system_active_average.fclk_derate_percent / 100.0; + derate_dram_factor = soc->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[uclk_dpm_level] != 0 ? + soc->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[uclk_dpm_level] / 100.0 : + soc->qos_parameters.derate_table.system_active_average.dram_derate_percent_pixel / 100.0; } } else { // urgent bw if (state_type == dml2_core_internal_soc_state_svp_prefetch) { @@ -2778,6 +2786,7 @@ static double dml_get_return_bandwidth_available( DML_LOG_VERBOSE("DML::%s: derate_fabric_bandwidth = %f (derate %f)\n", __func__, derate_fabric_bandwidth, derate_fabric_factor); DML_LOG_VERBOSE("DML::%s: derate_dram_bandwidth = %f (derate %f)\n", __func__, derate_dram_bandwidth, derate_dram_factor); DML_LOG_VERBOSE("DML::%s: return_bw_mbps = %f\n", __func__, return_bw_mbps); + DML_LOG_VERBOSE("DML::%s: uclk_dpm_level = %u\n", __func__, uclk_dpm_level); return return_bw_mbps; } @@ -2793,7 +2802,8 @@ static noinline_for_stack void calculate_bandwidth_available( bool HostVMEnable, double dcfclk_mhz, double fclk_mhz, - double dram_bw_mbps) + double dram_bw_mbps, + unsigned int uclk_dpm_level) { unsigned int n, m; @@ -2812,9 +2822,10 @@ static noinline_for_stack void calculate_bandwidth_available( 0, // hvm_only dcfclk_mhz, fclk_mhz, - dram_bw_mbps); + dram_bw_mbps, + uclk_dpm_level); - urg_bandwidth_available[m][n] = dml_get_return_bandwidth_available(soc, m, n, 0, HostVMEnable, 0, dcfclk_mhz, fclk_mhz, dram_bw_mbps); + urg_bandwidth_available[m][n] = dml_get_return_bandwidth_available(soc, m, n, 0, HostVMEnable, 0, dcfclk_mhz, fclk_mhz, dram_bw_mbps, uclk_dpm_level); #ifdef __DML_VBA_DEBUG__ @@ -2824,8 +2835,8 @@ static noinline_for_stack void calculate_bandwidth_available( // urg_bandwidth_available_vm_only is indexed by soc_state if (n == dml2_core_internal_bw_dram) { - urg_bandwidth_available_vm_only[m] = dml_get_return_bandwidth_available(soc, m, n, 0, HostVMEnable, 1, dcfclk_mhz, fclk_mhz, dram_bw_mbps); - urg_bandwidth_available_pixel_and_vm[m] = dml_get_return_bandwidth_available(soc, m, n, 0, HostVMEnable, 0, dcfclk_mhz, fclk_mhz, dram_bw_mbps); + urg_bandwidth_available_vm_only[m] = dml_get_return_bandwidth_available(soc, m, n, 0, HostVMEnable, 1, dcfclk_mhz, fclk_mhz, dram_bw_mbps, uclk_dpm_level); + urg_bandwidth_available_pixel_and_vm[m] = dml_get_return_bandwidth_available(soc, m, n, 0, HostVMEnable, 0, dcfclk_mhz, fclk_mhz, dram_bw_mbps, uclk_dpm_level); } } @@ -9483,7 +9494,8 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out display_cfg->hostvm_enable, mode_lib->ms.DCFCLK, mode_lib->ms.FabricClock, - mode_lib->ms.dram_bw_mbps); + mode_lib->ms.dram_bw_mbps, + mode_lib->ms.active_min_uclk_dpm_index); calculate_bandwidth_available( mode_lib->ms.support.avg_bandwidth_available_min, @@ -9498,10 +9510,12 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out mode_lib->ms.MaxDCFCLK, mode_lib->ms.MaxFabricClock, #ifdef DML_MODE_SUPPORT_USE_DPM_DRAM_BW - mode_lib->ms.dram_bw_mbps); + mode_lib->ms.dram_bw_mbps, #else - mode_lib->ms.max_dram_bw_mbps); + mode_lib->ms.max_dram_bw_mbps, #endif + mode_lib->ms.active_min_uclk_dpm_index); + // Average BW support check calculate_avg_bandwidth_required( @@ -10958,7 +10972,8 @@ static bool dml_core_mode_programming(struct dml2_core_calcs_mode_programming_ex display_cfg->hostvm_enable, mode_lib->mp.Dcfclk, mode_lib->mp.FabricClock, - mode_lib->mp.dram_bw_mbps); + mode_lib->mp.dram_bw_mbps, + mode_lib->mp.active_min_uclk_dpm_index); calculate_hostvm_inefficiency_factor( -- cgit v1.2.3 From 932d6696d527ec498c0f3d54b7c3c98f11863d2b Mon Sep 17 00:00:00 2001 From: Chandana G B Date: Mon, 1 Jun 2026 11:09:55 +0530 Subject: drm/amd/display: Fix intermittently CRC open failure during active rendering [Why] Opening the CRC data file during active rendering can fail with -EINVAL. Closing the CRC data file with ctrl+C (which will send SIGINT to the kernel and if the wait thread in sleep, kernel will send the -ERESTARTSYS to the wait_for_completion_interruptible_timeout) resulting in intermittently getting -ERESTARTSYS. which will just do the clean up without releasing the vblank reference causing -EINVAL while opening the crc data file in the next iteration [How] Ignoring the ERESTARTSYS as this is a return value for the wait_for_completion_interruptible_timeout() Reviewed-by: Chen-Yu Chen Signed-off-by: Chandana G B Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 54d3c5c9e652..970490c401e9 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 @@ -681,7 +681,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) */ ret = wait_for_completion_interruptible_timeout( &commit->hw_done, 10 * HZ); - if (ret < 0) + if (ret < 0 && ret != -ERESTARTSYS) goto cleanup; if (ret == 0) { -- cgit v1.2.3 From 073875c5d27c89e7fe831e9496f8f9c2fd9e95ea Mon Sep 17 00:00:00 2001 From: George Shen Date: Tue, 21 Apr 2026 20:27:42 -0400 Subject: drm/amd/display: Add flag to disable dynamic expansion for 12bpc [Why] Dynamic expansion is not needed when outputting 12bpc test patterns. [How] Add a debug flag to control disabling dynamic expansion in the case of 12bpc test patterns. Reviewed-by: Michael Strauss Signed-off-by: George Shen Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index b21fdea5fca3..c2a1f75ae9ae 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1288,6 +1288,7 @@ struct dc_debug_options { unsigned int force_odm2to1_for_edp_pixclk_mhz; bool enable_replay_esd_recovery; uint8_t iommu_mismatch_temp_wka; + bool disable_dynamic_expansion_for_test_pattern; }; diff --git a/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c b/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c index 50b6973ef123..881b8da656b2 100644 --- a/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c +++ b/drivers/gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c @@ -89,6 +89,24 @@ void opp2_set_disp_pattern_generator( break; } + if (opp->ctx->dc->debug.disable_dynamic_expansion_for_test_pattern) { + switch (test_pattern) { + case CONTROLLER_DP_TEST_PATTERN_COLORSQUARES: + case CONTROLLER_DP_TEST_PATTERN_COLORSQUARES_CEA: + case CONTROLLER_DP_TEST_PATTERN_VERTICALBARS: + case CONTROLLER_DP_TEST_PATTERN_HORIZONTALBARS: + case CONTROLLER_DP_TEST_PATTERN_COLORRAMP: + if (color_depth == COLOR_DEPTH_121212) + REG_UPDATE(FMT_DYNAMIC_EXP_CNTL, FMT_DYNAMIC_EXP_EN, 0); + break; + case CONTROLLER_DP_TEST_PATTERN_VIDEOMODE: + REG_UPDATE(FMT_DYNAMIC_EXP_CNTL, FMT_DYNAMIC_EXP_EN, 1); + break; + default: + break; + } + } + /* set DPG dimentions */ REG_SET_2(DPG_DIMENSIONS, 0, DPG_ACTIVE_WIDTH, width, -- cgit v1.2.3 From 1f8722455ed9e1771cd4fdaa8b2459a3dadc33d2 Mon Sep 17 00:00:00 2001 From: Alvin Lee Date: Tue, 5 May 2026 20:45:10 -0400 Subject: drm/amd/display: Update LSDMA commands to explicitly handle DCC fields [Description] - Previously linear copy commands for LSDMA assumed no DCC - Update so that there is explicit assignment for DCC related fields - Caller can 0 out the fields if DCC is not used - For linear copy command don't subtract 1 from the count - this will be done at a lower layer Reviewed-by: Rafal Ostrowski Signed-off-by: Alvin Lee Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c | 29 +++++++++++++++++------- drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h | 34 ++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c index 4c81989898e2..68ed0e16639d 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c @@ -2125,9 +2125,7 @@ bool dmub_lsdma_init(struct dc_dmub_srv *dc_dmub_srv) bool dmub_lsdma_send_linear_copy_command( struct dc_dmub_srv *dc_dmub_srv, - uint64_t src_addr, - uint64_t dst_addr, - uint32_t count + struct lsdma_linear_copy_params copy_data ) { struct dc_context *dc_ctx = dc_dmub_srv->ctx; @@ -2142,11 +2140,20 @@ bool dmub_lsdma_send_linear_copy_command( cmd.cmd_common.header.sub_type = DMUB_CMD__LSDMA_LINEAR_COPY; wait_type = DM_DMUB_WAIT_TYPE_NO_WAIT; - lsdma_data->u.linear_copy_data.count = count - 1; // LSDMA controller expects bytes to copy -1 - lsdma_data->u.linear_copy_data.src_lo = src_addr & 0xFFFFFFFF; - lsdma_data->u.linear_copy_data.src_hi = (src_addr >> 32) & 0xFFFFFFFF; - lsdma_data->u.linear_copy_data.dst_lo = dst_addr & 0xFFFFFFFF; - lsdma_data->u.linear_copy_data.dst_hi = (dst_addr >> 32) & 0xFFFFFFFF; + lsdma_data->u.linear_copy_data.count = copy_data.count; + lsdma_data->u.linear_copy_data.src_lo = copy_data.src_lo; + lsdma_data->u.linear_copy_data.src_hi = copy_data.src_hi; + lsdma_data->u.linear_copy_data.dst_lo = copy_data.dst_lo; + lsdma_data->u.linear_copy_data.dst_hi = copy_data.dst_hi; + lsdma_data->u.linear_copy_data.tmz = copy_data.tmz; + lsdma_data->u.linear_copy_data.data_format = copy_data.data_format; + lsdma_data->u.linear_copy_data.num_type = copy_data.num_type; + lsdma_data->u.linear_copy_data.read_compress = copy_data.read_compress; + lsdma_data->u.linear_copy_data.write_compress = copy_data.write_compress; + lsdma_data->u.linear_copy_data.max_com = copy_data.max_com; + lsdma_data->u.linear_copy_data.max_uncom = copy_data.max_uncom; + lsdma_data->u.linear_copy_data.cache_policy_src = copy_data.cache_policy_src; + lsdma_data->u.linear_copy_data.cache_policy_dst = copy_data.cache_policy_dst; result = dc_wake_and_execute_dmub_cmd(dc_ctx, &cmd, wait_type); @@ -2191,6 +2198,12 @@ bool dmub_lsdma_send_linear_sub_window_copy_command( lsdma_data->u.linear_sub_window_copy_data.rect_y = copy_data.rect_y; lsdma_data->u.linear_sub_window_copy_data.src_cache_policy = copy_data.src_cache_policy; lsdma_data->u.linear_sub_window_copy_data.dst_cache_policy = copy_data.dst_cache_policy; + lsdma_data->u.linear_sub_window_copy_data.data_format = copy_data.data_format; + lsdma_data->u.linear_sub_window_copy_data.num_type = copy_data.num_type; + lsdma_data->u.linear_sub_window_copy_data.read_compress = copy_data.read_compress; + lsdma_data->u.linear_sub_window_copy_data.write_compress = copy_data.write_compress; + lsdma_data->u.linear_sub_window_copy_data.max_com = copy_data.max_com; + lsdma_data->u.linear_sub_window_copy_data.max_uncom = copy_data.max_uncom; result = dc_wake_and_execute_dmub_cmd(dc_ctx, &cmd, wait_type); diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h index 5d399e6a8345..8bdaac0b0f98 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h @@ -203,11 +203,31 @@ void dc_dmub_srv_fams2_passthrough_flip( int surface_count); bool dmub_lsdma_init(struct dc_dmub_srv *dc_dmub_srv); + +struct lsdma_linear_copy_params { + uint32_t src_lo; + uint32_t src_hi; + + uint32_t dst_lo; + uint32_t dst_hi; + + uint32_t count : 30; + uint32_t read_compress : 2; + + uint32_t tmz : 4; + uint32_t cache_policy_src : 3; + uint32_t cache_policy_dst : 3; + uint32_t data_format : 6; + uint32_t num_type : 3; + uint32_t write_compress : 2; + uint32_t max_com : 2; + uint32_t max_uncom : 1; + uint32_t reserved0 : 8; +}; + bool dmub_lsdma_send_linear_copy_command( struct dc_dmub_srv *dc_dmub_srv, - uint64_t src_addr, - uint64_t dst_addr, - uint32_t count); + struct lsdma_linear_copy_params copy_data); struct lsdma_linear_sub_window_copy_params { uint32_t src_lo; @@ -235,7 +255,13 @@ struct lsdma_linear_sub_window_copy_params { uint32_t element_size : 3; uint32_t src_cache_policy : 3; uint32_t dst_cache_policy : 3; - uint32_t padding : 19; + uint32_t data_format : 6; + uint32_t num_type : 3; + uint32_t read_compress : 2; + uint32_t write_compress : 2; + uint32_t max_com : 2; + uint32_t max_uncom : 1; + uint32_t reserved0 : 3; }; bool dmub_lsdma_send_linear_sub_window_copy_command( -- cgit v1.2.3 From 3754bdca435c8d38111b46467f82756e36465a58 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 9 Jun 2026 18:38:23 -0400 Subject: drm/amd/display: Update ONO PG Workaround for DCN42 [Why & How] There is an updated workaround for PG Repeater issue in DCN42. This PR is addressing the dynamic power gating use cases (Driver PG) to align with the new sequence. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Leo Chen Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 18 +++- .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h | 1 + drivers/gpu/drm/amd/display/dc/inc/hw/dccg.h | 1 + .../drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c | 97 +++++++++++++++------- .../drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.h | 22 ++++- 5 files changed, 99 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index 6cbc1f4ef411..616a896f0782 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -77,7 +77,7 @@ void dccg42_otg_drop_pixel(struct dccg *dccg, } } -void dccg42_enable_global_fgcg(struct dccg *dccg, bool value) +void dccg42_enable_global_fgcg(struct dccg *dccg, bool enable) { struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); @@ -85,9 +85,18 @@ void dccg42_enable_global_fgcg(struct dccg *dccg, bool value) * Fine grain control via bit2 of debug flag. */ if (dccg->ctx->dc->debug.disable_clock_gate || (dccg->ctx->dc->debug.iommu_mismatch_temp_wka & 0x4)) - value = false; + enable = false; - REG_UPDATE(DCCG_GLOBAL_FGCG_REP_CNTL, DCCG_GLOBAL_FGCG_REP_DIS, !value); + REG_UPDATE(DCCG_GLOBAL_FGCG_REP_CNTL, DCCG_GLOBAL_FGCG_REP_DIS, !enable); +} + +bool dccg42_get_global_fgcg_status(struct dccg *dccg) +{ + struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); + uint32_t disabled = 0; + + REG_GET(DCCG_GLOBAL_FGCG_REP_CNTL, DCCG_GLOBAL_FGCG_REP_DIS, &disabled); + return disabled & 0x1; } void dccg42_set_physymclk( @@ -339,7 +348,8 @@ static const struct dccg_funcs dccg42_funcs = { .dccg_root_gate_disable_control = dccg35_root_gate_disable_control, .dccg_read_reg_state = dccg31_read_reg_state, .dccg_enable_global_fgcg = dccg42_enable_global_fgcg, - .allow_clock_gating = dccg2_allow_clock_gating + .allow_clock_gating = dccg2_allow_clock_gating, + .dccg_get_global_fgcg_status = dccg42_get_global_fgcg_status, }; struct dccg *dccg42_create( diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h index a2b17ed11bdb..ebd3cec1a977 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h @@ -247,6 +247,7 @@ void dccg42_otg_add_pixel(struct dccg *dccg, void dccg42_otg_drop_pixel(struct dccg *dccg, uint32_t otg_inst); void dccg42_enable_global_fgcg(struct dccg *dccg, bool value); +bool dccg42_get_global_fgcg_status(struct dccg *dccg); void dccg42_set_physymclk( struct dccg *dccg, diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/dccg.h b/drivers/gpu/drm/amd/display/dc/inc/hw/dccg.h index 6db7c8753081..e756719308ab 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/dccg.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/dccg.h @@ -348,6 +348,7 @@ struct dccg_funcs { void (*dccg_root_gate_disable_control)(struct dccg *dccg, uint32_t pipe_idx, uint32_t disable_clock_gating); void (*dccg_read_reg_state)(struct dccg *dccg, struct dcn_dccg_reg_state *dccg_reg_state); void (*dccg_enable_global_fgcg)(struct dccg *dccg, bool enable); + bool (*dccg_get_global_fgcg_status)(struct dccg *dccg); }; #endif //__DAL_DCCG_H__ diff --git a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c index 729c2b653161..94361e326c56 100644 --- a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c +++ b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c @@ -22,6 +22,45 @@ #define DC_LOGGER \ pg_cntl->ctx->logger +/* + * ONO PG Workoaround: Saved FGCG repeaters states captured before powering up an ONO + * domain so it can be restored once the domain is powered up. + */ +struct dcn42_global_fgcg_rep_state { + uint32_t dmu_rep_fgcg; + uint32_t dccg_global_ono_rep_fgcg; + uint32_t az_rep_fgcg; +}; + +/* Save and disable FGCG repeaters before powering up the ONO domain. */ +static void pg_cntl42_save_and_disable_global_fgcg_rep(struct pg_cntl *pg_cntl, + struct dcn42_global_fgcg_rep_state *state) +{ + struct dcn_pg_cntl *pg_cntl_dcn = TO_DCN_PG_CNTL(pg_cntl); + + REG_GET(DMU_CLK_CNTL, LONO_FGCG_REP_DIS, &state->dmu_rep_fgcg); + if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_get_global_fgcg_status) + state->dccg_global_ono_rep_fgcg = pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_get_global_fgcg_status(pg_cntl->ctx->dc->res_pool->dccg); + REG_GET(AZ_CLOCK_CNTL, AZ_GLOBAL_FGCG_REP_DIS, &state->az_rep_fgcg); + + REG_UPDATE(DMU_CLK_CNTL, LONO_FGCG_REP_DIS, 1); + if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) + pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, false); + REG_UPDATE(AZ_CLOCK_CNTL, AZ_GLOBAL_FGCG_REP_DIS, 1); +} + +/* Restore FGCG repeaters after the ONO domains are powered up. */ +static void pg_cntl42_restore_global_fgcg_rep(struct pg_cntl *pg_cntl, + struct dcn42_global_fgcg_rep_state *state) +{ + struct dcn_pg_cntl *pg_cntl_dcn = TO_DCN_PG_CNTL(pg_cntl); + + REG_UPDATE(DMU_CLK_CNTL, LONO_FGCG_REP_DIS, state->dmu_rep_fgcg); + if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) + pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, state->dccg_global_ono_rep_fgcg); + REG_UPDATE(AZ_CLOCK_CNTL, AZ_GLOBAL_FGCG_REP_DIS, state->az_rep_fgcg); +} + static bool pg_cntl42_dsc_pg_status(struct pg_cntl *pg_cntl, unsigned int dsc_inst) { struct dcn_pg_cntl *pg_cntl_dcn = TO_DCN_PG_CNTL(pg_cntl); @@ -54,6 +93,7 @@ void pg_cntl42_dsc_pg_control(struct pg_cntl *pg_cntl, unsigned int dsc_inst, bo uint32_t power_gate = power_on ? 0 : 1; uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl = 0; + struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; bool block_enabled; /*need to enable dscclk regardless DSC_PG*/ @@ -81,10 +121,9 @@ void pg_cntl42_dsc_pg_control(struct pg_cntl *pg_cntl, unsigned int dsc_inst, bo if (org_ip_request_cntl == 0) REG_SET(DC_IP_REQUEST_CNTL, 0, IP_REQUEST_EN, 1); - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, false); - } + if (power_on) + pg_cntl42_save_and_disable_global_fgcg_rep(pg_cntl, &fgcg_rep_state); + switch (dsc_inst) { case 0: /* DSC0 */ REG_UPDATE(DOMAIN16_PG_CONFIG, @@ -123,10 +162,8 @@ void pg_cntl42_dsc_pg_control(struct pg_cntl *pg_cntl, unsigned int dsc_inst, bo break; } - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, true); - } + if (power_on) + pg_cntl42_restore_global_fgcg_rep(pg_cntl, &fgcg_rep_state); if (dsc_inst < MAX_PIPES) pg_cntl->pg_pipe_res_enable[PG_DSC][dsc_inst] = power_on; @@ -174,6 +211,7 @@ void pg_cntl42_hubp_dpp_pg_control(struct pg_cntl *pg_cntl, unsigned int hubp_dp uint32_t power_gate = power_on ? 0 : 1; uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; + struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; bool block_enabled; bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || pg_cntl->ctx->dc->debug.disable_hubp_power_gate || @@ -196,10 +234,8 @@ void pg_cntl42_hubp_dpp_pg_control(struct pg_cntl *pg_cntl, unsigned int hubp_dp if (org_ip_request_cntl == 0) REG_SET(DC_IP_REQUEST_CNTL, 0, IP_REQUEST_EN, 1); - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, false); - } + if (power_on) + pg_cntl42_save_and_disable_global_fgcg_rep(pg_cntl, &fgcg_rep_state); switch (hubp_dpp_inst) { case 0: @@ -227,10 +263,9 @@ void pg_cntl42_hubp_dpp_pg_control(struct pg_cntl *pg_cntl, unsigned int hubp_dp break; } - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, true); - } + if (power_on) + pg_cntl42_restore_global_fgcg_rep(pg_cntl, &fgcg_rep_state); + DC_LOG_DEBUG("HUBP DPP instance %d, power %s", hubp_dpp_inst, power_on ? "ON" : "OFF"); @@ -258,6 +293,7 @@ void pg_cntl42_hpo_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; uint32_t power_forceon; + struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; bool block_enabled; bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || @@ -282,17 +318,15 @@ void pg_cntl42_hpo_pg_control(struct pg_cntl *pg_cntl, bool power_on) REG_GET(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, &org_ip_request_cntl); if (org_ip_request_cntl == 0) REG_SET(DC_IP_REQUEST_CNTL, 0, IP_REQUEST_EN, 1); - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, false); - } + if (power_on) + pg_cntl42_save_and_disable_global_fgcg_rep(pg_cntl, &fgcg_rep_state); + REG_UPDATE(DOMAIN25_PG_CONFIG, DOMAIN_POWER_GATE, power_gate); REG_WAIT(DOMAIN25_PG_STATUS, DOMAIN_PGFSM_PWR_STATUS, pwr_status, 1, 1000); - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, true); - } + if (power_on) + pg_cntl42_restore_global_fgcg_rep(pg_cntl, &fgcg_rep_state); + pg_cntl->pg_res_enable[PG_HPO] = power_on; } @@ -466,6 +500,7 @@ void pg_cntl42_dio_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t power_gate = power_on ? 0 : 1; uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; + struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; bool block_enabled; bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || @@ -486,18 +521,16 @@ void pg_cntl42_dio_pg_control(struct pg_cntl *pg_cntl, bool power_on) REG_GET(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, &org_ip_request_cntl); if (org_ip_request_cntl == 0) REG_SET(DC_IP_REQUEST_CNTL, 0, IP_REQUEST_EN, 1); - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, false); - } + if (power_on) + pg_cntl42_save_and_disable_global_fgcg_rep(pg_cntl, &fgcg_rep_state); + /* DIO */ REG_UPDATE(DOMAIN26_PG_CONFIG, DOMAIN_POWER_GATE, power_gate); REG_WAIT(DOMAIN26_PG_STATUS, DOMAIN_PGFSM_PWR_STATUS, pwr_status, 1, 1000); - if (power_on) { - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg) - pg_cntl->ctx->dc->res_pool->dccg->funcs->dccg_enable_global_fgcg(pg_cntl->ctx->dc->res_pool->dccg, true); - } + if (power_on) + pg_cntl42_restore_global_fgcg_rep(pg_cntl, &fgcg_rep_state); + pg_cntl->pg_res_enable[PG_DIO] = power_on; } diff --git a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.h b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.h index 7e8f4f03ae0e..813fa5c81172 100644 --- a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.h +++ b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.h @@ -34,7 +34,9 @@ SR(DOMAIN24_PG_STATUS), \ SR(DOMAIN25_PG_STATUS), \ SR(DOMAIN26_PG_STATUS), \ - SR(DC_IP_REQUEST_CNTL) + SR(DC_IP_REQUEST_CNTL), \ + SR(DMU_CLK_CNTL), \ + SR(AZ_CLOCK_CNTL) #define PG_CNTL_REG_LIST_DCN42B()\ SR(DOMAIN0_PG_CONFIG), \ @@ -63,7 +65,9 @@ SR(DOMAIN24_PG_STATUS), \ SR(DOMAIN25_PG_STATUS), \ SR(DOMAIN26_PG_STATUS), \ - SR(DC_IP_REQUEST_CNTL) + SR(DC_IP_REQUEST_CNTL), \ + SR(DMU_CLK_CNTL), \ + SR(AZ_CLOCK_CNTL) #define PG_CNTL_SF(reg_name, field_name, post_fix)\ .field_name = reg_name ## __ ## field_name ## post_fix @@ -121,7 +125,9 @@ PG_CNTL_SF(DOMAIN25_PG_STATUS, DOMAIN_PGFSM_PWR_STATUS, mask_sh), \ PG_CNTL_SF(DOMAIN26_PG_STATUS, DOMAIN_DESIRED_PWR_STATE, mask_sh), \ PG_CNTL_SF(DOMAIN26_PG_STATUS, DOMAIN_PGFSM_PWR_STATUS, mask_sh), \ - PG_CNTL_SF(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, mask_sh) + PG_CNTL_SF(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, mask_sh), \ + PG_CNTL_SF(DMU_CLK_CNTL, LONO_FGCG_REP_DIS, mask_sh), \ + PG_CNTL_SF(AZ_CLOCK_CNTL, AZ_GLOBAL_FGCG_REP_DIS, mask_sh) /* Not in DCN42B: * PG_CNTL_SF(DOMAIN19_PG_CONFIG, DOMAIN_POWER_FORCEON, mask_sh), @@ -178,7 +184,9 @@ PG_CNTL_SF(DOMAIN25_PG_STATUS, DOMAIN_PGFSM_PWR_STATUS, mask_sh), \ PG_CNTL_SF(DOMAIN26_PG_STATUS, DOMAIN_DESIRED_PWR_STATE, mask_sh), \ PG_CNTL_SF(DOMAIN26_PG_STATUS, DOMAIN_PGFSM_PWR_STATUS, mask_sh), \ - PG_CNTL_SF(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, mask_sh) + PG_CNTL_SF(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, mask_sh), \ + PG_CNTL_SF(DMU_CLK_CNTL, LONO_FGCG_REP_DIS, mask_sh), \ + PG_CNTL_SF(AZ_CLOCK_CNTL, AZ_GLOBAL_FGCG_REP_DIS, mask_sh) struct pg_cntl_shift { uint8_t IP_REQUEST_EN; @@ -186,6 +194,8 @@ struct pg_cntl_shift { uint8_t DOMAIN_POWER_GATE; uint8_t DOMAIN_DESIRED_PWR_STATE; uint8_t DOMAIN_PGFSM_PWR_STATUS; + uint8_t LONO_FGCG_REP_DIS; + uint8_t AZ_GLOBAL_FGCG_REP_DIS; }; struct pg_cntl_mask { uint32_t IP_REQUEST_EN; @@ -193,6 +203,8 @@ struct pg_cntl_mask { uint32_t DOMAIN_POWER_GATE; uint32_t DOMAIN_DESIRED_PWR_STATE; uint32_t DOMAIN_PGFSM_PWR_STATUS; + uint32_t LONO_FGCG_REP_DIS; + uint32_t AZ_GLOBAL_FGCG_REP_DIS; }; struct pg_cntl_registers { @@ -224,6 +236,8 @@ struct pg_cntl_registers { uint32_t DOMAIN24_PG_STATUS; uint32_t DOMAIN25_PG_STATUS; uint32_t DOMAIN26_PG_STATUS; + uint32_t DMU_CLK_CNTL; + uint32_t AZ_CLOCK_CNTL; }; struct dcn_pg_cntl { -- cgit v1.2.3 From 7874b9d6c59d8a99733c07eb9ed674bf4eb93099 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Wed, 10 Jun 2026 17:17:49 -0400 Subject: drm/amd/display: Remove unnecessary DSCCLK enable during DSC PG [Why & How] DSCCLK is not required when power gating or ungating the DSC block. Remove the unnecessary DSCCLK enable sequence. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Leo Chen Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c index 94361e326c56..2fc17dc510df 100644 --- a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c +++ b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c @@ -96,11 +96,6 @@ void pg_cntl42_dsc_pg_control(struct pg_cntl *pg_cntl, unsigned int dsc_inst, bo struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; bool block_enabled; - /*need to enable dscclk regardless DSC_PG*/ - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->enable_dsc && power_on) - pg_cntl->ctx->dc->res_pool->dccg->funcs->enable_dsc( - pg_cntl->ctx->dc->res_pool->dccg, dsc_inst); - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || pg_cntl->ctx->dc->debug.disable_dsc_power_gate || pg_cntl->ctx->dc->idle_optimizations_allowed; @@ -167,12 +162,6 @@ void pg_cntl42_dsc_pg_control(struct pg_cntl *pg_cntl, unsigned int dsc_inst, bo if (dsc_inst < MAX_PIPES) pg_cntl->pg_pipe_res_enable[PG_DSC][dsc_inst] = power_on; - - if (pg_cntl->ctx->dc->res_pool->dccg->funcs->disable_dsc && !power_on) { - /*this is to disable dscclk*/ - pg_cntl->ctx->dc->res_pool->dccg->funcs->disable_dsc( - pg_cntl->ctx->dc->res_pool->dccg, dsc_inst); - } } static bool pg_cntl42_hubp_dpp_pg_status(struct pg_cntl *pg_cntl, unsigned int hubp_dpp_inst) -- cgit v1.2.3 From 8b6ab8bdf835efb91c1d782b7c2cf32dad39238f Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Wed, 10 Jun 2026 17:20:01 -0400 Subject: drm/amd/display: Enable HUBP/DPP power gate for DCN42 [Why & How] Enable Driver PG for HUBP and DPP in DCN42. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Leo Chen Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 eb7fe5d70264..44728894dceb 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 @@ -729,8 +729,8 @@ static const struct dc_debug_options debug_defaults_drv = { .clock_trace = true, .disable_pplib_clock_request = false, .ignore_pg = false, - .disable_dpp_power_gate = true, - .disable_hubp_power_gate = true, + .disable_dpp_power_gate = false, + .disable_hubp_power_gate = false, .disable_optc_power_gate = true, .disable_dsc_power_gate = false, .disable_dio_power_gate = true, -- cgit v1.2.3 From 19e01bbaa1e58ccc88acd45858ef4f70d21fa41f Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Thu, 11 Jun 2026 13:28:59 -0400 Subject: drm/amd/display: Refactor Driver PG's skip PG logic [Why & How] When driver allows idle optimization, no HW state should be modified further by DC. Refactor the skip PG logic in pg_cntl in DCN42. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Leo Chen Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c | 150 +++++++++------------ 1 file changed, 60 insertions(+), 90 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c index 2fc17dc510df..78b33b2dbae8 100644 --- a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c +++ b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c @@ -61,6 +61,21 @@ static void pg_cntl42_restore_global_fgcg_rep(struct pg_cntl *pg_cntl, REG_UPDATE(AZ_CLOCK_CNTL, AZ_GLOBAL_FGCG_REP_DIS, state->az_rep_fgcg); } +static bool should_skip_pg_control(bool dc_in_idle_opt, bool power_on, bool block_enabled) +{ + if (dc_in_idle_opt) + return true; + + if (power_on && block_enabled) + return true; + + if (!power_on && !block_enabled) + return true; + + return false; +} + + static bool pg_cntl42_dsc_pg_status(struct pg_cntl *pg_cntl, unsigned int dsc_inst) { struct dcn_pg_cntl *pg_cntl_dcn = TO_DCN_PG_CNTL(pg_cntl); @@ -94,23 +109,14 @@ void pg_cntl42_dsc_pg_control(struct pg_cntl *pg_cntl, unsigned int dsc_inst, bo uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl = 0; struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; - bool block_enabled; - - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->debug.disable_dsc_power_gate || - pg_cntl->ctx->dc->idle_optimizations_allowed; + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || pg_cntl->ctx->dc->debug.disable_dsc_power_gate; - if (skip_pg && !power_on) + if (block_pg_disabled && !power_on) return; - block_enabled = pg_cntl42_dsc_pg_status(pg_cntl, dsc_inst); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } + bool block_enabled = pg_cntl42_dsc_pg_status(pg_cntl, dsc_inst); + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) + return; REG_GET(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, &org_ip_request_cntl); if (org_ip_request_cntl == 0) @@ -201,23 +207,16 @@ void pg_cntl42_hubp_dpp_pg_control(struct pg_cntl *pg_cntl, unsigned int hubp_dp uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; - bool block_enabled; - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->debug.disable_hubp_power_gate || - pg_cntl->ctx->dc->debug.disable_dpp_power_gate || - pg_cntl->ctx->dc->idle_optimizations_allowed; + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || + pg_cntl->ctx->dc->debug.disable_hubp_power_gate || + pg_cntl->ctx->dc->debug.disable_dpp_power_gate; - if (skip_pg && !power_on) + if (block_pg_disabled && !power_on) return; - block_enabled = pg_cntl42_hubp_dpp_pg_status(pg_cntl, hubp_dpp_inst); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } + bool block_enabled = pg_cntl42_hubp_dpp_pg_status(pg_cntl, hubp_dpp_inst); + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) + return; REG_GET(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, &org_ip_request_cntl); if (org_ip_request_cntl == 0) @@ -283,22 +282,17 @@ void pg_cntl42_hpo_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t org_ip_request_cntl; uint32_t power_forceon; struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; - bool block_enabled; - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->debug.disable_hpo_power_gate || - pg_cntl->ctx->dc->idle_optimizations_allowed; + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || + pg_cntl->ctx->dc->debug.disable_hpo_power_gate; - if (skip_pg && !power_on) + if (block_pg_disabled && !power_on) + return; + + bool block_enabled = pg_cntl42_hpo_pg_status(pg_cntl); + + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) return; - block_enabled = pg_cntl42_hpo_pg_status(pg_cntl); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } REG_GET(DOMAIN25_PG_CONFIG, DOMAIN_POWER_FORCEON, &power_forceon); if (power_forceon) @@ -337,23 +331,17 @@ void pg_cntl42_io_clk_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; uint32_t power_forceon; - bool block_enabled; - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->idle_optimizations_allowed || + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || pg_cntl->ctx->dc->debug.disable_io_clk_power_gate; - if (skip_pg && !power_on) + if (block_pg_disabled && !power_on) return; - block_enabled = pg_cntl42_io_clk_status(pg_cntl); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } + bool block_enabled = pg_cntl42_io_clk_status(pg_cntl); + + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) + return; REG_GET(DOMAIN22_PG_CONFIG, DOMAIN_POWER_FORCEON, &power_forceon); if (power_forceon) @@ -435,24 +423,16 @@ void pg_cntl42_mem_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; uint32_t power_forceon; - bool block_enabled; - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->idle_optimizations_allowed || + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || pg_cntl->ctx->dc->debug.disable_mem_power_gate; - if (skip_pg && !power_on) + if (block_pg_disabled && !power_on) return; - block_enabled = pg_cntl42_mem_status(pg_cntl); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } - + bool block_enabled = pg_cntl42_mem_status(pg_cntl); + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) + return; REG_GET(DOMAIN23_PG_CONFIG, DOMAIN_POWER_FORCEON, &power_forceon); if (power_forceon) return; @@ -490,22 +470,16 @@ void pg_cntl42_dio_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; struct dcn42_global_fgcg_rep_state fgcg_rep_state = {0}; - bool block_enabled; - bool skip_pg = pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->idle_optimizations_allowed || + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || pg_cntl->ctx->dc->debug.disable_dio_power_gate; - if (skip_pg && !power_on) + + if (block_pg_disabled && !power_on) return; - block_enabled = pg_cntl42_dio_pg_status(pg_cntl); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } + bool block_enabled = pg_cntl42_dio_pg_status(pg_cntl); + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) + return; REG_GET(DC_IP_REQUEST_CNTL, IP_REQUEST_EN, &org_ip_request_cntl); if (org_ip_request_cntl == 0) @@ -531,23 +505,19 @@ void pg_cntl42_plane_otg_pg_control(struct pg_cntl *pg_cntl, bool power_on) uint32_t pwr_status = power_on ? 0 : 2; uint32_t org_ip_request_cntl; unsigned int i; - bool block_enabled; bool all_mpcc_disabled = true, all_opp_disabled = true; bool all_optc_disabled = true, all_stream_disabled = true; - if (pg_cntl->ctx->dc->debug.ignore_pg || - pg_cntl->ctx->dc->debug.disable_optc_power_gate || - pg_cntl->ctx->dc->idle_optimizations_allowed) + bool block_pg_disabled = pg_cntl->ctx->dc->debug.ignore_pg || + pg_cntl->ctx->dc->debug.disable_optc_power_gate; + + if (block_pg_disabled && !power_on) return; - block_enabled = pg_cntl42_plane_otg_status(pg_cntl); - if (power_on) { - if (block_enabled) - return; - } else { - if (!block_enabled) - return; - } + bool block_enabled = pg_cntl42_plane_otg_status(pg_cntl); + + if (should_skip_pg_control(pg_cntl->ctx->dc->idle_optimizations_allowed, power_on, block_enabled)) + return; for (i = 0; i < pg_cntl->ctx->dc->res_pool->pipe_count; i++) { struct pipe_ctx *pipe_ctx = &pg_cntl->ctx->dc->current_state->res_ctx.pipe_ctx[i]; -- cgit v1.2.3 From e82936e8dad0ccbe067323fe7c4e1ae4593104f3 Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Tue, 9 Jun 2026 19:01:13 -0400 Subject: drm/amd/display: Add Debug Option To Enable Per-DPM De-rate Usage [Why] DML has been updated to use per-DPM derates when provided but per-DPM de-rates have not been finalized. Need to validate to see what values should be stored in the bounding box. [How] Add debug options to set custom derates per DPM (starting at DPM0) and their values Each entry in the custom derate expects the derates to be stored in the following format: bits 0-7: dram_derate_percent_pixel bits 8-15: fclk_derate_percent bits 16-23: dcfclk_derate_percent bits 24-31 are unused. e.g. Using the value 0x414020 will set the following derates for DPM0 DPM0: 0x20, 0x40, 0x41 for dram, fclk, and dcfclk respectively Note that global derate value will be used if the per-DPM derate is 0. Reviewed-by: Jun Lei Signed-off-by: Austin Zheng Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 ++ .../dcn401/dcn401_soc_and_ip_translator.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index c2a1f75ae9ae..c628bf8778c9 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1289,6 +1289,8 @@ struct dc_debug_options { bool enable_replay_esd_recovery; uint8_t iommu_mismatch_temp_wka; bool disable_dynamic_expansion_for_test_pattern; + uint32_t dml21_custom_derate_num_dpms; + uint32_t dml21_custom_derate_at_dpm[DML2_MAX_NUM_DPM_LVL]; }; diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c index 89f7ccd7f81f..0c8e652c3532 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c @@ -269,6 +269,22 @@ void dcn401_update_soc_bb_with_values_from_software_policy(struct dml2_soc_bb *s if (dc->bb_overrides.sr_enter_plus_exit_z8_time_ns) soc_bb->power_management_parameters.z8_stutter_enter_plus_exit_latency_us = dc->bb_overrides.sr_enter_plus_exit_z8_time_ns / 1000.0; + + /* Override per-dpm derates based on a custom derate table. + * Global derate value will be used for derates that aren't populated + * 3 derates for a single DPM level: + * bits 0-7: dram_derate_percent_pixel + * bits 8-15: fclk_derate_percent + * bits 16-23: dcfclk_derate_percent + */ + for (unsigned int i = 0; i < dc->debug.dml21_custom_derate_num_dpms; i++) { + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[i] + = dc->debug.dml21_custom_derate_at_dpm[i] & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 8) & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 16) & 0xFF; + } } static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) -- cgit v1.2.3 From a91135995ccd6358d0f6ad203d92aaaeaf16a53f Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Fri, 12 Jun 2026 18:44:35 -0400 Subject: drm/amd/display: [FW Promotion] Release 0.1.64.0 Added panel polarity feature Signed-off-by: Taimur Hassan Signed-off-by: George Zhang Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 111 +++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h index 57f30be6bc9c..e7879bd81f83 100644 --- a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h +++ b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h @@ -246,14 +246,14 @@ * OS/FW agnostic memcpy */ #ifndef dmub_memcpy -#define dmub_memcpy(dest, source, bytes) memcpy((dest), (source), (bytes)) +#define dmub_memcpy(dest, source, bytes) ((void)memcpy((dest), (source), (bytes))) #endif /** * OS/FW agnostic memset */ #ifndef dmub_memset -#define dmub_memset(dest, val, bytes) memset((dest), (val), (bytes)) +#define dmub_memset(dest, val, bytes) ((void)memset((dest), (val), (bytes))) #endif /** @@ -1702,6 +1702,17 @@ enum dmub_gpint_command { * ARGS: 1 - Power off */ DMUB_GPINT__PANEL_POWER_OFF_SEQ = 138, + /** + * DESC: Gets panel polarity bias. + * ARGS: 0 - Get panel polarity bias + */ + DMUB_GPINT__PANEL_POLARITY_GET_BIAS = 139, + /** + * DESC: Enables panel polarity. + * ARGS: 0 - Disable panel polarity + * 1 - Enable panel polarity + */ + DMUB_GPINT__PANEL_POLARITY_DEBUG_ENABLE = 140, }; /** @@ -1956,6 +1967,11 @@ enum dmub_cmd_type { */ DMUB_CMD__BOOT_TIME_CRC = 96, + /** + * Command type use for all Panel Polarity commands. + */ + DMUB_CMD__PANEL_POLARITY = 97, + /** * Command type use for VBIOS shared commands. */ @@ -4365,6 +4381,15 @@ enum dmub_cmd_replay_type { DMUB_CMD__REPLAY_SET_GENERAL_CMD = 16, }; +/* + * Panel Polarity sub-types + */ +enum dmub_cmd_panel_polarity_type { + DMUB_CMD__PANEL_POLARITY_ENABLE = 0, + DMUB_CMD__PANEL_POLARITY_GET_BIAS = 1, + DMUB_CMD__PANEL_POLARITY_RESET = 2, +}; + /* * Panel Replay sub-types */ @@ -7031,6 +7056,80 @@ struct dmub_cmd_pr_enable_data { uint8_t pad[2]; }; +struct dmub_cmd_panel_polarity_enable_data { + /** + * Panel Polarity enable or disable. + */ + uint8_t enable; + /** + * OTG instance + */ + uint8_t otg_inst; + /** + * @pad: Align structure to 4 byte boundary. + */ + uint8_t pad[2]; +}; + +struct dmub_cmd_panel_polarity_reset_data { + /** + * OTG instance + */ + uint8_t otg_inst; + /** + * @pad: Align structure to 4 byte boundary. + */ + uint8_t pad[3]; +}; + +struct dmub_cmd_panel_polarity_get_bias_input { + /** + * OTG instance + */ + uint8_t otg_inst; + uint8_t pad[3]; +}; + +struct dmub_cmd_panel_polarity_get_bias_output { + /** + * Accumulated Polarity Bias + */ + int32_t accumulated_bias; +}; + +struct dmub_rb_cmd_panel_polarity_enable { + /** + * Command header. + */ + struct dmub_cmd_header header; + + struct dmub_cmd_panel_polarity_enable_data data; +}; + + +struct dmub_rb_cmd_panel_polarity_get_bias { + /** + * Command header. + */ + struct dmub_cmd_header header; + + union dmub_cmd_panel_polarity_get_bias_data { + struct dmub_cmd_panel_polarity_get_bias_input input; /**< Input */ + struct dmub_cmd_panel_polarity_get_bias_output output; /**< Output */ + uint32_t output_raw; /**< Raw data output */ + } data; +}; + +struct dmub_rb_cmd_panel_polarity_reset { + /** + * Command header. + */ + struct dmub_cmd_header header; + + struct dmub_cmd_panel_polarity_reset_data data; +}; + + /** * Definition of a DMUB_CMD__PR_ENABLE command. * Panel Replay enable/disable is controlled using action in data. @@ -7646,6 +7745,7 @@ union dmub_rb_cmd { struct dmub_rb_cmd_pr_update_state pr_update_state; struct dmub_rb_cmd_pr_general_cmd pr_general_cmd; + /** * Definition of a DMUB_CMD__IHC command. */ @@ -7654,6 +7754,13 @@ union dmub_rb_cmd { * Definition of a DMUB_CMD__BOOT_TIME_CRC_INIT command. */ struct dmub_rb_cmd_boot_time_crc_init boot_time_crc_init; + + /** + * Definition of a DMUB_CMD__PANEL_POLARITY_ENABLE command. + */ + struct dmub_rb_cmd_panel_polarity_enable panel_polarity_enable; + struct dmub_rb_cmd_panel_polarity_get_bias panel_polarity_get_bias; + struct dmub_rb_cmd_panel_polarity_reset panel_polarity_reset; }; /** -- cgit v1.2.3 From 312c2729b0130fc1629f19eceebbce60aae5c7eb Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Sat, 13 Jun 2026 03:02:41 -0500 Subject: drm/amd/display: Promote DC to 3.2.387 DC Automatic Code Cutoff Signed-off-by: Taimur Hassan Signed-off-by: George Zhang Tested-by: Dan Wheeler Acked-by: George Zhang 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 c628bf8778c9..0e115b1aac5f 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -65,7 +65,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.386" +#define DC_VER "3.2.387" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC -- cgit v1.2.3 From bc1afb9ff985bb806e10eb7d81af974cbe86b57a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 12:10:58 -0600 Subject: drm/amd/display: Remove redundant IPS mode case for DCN 4.2 [WHAT] Remove the redundant IP_VERSION(4, 2, 0) case from dm_get_default_ips_mode() since it only reassigns the same DMUB_IPS_ENABLE value already set at initialization. Also remove the corresponding KUnit test. Reviewed-by: Chenyu Chen Signed-off-by: Alex Hung Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 4 ---- .../amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 18 ------------------ 2 files changed, 22 deletions(-) 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 b4c3371f5757..7519219db0f8 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 @@ -453,10 +453,6 @@ enum dmub_ips_disable_type dm_get_default_ips_mode( case IP_VERSION(3, 5, 1): ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; break; - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - ret = DMUB_IPS_ENABLE; - break; default: /* ASICs older than DCN35 do not have IPSs */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0)) 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 b82dd301a896..bf90ccfbf431 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 @@ -350,23 +350,6 @@ static void dm_test_get_default_ips_mode_dcn36(struct kunit *test) DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); } -/** - * dm_test_get_default_ips_mode_dcn42 - Test Get default ips mode dcn42 - * @test: The KUnit test context - */ -static void dm_test_get_default_ips_mode_dcn42(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(4, 2, 0); - - KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), - DMUB_IPS_DISABLE_ALL); -} - /** * dm_test_get_default_ips_mode_older_than_dcn35 - Test Get default ips mode older than dcn35 * @test: The KUnit test context @@ -572,7 +555,6 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_get_default_ips_mode_dcn35), KUNIT_CASE(dm_test_get_default_ips_mode_dcn351), KUNIT_CASE(dm_test_get_default_ips_mode_dcn36), - KUNIT_CASE(dm_test_get_default_ips_mode_dcn42), KUNIT_CASE(dm_test_get_default_ips_mode_older_than_dcn35), KUNIT_CASE(dm_test_get_default_ips_mode_newer_default), /* dm_dmub_hw_init() */ -- cgit v1.2.3 From 9e0896fa6f7dbe9ca3dbbd3b593fa91670f4820b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 15:01:19 +0200 Subject: drm/amd/display: avoid large stack allocation in commit_planes_do_stream_update_sequence The function has two arrays on the stack to hold temporary dsc_optc_config and dsc_config objects. The combination blows through common stack frame warning limits in combination with the other local variables: drivers/gpu/drm/amd/amdgpu/../display/dc/core/dc.c:4070:22: error: stack frame size (1352) exceeds limit (1280) in 'commit_planes_do_stream_update_sequence' [-Werror,-Wframe-larger-than] Since neither array is initialized or used outside of the add_link_update_dsc_config_sequence() function, there is no actual need to keep each element around. Replace the arrays with a single instance each to reduce the stack usage to less than half. Fixes: 9f49d3cd7e71 ("drm/amd/display: Implement block sequencing infrastructure for modular hardware operations.") Signed-off-by: Arnd Bergmann Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 0e3c27d526c3..0ecb025e76fa 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -4128,8 +4128,6 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, { int j; struct block_sequence_state seq_state = { .steps = block_sequence, .num_steps = num_steps }; - struct dsc_config dsc_cfgs[MAX_PIPES]; - struct dsc_optc_config dsc_optc_cfgs[MAX_PIPES]; unsigned int dsc_cfg_index = 0; *num_steps = 0; // Initialize to 0 @@ -4201,11 +4199,13 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, if (stream_update->dsc_config) if (dsc_cfg_index < MAX_PIPES) { + struct dsc_config dsc_cfg; + struct dsc_optc_config dsc_optc_cfg; + add_link_update_dsc_config_sequence(&seq_state, pipe_ctx, - &dsc_cfgs[dsc_cfg_index], - &dsc_optc_cfgs[dsc_cfg_index]); - dsc_cfg_index++; + &dsc_cfg, + &dsc_optc_cfg); } if (stream_update->mst_bw_update) { -- cgit v1.2.3 From c69657158582a14e057a9e582e48bbc012884d69 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Wed, 10 Jun 2026 18:30:11 -0700 Subject: drm/amd/display: remove check for nonexistent CONFIG_HAVE_KGDB drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h checks for CONFIG_HAVE_KGDB or CONFIG_KGDB to determine whether to call kgdb_breakpoint(). CONFIG_HAVE_KGDB has never existed in the kernel. Remove the check for it and retain only the correct check for CONFIG_KGDB. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h b/drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h index a6f6132df241..a0e9df382582 100644 --- a/drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h +++ b/drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h @@ -5,7 +5,7 @@ #ifndef SPL_DEBUG_H #define SPL_DEBUG_H -#if defined(CONFIG_HAVE_KGDB) || defined(CONFIG_KGDB) +#ifdef CONFIG_KGDB #define SPL_ASSERT_CRITICAL(expr) do { \ if (WARN_ON(!(expr))) { \ kgdb_breakpoint(); \ @@ -17,7 +17,7 @@ ; \ } \ } while (0) -#endif /* CONFIG_HAVE_KGDB || CONFIG_KGDB */ +#endif /* CONFIG_KGDB */ #if defined(CONFIG_DEBUG_KERNEL_DC) #define SPL_ASSERT(expr) SPL_ASSERT_CRITICAL(expr) -- cgit v1.2.3 From 21b62d3a52fe130ae40f4ad361d7ab9663c933b8 Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:01 -0300 Subject: drm/amd/display: add GPIO HW translation helpers Add generic helpers and lookup table types for GPIO hardware translation. The new helpers provide reusable conversions between GPIO IDs, register offsets and DDC lines, allowing ASIC-specific drivers to replace large switch statements with static lookup tables. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/gpio/hw_translate.c | 86 ++++++++++++++++++++++ drivers/gpu/drm/amd/display/dc/gpio/hw_translate.h | 21 ++++++ drivers/gpu/drm/amd/display/include/gpio_types.h | 48 ++++++++++++ 3 files changed, 155 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.c b/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.c index 64a5e11fce5c..b58af86dee10 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.c @@ -133,3 +133,89 @@ bool dal_hw_translate_init( return false; } } + +bool dal_hw_translate_gpio_offset_to_id( + const struct gpio_id_offset_entry *table, + uint32_t table_size, + uint32_t offset, + uint32_t mask, + enum gpio_id *id, + uint32_t *en) +{ + uint32_t i; + + for (i = 0; i < table_size; i++) { + const struct gpio_id_offset_entry *entry = &table[i]; + + if (entry->offset != offset) + continue; + + if (entry->check_mask && entry->mask != mask) + continue; + + *id = entry->id; + *en = entry->en; + + return true; + } + + return false; +} + +/* we don't care about the GPIO_ID for DDC + * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK + * directly in the create method + */ +bool dal_hw_translate_gpio_ddc_offset_to_id( + const struct gpio_ddc_offset_entry *table, + uint32_t table_size, + uint32_t offset, + uint32_t *en) +{ + uint32_t i; + + for (i = 0; i < table_size; i++) { + const struct gpio_ddc_offset_entry *entry = &table[i]; + + if (entry->offset != offset) + continue; + + *en = entry->en; + + return true; + } + + return false; +} + +bool dal_hw_translate_id_to_offset( + const struct gpio_pin_entry *table, + uint32_t table_size, + enum gpio_id id, + uint32_t en, + struct gpio_pin_info *info) +{ + uint32_t i; + + for (i = 0; i < table_size; i++) { + const struct gpio_pin_entry *entry = &table[i]; + + if (entry->id != id || entry->en != en) + continue; + + info->offset = entry->offset; + info->mask = entry->mask; + + info->offset_y = info->offset + 2; + info->offset_en = info->offset + 1; + info->offset_mask = info->offset - 1; + + info->mask_y = info->mask; + info->mask_en = info->mask; + info->mask_mask = info->mask; + + return true; + } + + return false; +} diff --git a/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.h b/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.h index 3a7d89ca1605..339e381f8fde 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.h +++ b/drivers/gpu/drm/amd/display/dc/gpio/hw_translate.h @@ -47,4 +47,25 @@ bool dal_hw_translate_init( enum dce_version dce_version, enum dce_environment dce_environment); +bool dal_hw_translate_gpio_offset_to_id( + const struct gpio_id_offset_entry *table, + uint32_t table_size, + uint32_t offset, + uint32_t mask, + enum gpio_id *id, + uint32_t *en); + +bool dal_hw_translate_gpio_ddc_offset_to_id( + const struct gpio_ddc_offset_entry *table, + uint32_t table_size, + uint32_t offset, + uint32_t *en); + +bool dal_hw_translate_id_to_offset( + const struct gpio_pin_entry *table, + uint32_t table_size, + enum gpio_id id, + uint32_t en, + struct gpio_pin_info *info); + #endif diff --git a/drivers/gpu/drm/amd/display/include/gpio_types.h b/drivers/gpu/drm/amd/display/include/gpio_types.h index 8dd46ed799e5..afd3fc73a911 100644 --- a/drivers/gpu/drm/amd/display/include/gpio_types.h +++ b/drivers/gpu/drm/amd/display/include/gpio_types.h @@ -277,6 +277,49 @@ enum gpio_config_type { GPIO_CONFIG_TYPE_I2C_AUX_DUAL_MODE }; +struct gpio_id_offset_entry { + uint32_t offset; + uint32_t mask; + + bool check_mask; + + enum gpio_id id; + uint32_t en; +}; + +#define GPIO_ENTRY(_offset, _id, _en) \ + { \ + .offset = REG(_offset), \ + .check_mask = false, \ + .id = (_id), \ + .en = (_en), \ + } + +#define GPIO_MASK_ENTRY(_offset, _mask, _id, _en) \ + { \ + .offset = REG(_offset), \ + .mask = (_mask), \ + .check_mask = true, \ + .id = (_id), \ + .en = (_en), \ + } + +struct gpio_pin_entry { + enum gpio_id id; + uint32_t en; + + uint32_t offset; + uint32_t mask; +}; + +#define GPIO_PIN_ENTRY(_id, _en, _offset, _mask) \ + { \ + .id = (_id), \ + .en = (_en), \ + .offset = REG(_offset), \ + .mask = (_mask), \ + } + /* DDC configuration */ enum gpio_ddc_config_type { @@ -293,6 +336,11 @@ struct gpio_ddc_config { bool clock_en_bit_present; }; +struct gpio_ddc_offset_entry { + uint32_t offset; + uint32_t en; +}; + /* HPD configuration */ struct gpio_hpd_config { -- cgit v1.2.3 From f79cb6df29af2e5ee63bdcb297a430ef7bb6e8b2 Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:02 -0300 Subject: drm/amd/display: convert dcn10 GPIO translation to lookup tables Replace dcn10 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/gpio/dcn10/hw_translate_dcn10.c | 484 ++++++++------------- 1 file changed, 173 insertions(+), 311 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn10/hw_translate_dcn10.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn10/hw_translate_dcn10.c index fecc8688048d..000f603def58 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn10/hw_translate_dcn10.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn10/hw_translate_dcn10.c @@ -58,146 +58,180 @@ /* macros to expend register list macro defined in HW object header file * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_G), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK, + GPIO_ID_HPD, GPIO_HPD_6), + /* SYNCA */ + GPIO_MASK_ENTRY(DC_GPIO_SYNCA_A, + DC_GPIO_SYNCA_A__DC_GPIO_HSYNCA_A_MASK, + GPIO_ID_SYNC, GPIO_SYNC_HSYNC_A), + GPIO_MASK_ENTRY(DC_GPIO_SYNCA_A, + DC_GPIO_SYNCA_A__DC_GPIO_VSYNCA_A_MASK, + GPIO_ID_SYNC, GPIO_SYNC_VSYNC_A), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDC6_A), GPIO_DDC_LINE_DDC6 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, + { REG(DC_GPIO_I2CPAD_A), GPIO_DDC_LINE_I2C_PAD }, +}; + +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC6, + DC_GPIO_DDC6_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_I2C_PAD, + DC_GPIO_I2CPAD_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC6, + DC_GPIO_DDC6_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_I2C_PAD, + DC_GPIO_I2CPAD_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_G, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_6, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK), + /* SYNCA */ + GPIO_PIN_ENTRY(GPIO_ID_SYNC, GPIO_SYNC_HSYNC_A, + DC_GPIO_SYNCA_A, DC_GPIO_SYNCA_A__DC_GPIO_HSYNCA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_SYNC, GPIO_SYNC_VSYNC_A, + DC_GPIO_SYNCA_A, DC_GPIO_SYNCA_A__DC_GPIO_VSYNCA_A_MASK), + /* GSL */ + GPIO_PIN_ENTRY(GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK, + DC_GPIO_GENLK_A, DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC, + DC_GPIO_GENLK_A, DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A, + DC_GPIO_GENLK_A, DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B, + DC_GPIO_GENLK_A, DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK), +}; + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK: - *en = GPIO_GENERIC_G; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK: - *en = GPIO_HPD_6; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* SYNCA */ - case REG(DC_GPIO_SYNCA_A): - *id = GPIO_ID_SYNC; - switch (mask) { - case DC_GPIO_SYNCA_A__DC_GPIO_HSYNCA_A_MASK: - *en = GPIO_SYNC_HSYNC_A; - return true; - case DC_GPIO_SYNCA_A__DC_GPIO_VSYNCA_A_MASK: - *en = GPIO_SYNC_VSYNC_A; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; - return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; - return true; - case REG(DC_GPIO_DDC6_A): - *en = GPIO_DDC_LINE_DDC6; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; - return true; - /* GPIO_I2CPAD */ - case REG(DC_GPIO_I2CPAD_A): - *en = GPIO_DDC_LINE_I2C_PAD; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; - /* Not implemented */ - case REG(DC_GPIO_PWRSEQ_A): - case REG(DC_GPIO_PAD_STRENGTH_1): - case REG(DC_GPIO_PAD_STRENGTH_2): - case REG(DC_GPIO_DEBUG): - return false; - /* UNEXPECTED */ - default: - ASSERT_CRITICAL(false); - return false; - } + + ASSERT_CRITICAL(false); + return false; } static bool id_to_offset( @@ -205,186 +239,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC6: - info->offset = REG(DC_GPIO_DDC6_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - info->offset = REG(DC_GPIO_I2CPAD_A); - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC6: - info->offset = REG(DC_GPIO_DDC6_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - info->offset = REG(DC_GPIO_I2CPAD_A); - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - case GPIO_GENERIC_G: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - case GPIO_HPD_6: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - switch (en) { - case GPIO_SYNC_HSYNC_A: - info->offset = REG(DC_GPIO_SYNCA_A); - info->mask = DC_GPIO_SYNCA_A__DC_GPIO_HSYNCA_A_MASK; - break; - case GPIO_SYNC_VSYNC_A: - info->offset = REG(DC_GPIO_SYNCA_A); - info->mask = DC_GPIO_SYNCA_A__DC_GPIO_VSYNCA_A_MASK; - break; - case GPIO_SYNC_HSYNC_B: - case GPIO_SYNC_VSYNC_B: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - info->offset = REG(DC_GPIO_GENLK_A); - info->mask = DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK; - break; - case GPIO_GSL_GENLOCK_VSYNC: - info->offset = REG(DC_GPIO_GENLK_A); - info->mask = - DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK; - break; - case GPIO_GSL_SWAPLOCK_A: - info->offset = REG(DC_GPIO_GENLK_A); - info->mask = DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK; - break; - case GPIO_GSL_SWAPLOCK_B: - info->offset = REG(DC_GPIO_GENLK_A); - info->mask = DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } /* function table */ -- cgit v1.2.3 From fb2f057b2c8cb59ddaa546cebafcba82d810532d Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:03 -0300 Subject: drm/amd/display: convert dcn20 GPIO translation to lookup tables Replace dcn20 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/gpio/dcn20/hw_translate_dcn20.c | 432 ++++++++------------- 1 file changed, 153 insertions(+), 279 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn20/hw_translate_dcn20.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn20/hw_translate_dcn20.c index 3005ee7751a0..a21df8668266 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn20/hw_translate_dcn20.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn20/hw_translate_dcn20.c @@ -62,131 +62,161 @@ * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_G), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK, + GPIO_ID_HPD, GPIO_HPD_6), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDC6_A), GPIO_DDC_LINE_DDC6 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + + +/* + * GSL is intentionally omitted here. + * id_to_offset() for GSL is not implemented on this ASIC. + */ +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC6, + DC_GPIO_DDC6_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC6, + DC_GPIO_DDC6_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_G, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_6, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK), +}; + + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK: - *en = GPIO_GENERIC_G; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK: - *en = GPIO_HPD_6; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method - */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; - return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; - return true; - case REG(DC_GPIO_DDC6_A): - *en = GPIO_DDC_LINE_DDC6; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; -/* - * case REG(DC_GPIO_I2CPAD_A): not exit - * case REG(DC_GPIO_PWRSEQ_A): - * case REG(DC_GPIO_PAD_STRENGTH_1): - * case REG(DC_GPIO_PAD_STRENGTH_2): - * case REG(DC_GPIO_DEBUG): - */ - /* UNEXPECTED */ - default: -/* case REG(DC_GPIO_SYNCA_A): not exist */ - ASSERT_CRITICAL(false); - return false; - } + ASSERT_CRITICAL(false); + return false; } static bool id_to_offset( @@ -194,170 +224,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC6: - info->offset = REG(DC_GPIO_DDC6_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC6: - info->offset = REG(DC_GPIO_DDC6_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - case GPIO_GENERIC_G: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - case GPIO_HPD_6: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_GENLOCK_VSYNC: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_A: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_B: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } /* function table */ -- cgit v1.2.3 From 906757bbcff35f65b1ebdc79cba3c7088ddafac8 Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:04 -0300 Subject: drm/amd/display: convert dcn21 GPIO translation to lookup tables Replace dcn21 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/gpio/dcn21/hw_translate_dcn21.c | 417 ++++++++------------- 1 file changed, 147 insertions(+), 270 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn21/hw_translate_dcn21.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn21/hw_translate_dcn21.c index e3b11b3c1daa..18bd4d4e32d0 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn21/hw_translate_dcn21.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn21/hw_translate_dcn21.c @@ -60,6 +60,135 @@ /* macros to expend register list macro defined in HW object header file * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_G), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK, + GPIO_ID_HPD, GPIO_HPD_6), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + + +/* + * GSL is intentionally omitted here. + * id_to_offset() for GSL is not implemented on this ASIC. + */ +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_G, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_6, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK), +}; + static bool offset_to_id( uint32_t offset, @@ -67,122 +196,20 @@ static bool offset_to_id( enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK: - *en = GPIO_GENERIC_G; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK: - *en = GPIO_HPD_6; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method - */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; - return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; -/* - * case REG(DC_GPIO_I2CPAD_A): not exit - * case REG(DC_GPIO_PWRSEQ_A): - * case REG(DC_GPIO_PAD_STRENGTH_1): - * case REG(DC_GPIO_PAD_STRENGTH_2): - * case REG(DC_GPIO_DEBUG): - */ - /* UNEXPECTED */ - default: -/* case REG(DC_GPIO_SYNCA_A): not exist */ - ASSERT_CRITICAL(false); - return false; - } + ASSERT_CRITICAL(false); + return false; } static bool id_to_offset( @@ -190,164 +217,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC5_A__DC_GPIO_DDC5DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC5_A__DC_GPIO_DDC5CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - case GPIO_GENERIC_G: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - case GPIO_HPD_6: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_GENLOCK_VSYNC: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_A: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_B: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } /* function table */ -- cgit v1.2.3 From 20d41add95d29acaf2eb680126a31921c624cdcb Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:05 -0300 Subject: drm/amd/display: convert dcn30 GPIO translation to lookup tables Replace dcn30 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/gpio/dcn30/hw_translate_dcn30.c | 432 ++++++++------------- 1 file changed, 153 insertions(+), 279 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn30/hw_translate_dcn30.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn30/hw_translate_dcn30.c index 49d6250037a9..c4225231f725 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn30/hw_translate_dcn30.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn30/hw_translate_dcn30.c @@ -67,131 +67,161 @@ * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_G), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK, + GPIO_ID_HPD, GPIO_HPD_6), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDC6_A), GPIO_DDC_LINE_DDC6 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + + +/* + * GSL is intentionally omitted here. + * id_to_offset() for GSL is not implemented on this ASIC. + */ +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC6, + DC_GPIO_DDC6_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC6, + DC_GPIO_DDC6_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_G, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_6, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK), +}; + + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK: - *en = GPIO_GENERIC_G; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK: - *en = GPIO_HPD_6; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method - */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; - return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; - return true; - case REG(DC_GPIO_DDC6_A): - *en = GPIO_DDC_LINE_DDC6; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; -/* - * case REG(DC_GPIO_I2CPAD_A): not exit - * case REG(DC_GPIO_PWRSEQ_A): - * case REG(DC_GPIO_PAD_STRENGTH_1): - * case REG(DC_GPIO_PAD_STRENGTH_2): - * case REG(DC_GPIO_DEBUG): - */ - /* UNEXPECTED */ - default: -/* case REG(DC_GPIO_SYNCA_A): not exist */ - ASSERT_CRITICAL(false); - return false; - } + ASSERT_CRITICAL(false); + return false; } static bool id_to_offset( @@ -199,170 +229,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC6: - info->offset = REG(DC_GPIO_DDC6_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC6: - info->offset = REG(DC_GPIO_DDC6_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - case GPIO_GENERIC_G: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - case GPIO_HPD_6: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_GENLOCK_VSYNC: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_A: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_B: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } /* function table */ -- cgit v1.2.3 From f053749e33d7925a6ae58bac7673de8cd4ee10de Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:06 -0300 Subject: drm/amd/display: convert dcn315 GPIO translation to lookup tables Replace dcn315 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../display/dc/gpio/dcn315/hw_translate_dcn315.c | 418 ++++++++------------- 1 file changed, 148 insertions(+), 270 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn315/hw_translate_dcn315.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn315/hw_translate_dcn315.c index fbdaba57f718..aa507f7f4ef9 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn315/hw_translate_dcn315.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn315/hw_translate_dcn315.c @@ -62,128 +62,156 @@ * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_G), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK, + GPIO_ID_HPD, GPIO_HPD_6), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + + +/* + * GSL is intentionally omitted here. + * id_to_offset() for GSL is not implemented on this ASIC. + */ +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_G, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_6, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK), +}; + + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK: - *en = GPIO_GENERIC_G; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK: - *en = GPIO_HPD_6; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method - */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; - return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; -/* - * case REG(DC_GPIO_I2CPAD_A): not exit - * case REG(DC_GPIO_PWRSEQ_A): - * case REG(DC_GPIO_PAD_STRENGTH_1): - * case REG(DC_GPIO_PAD_STRENGTH_2): - * case REG(DC_GPIO_DEBUG): - */ - /* UNEXPECTED */ - default: -/* case REG(DC_GPIO_SYNCA_A): not exist */ - ASSERT_CRITICAL(false); - return false; - } + ASSERT_CRITICAL(false); + return false; } static bool id_to_offset( @@ -191,164 +219,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - case GPIO_GENERIC_G: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - case GPIO_HPD_6: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_GENLOCK_VSYNC: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_A: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_B: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } /* function table */ -- cgit v1.2.3 From 6688bf379b7ee7c0f94341c135e3c985b52caf69 Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:07 -0300 Subject: drm/amd/display: convert dcn32 GPIO translation to lookup tables Replace dcn32 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/gpio/dcn32/hw_translate_dcn32.c | 386 ++++++++------------- 1 file changed, 138 insertions(+), 248 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn32/hw_translate_dcn32.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn32/hw_translate_dcn32.c index 8493b9981f9e..71067a8da121 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn32/hw_translate_dcn32.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn32/hw_translate_dcn32.c @@ -60,111 +60,145 @@ * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + +/* + * GSL is intentionally omitted here. + * id_to_offset() for GSL is not implemented on this ASIC. + */ +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), +}; + + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; - return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; - default: - ASSERT_CRITICAL(false); - return false; - } + + ASSERT_CRITICAL(false); + return false; } static bool id_to_offset( @@ -172,158 +206,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_GENLOCK_VSYNC: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_A: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_B: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } /* function table */ -- cgit v1.2.3 From 334cbfa3cf0aeb1b51741e1925257a911c646f30 Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:08 -0300 Subject: drm/amd/display: convert dcn401 GPIO translation to lookup tables Replace dcn401 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../display/dc/gpio/dcn401/hw_translate_dcn401.c | 392 +++++++-------------- 1 file changed, 137 insertions(+), 255 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn401/hw_translate_dcn401.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn401/hw_translate_dcn401.c index ea416f01f888..7aa97f09955c 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn401/hw_translate_dcn401.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn401/hw_translate_dcn401.c @@ -35,119 +35,145 @@ * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* GENERIC */ + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_A), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_B), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_C), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_D), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_E), + GPIO_MASK_ENTRY(DC_GPIO_GENERIC_A, + DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK, + GPIO_ID_GENERIC, GPIO_GENERIC_F), + /* HPD */ + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK, + GPIO_ID_HPD, GPIO_HPD_1), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK, + GPIO_ID_HPD, GPIO_HPD_2), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK, + GPIO_ID_HPD, GPIO_HPD_3), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK, + GPIO_ID_HPD, GPIO_HPD_4), + GPIO_MASK_ENTRY(DC_GPIO_HPD_A, + DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK, + GPIO_ID_HPD, GPIO_HPD_5), + /* GSL */ + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_CLOCK), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK, + GPIO_ID_GSL, GPIO_GSL_GENLOCK_VSYNC), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_A), + GPIO_MASK_ENTRY(DC_GPIO_GENLK_A, + DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK, + GPIO_ID_GSL, GPIO_GSL_SWAPLOCK_B), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + + +/* + * GSL is intentionally omitted here. + * id_to_offset() for GSL is not implemented on this ASIC. + */ +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + /* GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + /* GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + /* GENERIC */ + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_A, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_B, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_C, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_D, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_E, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_GENERIC, GPIO_GENERIC_F, + DC_GPIO_GENERIC_A, DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK), + /* HPD */ + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_1, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_2, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_3, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_4, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_HPD, GPIO_HPD_5, + DC_GPIO_HPD_A, DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK), +}; + + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - switch (offset) { - /* GENERIC */ - case REG(DC_GPIO_GENERIC_A): - *id = GPIO_ID_GENERIC; - switch (mask) { - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK: - *en = GPIO_GENERIC_A; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK: - *en = GPIO_GENERIC_B; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK: - *en = GPIO_GENERIC_C; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK: - *en = GPIO_GENERIC_D; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK: - *en = GPIO_GENERIC_E; - return true; - case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK: - *en = GPIO_GENERIC_F; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* HPD */ - case REG(DC_GPIO_HPD_A): - *id = GPIO_ID_HPD; - switch (mask) { - case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK: - *en = GPIO_HPD_1; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK: - *en = GPIO_HPD_2; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK: - *en = GPIO_HPD_3; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK: - *en = GPIO_HPD_4; - return true; - case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK: - *en = GPIO_HPD_5; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* REG(DC_GPIO_GENLK_MASK */ - case REG(DC_GPIO_GENLK_A): - *id = GPIO_ID_GSL; - switch (mask) { - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK: - *en = GPIO_GSL_GENLOCK_CLOCK; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK: - *en = GPIO_GSL_GENLOCK_VSYNC; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK: - *en = GPIO_GSL_SWAPLOCK_A; - return true; - case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK: - *en = GPIO_GSL_SWAPLOCK_B; - return true; - default: - ASSERT_CRITICAL(false); - return false; - } - break; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method - */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; - return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; -/* - * case REG(DC_GPIO_I2CPAD_A): not exit - * case REG(DC_GPIO_PWRSEQ_A): - * case REG(DC_GPIO_PAD_STRENGTH_1): - * case REG(DC_GPIO_PAD_STRENGTH_2): - * case REG(DC_GPIO_DEBUG): - */ - /* UNEXPECTED */ - default: -/* case REG(DC_GPIO_SYNCA_A): not exist */ - ASSERT_CRITICAL(false); - return false; - } + ASSERT_CRITICAL(false); + return false; } @@ -156,158 +182,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; -/* case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; */ - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; -/* case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; */ - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GENERIC: - info->offset = REG(DC_GPIO_GENERIC_A); - switch (en) { - case GPIO_GENERIC_A: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK; - break; - case GPIO_GENERIC_B: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK; - break; - case GPIO_GENERIC_C: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK; - break; - case GPIO_GENERIC_D: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK; - break; - case GPIO_GENERIC_E: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK; - break; - case GPIO_GENERIC_F: - info->mask = DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_HPD: - info->offset = REG(DC_GPIO_HPD_A); - switch (en) { - case GPIO_HPD_1: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK; - break; - case GPIO_HPD_2: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK; - break; - case GPIO_HPD_3: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK; - break; - case GPIO_HPD_4: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK; - break; - case GPIO_HPD_5: - info->mask = DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK; - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_GSL: - switch (en) { - case GPIO_GSL_GENLOCK_CLOCK: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_GENLOCK_VSYNC: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_A: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - break; - case GPIO_GSL_SWAPLOCK_B: - /*not implmented*/ - ASSERT_CRITICAL(false); - result = false; - - break; - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; - return result; + ASSERT_CRITICAL(false); + return false; } -- cgit v1.2.3 From 272e584fc0d831596924130303405b9ed631e5e7 Mon Sep 17 00:00:00 2001 From: Guilherme Ivo Bozi Date: Thu, 11 Jun 2026 16:49:09 -0300 Subject: drm/amd/display: convert dcn42 GPIO translation to lookup tables Replace dcn42 GPIO translation switch statements with the generic table-based translation helpers. This simplifies the GPIO mapping logic and reduces duplicated translation code. No functional changes intended. Signed-off-by: Guilherme Ivo Bozi Tested-by: Dan Wheeler Acked-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/gpio/dcn42/hw_translate_dcn42.c | 193 ++++++++------------- 1 file changed, 70 insertions(+), 123 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c index e7e1d9979876..7b2c4cd42450 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c @@ -39,62 +39,76 @@ * end *********************/ +static const struct gpio_id_offset_entry gpio_offsets[] = { + /* HPD */ + GPIO_ENTRY(HPD0_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_1), + GPIO_ENTRY(HPD1_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_2), + GPIO_ENTRY(HPD2_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_3), + GPIO_ENTRY(HPD3_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_4), + GPIO_ENTRY(HPD4_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_5), +}; + + +/* DDC */ +static const struct gpio_ddc_offset_entry ddc_offset_map[] = { + { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 }, + { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 }, + { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 }, + { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 }, + { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 }, + { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA }, +}; + + +static const struct gpio_pin_entry gpio_pins[] = { + /* DDC */ + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1, + DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2, + DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3, + DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4, + DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5, + DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), + GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA, + DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK), +}; + + static bool offset_to_id( uint32_t offset, uint32_t mask, enum gpio_id *id, uint32_t *en) { - (void)mask; - switch (offset) { - /* HPD */ - case REG(HPD0_DC_HPD_INT_STATUS): - *id = GPIO_ID_HPD; - *en = GPIO_HPD_1; - return true; - case REG(HPD1_DC_HPD_INT_STATUS): - *id = GPIO_ID_HPD; - *en = GPIO_HPD_2; + if (dal_hw_translate_gpio_ddc_offset_to_id( + ddc_offset_map, + ARRAY_SIZE(ddc_offset_map), + offset, en)) return true; - case REG(HPD2_DC_HPD_INT_STATUS): - *id = GPIO_ID_HPD; - *en = GPIO_HPD_3; - return true; - case REG(HPD3_DC_HPD_INT_STATUS): - *id = GPIO_ID_HPD; - *en = GPIO_HPD_4; - return true; - case REG(HPD4_DC_HPD_INT_STATUS): - *id = GPIO_ID_HPD; - *en = GPIO_HPD_5; - return true; - /* DDC */ - /* we don't care about the GPIO_ID for DDC - * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK - * directly in the create method - */ - case REG(DC_GPIO_DDC1_A): - *en = GPIO_DDC_LINE_DDC1; - return true; - case REG(DC_GPIO_DDC2_A): - *en = GPIO_DDC_LINE_DDC2; - return true; - case REG(DC_GPIO_DDC3_A): - *en = GPIO_DDC_LINE_DDC3; - return true; - case REG(DC_GPIO_DDC4_A): - *en = GPIO_DDC_LINE_DDC4; - return true; - case REG(DC_GPIO_DDC5_A): - *en = GPIO_DDC_LINE_DDC5; - return true; - case REG(DC_GPIO_DDCVGA_A): - *en = GPIO_DDC_LINE_DDC_VGA; + + if (dal_hw_translate_gpio_offset_to_id( + gpio_offsets, + ARRAY_SIZE(gpio_offsets), + offset, mask, id, en)) return true; - default: - ASSERT_CRITICAL(false); - return false; - } + + ASSERT_CRITICAL(false); + return false; } @@ -103,81 +117,14 @@ static bool id_to_offset( uint32_t en, struct gpio_pin_info *info) { - bool result = true; - - switch (id) { - case GPIO_ID_DDC_DATA: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_DDC_CLOCK: - info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK; - switch (en) { - case GPIO_DDC_LINE_DDC1: - info->offset = REG(DC_GPIO_DDC1_A); - break; - case GPIO_DDC_LINE_DDC2: - info->offset = REG(DC_GPIO_DDC2_A); - break; - case GPIO_DDC_LINE_DDC3: - info->offset = REG(DC_GPIO_DDC3_A); - break; - case GPIO_DDC_LINE_DDC4: - info->offset = REG(DC_GPIO_DDC4_A); - break; - case GPIO_DDC_LINE_DDC5: - info->offset = REG(DC_GPIO_DDC5_A); - break; - case GPIO_DDC_LINE_DDC_VGA: - info->offset = REG(DC_GPIO_DDCVGA_A); - break; - case GPIO_DDC_LINE_I2C_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - break; - case GPIO_ID_SYNC: - case GPIO_ID_VIP_PAD: - default: - ASSERT_CRITICAL(false); - result = false; - } - - if (result) { - info->offset_y = info->offset + 2; - info->offset_en = info->offset + 1; - info->offset_mask = info->offset - 1; - - info->mask_y = info->mask; - info->mask_en = info->mask; - info->mask_mask = info->mask; - } - - return result; + if (dal_hw_translate_id_to_offset( + gpio_pins, + ARRAY_SIZE(gpio_pins), + id, en, info)) + return true; + + ASSERT_CRITICAL(false); + return false; } -- cgit v1.2.3 From cc80854eda65058a66393c94daccb8f30c2c0f95 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Thu, 11 Jun 2026 11:39:33 -0400 Subject: drm/amdkfd: avoid race condition of mqd when reading sdma counter MQD used outside of dpm_lock is unsafe because the queue could be destroyed during the window of dqm_unlock, moving into dqm_lock range is the best practice. Signed-off-by: Eric Huang Reviewed-by: Harish Kasiviswanathan Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index 303b2b26f1cc..c52a93c66256 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -91,7 +91,6 @@ struct kfd_sdma_activity_handler_workarea { struct temp_sdma_queue_list { uint64_t __user *rptr; - void *mqd; uint64_t sdma_val; unsigned int queue_id; struct list_head list; @@ -154,6 +153,21 @@ static void kfd_sdma_activity_worker(struct work_struct *work) (q->properties.type != KFD_QUEUE_TYPE_SDMA_XGMI)) continue; + if (dqm->dev->kfd2kgd->hqd_sdma_get_counter) { + val = 0; + ret = dqm->dev->kfd2kgd->hqd_sdma_get_counter( + dqm->dev->adev, q->mqd, + dqm->dev->kfd->device_info.num_sdma_queues_per_engine, + &val); + + if (ret) + pr_debug("Failed to read SDMA queue active counter %i\n", ret); + else + workarea->sdma_activity_counter += val; + + continue; + } + sdma_q = kzalloc_obj(struct temp_sdma_queue_list); if (!sdma_q) { dqm_unlock(dqm); @@ -162,7 +176,6 @@ static void kfd_sdma_activity_worker(struct work_struct *work) INIT_LIST_HEAD(&sdma_q->list); sdma_q->rptr = (uint64_t __user *)q->properties.read_ptr; - sdma_q->mqd = q->mqd; sdma_q->queue_id = q->properties.queue_id; list_add_tail(&sdma_q->list, &sdma_q_list.list); } @@ -173,7 +186,7 @@ static void kfd_sdma_activity_worker(struct work_struct *work) * count */ if (list_empty(&sdma_q_list.list)) { - workarea->sdma_activity_counter = pdd->sdma_past_activity_counter; + workarea->sdma_activity_counter += pdd->sdma_past_activity_counter; dqm_unlock(dqm); return; } @@ -191,15 +204,7 @@ static void kfd_sdma_activity_worker(struct work_struct *work) list_for_each_entry(sdma_q, &sdma_q_list.list, list) { val = 0; - - if (dqm->dev->kfd2kgd->hqd_sdma_get_counter) - ret = dqm->dev->kfd2kgd->hqd_sdma_get_counter( - dqm->dev->adev, sdma_q->mqd, - dqm->dev->kfd->device_info.num_sdma_queues_per_engine, - &val); - else - ret = read_sdma_queue_counter(sdma_q->rptr, &val); - + ret = read_sdma_queue_counter(sdma_q->rptr, &val); if (ret) { pr_debug("Failed to read SDMA queue active counter for queue id: %d", sdma_q->queue_id); -- cgit v1.2.3 From 26373c71945544bceed6e08eede8100c97be74fa Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 22 Jun 2026 09:19:14 -0700 Subject: drm/amdgpu: don't free standalone ip_discovery sysfs in sysfs_fini The standalone_mode ip_discovery sysfs hierarchy is tied to the PCI device lifetime and tracked in early_ip_discovery_list. It is torn down only by amdgpu_discovery_sysfs_early_fini() on driver unbind, which is why amdgpu_discovery_fini() already guards its teardown with !standalone_mode. Commit 7de02fe95312 ("drm/amdgpu: clean up discovery and preempt sysfs entries on shutdown") added an unconditional amdgpu_discovery_sysfs_fini() call in amdgpu_device_sys_interface_fini(), which runs during amdgpu_device_fini_hw() on every unbind/reload. On reload this freed the PCI-device-owned ip_top via kobject_put()->ip_disc_release()->kfree(), leaving a dangling pointer in early_ip_discovery_list. The subsequent amdgpu_discovery_sysfs_early_fini() then dereferenced and put the freed object, causing a use-after-free and double-free, and prematurely destroyed the sysfs that was meant to persist across reloads. Make amdgpu_discovery_sysfs_fini() skip standalone_mode objects so the invariant is centralized at the teardown site and the new call site cannot free the PCI-device-owned ip_top. Teardown of standalone sysfs remains the sole responsibility of amdgpu_discovery_sysfs_early_fini(). Fixes: 7de02fe95312 ("drm/amdgpu: clean up discovery and preempt sysfs entries on shutdown") Acked-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index e0cf6848ab7c..5605bc42ffc1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -1489,6 +1489,15 @@ void amdgpu_discovery_sysfs_fini(struct amdgpu_device *adev) if (!ip_top) return; + /* + * In standalone mode the sysfs hierarchy is tied to the PCI device + * lifetime and is torn down by amdgpu_discovery_sysfs_early_fini(). + * Freeing it here would leave a dangling pointer in the early + * discovery list, causing a use-after-free on driver unbind. + */ + if (ip_top->standalone_mode) + return; + adev->discovery.ip_top = NULL; die_kset = &ip_top->die_kset; spin_lock(&die_kset->list_lock); -- cgit v1.2.3 From 0a06bc174f07753526a680a541515eb2391cdbca Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Tue, 23 Jun 2026 17:58:57 +0200 Subject: drm/amd/display: use GAMCOR for degamma private props in subsampled format When setting plane degamma TF via AMD driver-specific color properties, the driver uses PRE_DEGAM color block (ROM). However, this block cannot be used with subsampled formats as it affects the linearity of color space in which HW scaler operates. For subsampled format, use the AMD color module to map plane degamma predefined curve to LUT and use GAMCOR block instead (RAM). This is based on Harry's implementation for Fixed Matrix Colorop. Link: https://lore.kernel.org/dri-devel/20260330153451.99472-1-harry.wentland@amd.com/ Co-developed-by: Harry Wentland Signed-off-by: Harry Wentland Tested-by: Matthew Schwartz Reviewed-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 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 9bcb73c95fef..357c7c5c85cf 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 @@ -1469,7 +1469,7 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, const struct drm_color_lut *degamma_lut; enum amdgpu_transfer_function tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; uint32_t degamma_size; - bool has_degamma_lut; + bool has_degamma_lut, is_subsampled_format; int ret; degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, @@ -1499,12 +1499,20 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, if (ret) return ret; } else { - dc_plane_state->in_transfer_func.type = - TF_TYPE_PREDEFINED; + /* Check if format requires post-scale color processing (subsampled formats) */ + is_subsampled_format = (dc_plane_state->format >= SURFACE_PIXEL_FORMAT_VIDEO_BEGIN && + dc_plane_state->format < SURFACE_PIXEL_FORMAT_SUBSAMPLE_END); + + dc_plane_state->in_transfer_func.type = TF_TYPE_PREDEFINED; if (!mod_color_calculate_degamma_params(color_caps, - &dc_plane_state->in_transfer_func, NULL, false)) + &dc_plane_state->in_transfer_func, + NULL, + is_subsampled_format)) { + drm_err(plane_state->state->dev, + "Failed to calculate degamma params.\n"); return -ENOMEM; + } } return 0; } -- cgit v1.2.3 From e0378c21fc431b125e10d2f81037af8340273ca7 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Tue, 23 Jun 2026 17:58:58 +0200 Subject: Revert "drm/amd/display: Remove unused cm3_helper_translate_curve_to_degamma_hw_format" This reverts commit 8b89acc0b2baecfe331f5336e7ff1fcc5a44b062. So that we can detach NL->L LUT programming from L->NL one, i.e., we can use cm3_helper_translate_curve_to_degamma_hw_format for plane degamma and blend (post-3DLUT curve) and cm3_helper_translate_curve_to_hw_format for plane shaper (pre-3DLUT curve) and stream regamma. Tested-by: Matthew Schwartz Reviewed-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c | 151 +++++++++++++++++++++ .../drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h | 4 + 2 files changed, 155 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index bfd5515c2f4f..0949b1dffc63 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -303,6 +303,157 @@ bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, return true; } +#define NUM_DEGAMMA_REGIONS 12 + + +bool cm3_helper_translate_curve_to_degamma_hw_format( + const struct dc_transfer_func *output_tf, + struct pwl_params *lut_params) +{ + struct curve_points3 *corner_points; + struct pwl_result_data *rgb_resulted; + struct pwl_result_data *rgb; + struct pwl_result_data *rgb_plus_1; + + int32_t region_start, region_end; + int32_t i; + uint32_t j, k, seg_distr[MAX_REGIONS_NUMBER], increment, start_index, hw_points; + + if (output_tf == NULL || lut_params == NULL || output_tf->type == TF_TYPE_BYPASS) + return false; + + corner_points = lut_params->corner_points; + rgb_resulted = lut_params->rgb_resulted; + hw_points = 0; + + memset(lut_params, 0, sizeof(struct pwl_params)); + memset(seg_distr, 0, sizeof(seg_distr)); + + region_start = -NUM_DEGAMMA_REGIONS; + region_end = 0; + + + for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) + seg_distr[i] = -1; + /* 12 segments + * segments are from 2^-12 to 0 + */ + for (i = 0; i < NUM_DEGAMMA_REGIONS ; i++) + seg_distr[i] = 4; + + for (k = 0; k < MAX_REGIONS_NUMBER; k++) { + if (seg_distr[k] != -1) + hw_points += (1 << seg_distr[k]); + } + + j = 0; + for (k = 0; k < (region_end - region_start); k++) { + increment = NUMBER_SW_SEGMENTS / (1 << seg_distr[k]); + start_index = (region_start + k + MAX_LOW_POINT) * + NUMBER_SW_SEGMENTS; + for (i = start_index; i < start_index + NUMBER_SW_SEGMENTS; + i += increment) { + if (j == hw_points - 1) + break; + if (i >= TRANSFER_FUNC_POINTS) + return false; + rgb_resulted[j].red = output_tf->tf_pts.red[i]; + rgb_resulted[j].green = output_tf->tf_pts.green[i]; + rgb_resulted[j].blue = output_tf->tf_pts.blue[i]; + j++; + } + } + + /* last point */ + start_index = (region_end + MAX_LOW_POINT) * NUMBER_SW_SEGMENTS; + rgb_resulted[hw_points - 1].red = output_tf->tf_pts.red[start_index]; + rgb_resulted[hw_points - 1].green = output_tf->tf_pts.green[start_index]; + rgb_resulted[hw_points - 1].blue = output_tf->tf_pts.blue[start_index]; + + corner_points[0].red.x = dc_fixpt_pow(dc_fixpt_from_int(2), + dc_fixpt_from_int(region_start)); + corner_points[0].green.x = corner_points[0].red.x; + corner_points[0].blue.x = corner_points[0].red.x; + corner_points[1].red.x = dc_fixpt_pow(dc_fixpt_from_int(2), + dc_fixpt_from_int(region_end)); + corner_points[1].green.x = corner_points[1].red.x; + corner_points[1].blue.x = corner_points[1].red.x; + + corner_points[0].red.y = rgb_resulted[0].red; + corner_points[0].green.y = rgb_resulted[0].green; + corner_points[0].blue.y = rgb_resulted[0].blue; + + /* see comment above, m_arrPoints[1].y should be the Y value for the + * region end (m_numOfHwPoints), not last HW point(m_numOfHwPoints - 1) + */ + corner_points[1].red.y = rgb_resulted[hw_points - 1].red; + corner_points[1].green.y = rgb_resulted[hw_points - 1].green; + corner_points[1].blue.y = rgb_resulted[hw_points - 1].blue; + corner_points[1].red.slope = dc_fixpt_zero; + corner_points[1].green.slope = dc_fixpt_zero; + corner_points[1].blue.slope = dc_fixpt_zero; + + if (output_tf->tf == TRANSFER_FUNCTION_PQ) { + /* for PQ, we want to have a straight line from last HW X point, + * and the slope to be such that we hit 1.0 at 10000 nits. + */ + const struct fixed31_32 end_value = + dc_fixpt_from_int(125); + + corner_points[1].red.slope = dc_fixpt_div( + dc_fixpt_sub(dc_fixpt_one, corner_points[1].red.y), + dc_fixpt_sub(end_value, corner_points[1].red.x)); + corner_points[1].green.slope = dc_fixpt_div( + dc_fixpt_sub(dc_fixpt_one, corner_points[1].green.y), + dc_fixpt_sub(end_value, corner_points[1].green.x)); + corner_points[1].blue.slope = dc_fixpt_div( + dc_fixpt_sub(dc_fixpt_one, corner_points[1].blue.y), + dc_fixpt_sub(end_value, corner_points[1].blue.x)); + } + + lut_params->hw_points_num = hw_points; + + k = 0; + for (i = 1; i < MAX_REGIONS_NUMBER; i++) { + if (seg_distr[k] != -1) { + lut_params->arr_curve_points[k].segments_num = + seg_distr[k]; + lut_params->arr_curve_points[i].offset = + lut_params->arr_curve_points[k].offset + (1 << seg_distr[k]); + } + k++; + } + + if (seg_distr[k] != -1) + lut_params->arr_curve_points[k].segments_num = seg_distr[k]; + + rgb = rgb_resulted; + rgb_plus_1 = rgb_resulted + 1; + + i = 1; + while (i != hw_points + 1) { + if (dc_fixpt_lt(rgb_plus_1->red, rgb->red)) + rgb_plus_1->red = rgb->red; + if (dc_fixpt_lt(rgb_plus_1->green, rgb->green)) + rgb_plus_1->green = rgb->green; + if (dc_fixpt_lt(rgb_plus_1->blue, rgb->blue)) + rgb_plus_1->blue = rgb->blue; + + rgb->delta_red = dc_fixpt_sub(rgb_plus_1->red, rgb->red); + rgb->delta_green = dc_fixpt_sub(rgb_plus_1->green, rgb->green); + rgb->delta_blue = dc_fixpt_sub(rgb_plus_1->blue, rgb->blue); + + ++rgb_plus_1; + ++rgb; + ++i; + } + cm3_helper_convert_to_custom_float(rgb_resulted, + lut_params->corner_points, + hw_points, false); + + return true; +} + bool cm3_helper_convert_to_custom_float( struct pwl_result_data *rgb_resulted, struct curve_points3 *corner_points, diff --git a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h index 95f9318a54ef..c23dc1bb29bf 100644 --- a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h +++ b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h @@ -63,6 +63,10 @@ bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, const struct dc_transfer_func *output_tf, struct pwl_params *lut_params, bool fixpoint); +bool cm3_helper_translate_curve_to_degamma_hw_format( + const struct dc_transfer_func *output_tf, + struct pwl_params *lut_params); + bool cm3_helper_convert_to_custom_float( struct pwl_result_data *rgb_resulted, struct curve_points3 *corner_points, -- cgit v1.2.3 From 222e63bddae5e828b1e7d0e520a4b38685b1f96c Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Tue, 23 Jun 2026 17:58:59 +0200 Subject: drm/amd/display: use a separate helper to translate degamma curves In newer DCN families, there is no hw predefined curves for shaper, blend and regamma. When userspace sets pre-defined curves for these blocks, the driver uses AMD color module to program predefined curve as LUT. However, it was using the same LUT segmentation for EOTF and inverse EOTF by using the same color management helper. This is causing banding on blend when PQ predefined curve is set. Besides that, degamma predefined HW curve cannot be used with subsampled 4:2:0/4:2:2 formats as it affects the linearity of color space in which HW scaler operates. To mitigate banding when using the blend block and better support subsampled format on degamma, use different translation helpers when linearizing and delinearizing. Tested-by: Matthew Schwartz Reviewed-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 1340f673ec3b..c2ea25927765 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 @@ -493,11 +493,9 @@ bool dcn32_set_mcm_luts( if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) lut_params = &plane_state->cm.blend_func.pwl; else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { - result = cm3_helper_translate_curve_to_hw_format( - plane_state->ctx, + result = cm3_helper_translate_curve_to_degamma_hw_format( &plane_state->cm.blend_func, - &dpp_base->regamma_params, - false); + &dpp_base->regamma_params); if (!result) return result; @@ -554,9 +552,8 @@ bool dcn32_set_input_transfer_func(struct dc *dc, if (plane_state->in_transfer_func.type == TF_TYPE_HWPWL) params = &plane_state->in_transfer_func.pwl; else if (plane_state->in_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_transfer_func, - &dpp_base->degamma_params, false)) + cm3_helper_translate_curve_to_degamma_hw_format(&plane_state->in_transfer_func, + &dpp_base->degamma_params)) params = &dpp_base->degamma_params; dpp_base->funcs->dpp_program_gamcor_lut(dpp_base, params); -- cgit v1.2.3 From 619e5b7e453a7f7416474250a92d19d704feb552 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Tue, 23 Jun 2026 17:59:00 +0200 Subject: drm/amd/display: support up to 256 samples per region in degamma/blend LUT cm3_helper_translate_curve_to_degamma_hw_format() reads one tf_pts entry per HW LUT point, limiting the number of samples per region to NUMBER_SW_SEGMENTS (16, at seg_distr[k] = 4) - higher seg_distr[k] underflows the increment to 0. But the next patch introduces a halving distribution for PQ/sRGB EOTFs that requires up to 128 samples in its upper region (seg_distr[k] = 7). As preparation, extend the loop index by 4 bits and linearly interpolate adjacent tf_pts entries with the new interp_tf_pts() helper, where the 4 least significant bits are weight in 1/16 increments. This raises the cap to 256 samples per region (seg_distr[k] = 8). seg_distr[k] <= 4 paths remain unchanged: the 4 least significant bits remain zero and interp_tf_pts() reduces to a direct lookup. Tested-by: Matthew Schwartz Reviewed-by: Harry Wentland Co-developed-by: Harry Wentland Signed-off-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c | 32 +++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index 0949b1dffc63..70b7bc3494a2 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -305,6 +305,22 @@ bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, #define NUM_DEGAMMA_REGIONS 12 +/* Linear interpolation of tf_pts entries, where (i >> 4) is the integer tf_pts + * index, (i & 0xf) is the 1/16 sub-position. + */ +static struct fixed31_32 interp_tf_pts(const struct fixed31_32 *output_tf_channel, int i) +{ + struct fixed31_32 in_plus_one, in, value; + uint32_t t = i & 0xf; + + in_plus_one = output_tf_channel[(i >> 4) + 1]; + in = output_tf_channel[i >> 4]; + value = dc_fixpt_sub(in_plus_one, in); + value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); + value = dc_fixpt_add(in, value); + + return value; +} bool cm3_helper_translate_curve_to_degamma_hw_format( const struct dc_transfer_func *output_tf, @@ -348,18 +364,20 @@ bool cm3_helper_translate_curve_to_degamma_hw_format( j = 0; for (k = 0; k < (region_end - region_start); k++) { - increment = NUMBER_SW_SEGMENTS / (1 << seg_distr[k]); + increment = (NUMBER_SW_SEGMENTS << 4) / (1 << seg_distr[k]); start_index = (region_start + k + MAX_LOW_POINT) * NUMBER_SW_SEGMENTS; - for (i = start_index; i < start_index + NUMBER_SW_SEGMENTS; - i += increment) { + for (i = (start_index << 4); + i < (start_index << 4) + (NUMBER_SW_SEGMENTS << 4); + i += increment) { if (j == hw_points - 1) break; - if (i >= TRANSFER_FUNC_POINTS) + if ((i >> 4) + 1 >= TRANSFER_FUNC_POINTS) return false; - rgb_resulted[j].red = output_tf->tf_pts.red[i]; - rgb_resulted[j].green = output_tf->tf_pts.green[i]; - rgb_resulted[j].blue = output_tf->tf_pts.blue[i]; + + rgb_resulted[j].red = interp_tf_pts(output_tf->tf_pts.red, i); + rgb_resulted[j].green = interp_tf_pts(output_tf->tf_pts.green, i); + rgb_resulted[j].blue = interp_tf_pts(output_tf->tf_pts.blue, i); j++; } } -- cgit v1.2.3 From a71d2b051f334d1f36ba113bcd8dab69fbb37212 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Tue, 23 Jun 2026 17:59:01 +0200 Subject: drm/amd/display: use halving distribution for PQ/sRGB linearizing LUT When linearizing, the input is an encoded signal bounded to [0,1] and PQ/sRGB EOTFs are steepest near 1, requiring more precision near the bright end. Take the 8-bit sRGB case as a reference: 256 possible inputs and 256 HW LUT points line up, so the LUT acts as plain indexing. Float representations don't land perfectly, but LERP-ing between two HW entries, when input is within a small epsilon of one of them, doesn't materially change the result. Replace the uniform 12-region distribution (16 points each, 192 total, range [2^-12, 1]) with a 9-region halving distribution for the PQ/sRGB pre-defined EOTF: 128 points in the top region [0.5, 1], 64 in the next, 32 in the next, and so on, down to 1 point in each of the two darkest regions. Total samples grow from 192 to 256, with uniform 1/256 spacing across [0, 1]. The dark tail below 2^-9 is no longer sampled separately, which is acceptable for PQ/sRGB. Suggested-by: Krunoslav Kovac Tested-by: Matthew Schwartz Reviewed-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c | 33 ++++++++++++++++------ 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index 70b7bc3494a2..66fe7f313ea3 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -303,8 +303,6 @@ bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, return true; } -#define NUM_DEGAMMA_REGIONS 12 - /* Linear interpolation of tf_pts entries, where (i >> 4) is the integer tf_pts * index, (i & 0xf) is the 1/16 sub-position. */ @@ -345,17 +343,34 @@ bool cm3_helper_translate_curve_to_degamma_hw_format( memset(lut_params, 0, sizeof(struct pwl_params)); memset(seg_distr, 0, sizeof(seg_distr)); - region_start = -NUM_DEGAMMA_REGIONS; - region_end = 0; + if (output_tf->tf == TRANSFER_FUNCTION_PQ || + output_tf->tf == TRANSFER_FUNCTION_SRGB) { + /* 9 segments + * segments are from 2^-9 to 0 + */ + const uint8_t SEG_COUNT = 9; + seg_distr[0] = 0; // Since we only have one point in darkest region + for (k = 1; k < SEG_COUNT; k++) + seg_distr[k] = k - 1; // 2^(k-1) points per region; halves as k decreases + + region_start = -SEG_COUNT; + region_end = 0; + } else { + /* 12 segments + * segments are from 2^-12 to 2^0 + * There are less than 256 points, for optimization + */ + const uint8_t SEG_COUNT = 12; + + for (i = 0; i < SEG_COUNT; i++) + seg_distr[i] = 4; + region_start = -SEG_COUNT; + region_end = 0; + } for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) seg_distr[i] = -1; - /* 12 segments - * segments are from 2^-12 to 0 - */ - for (i = 0; i < NUM_DEGAMMA_REGIONS ; i++) - seg_distr[i] = 4; for (k = 0; k < MAX_REGIONS_NUMBER; k++) { if (seg_distr[k] != -1) -- cgit v1.2.3 From 98073e4328d7a8d75d03696ab27f6de70ef1aeda Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 22 Jun 2026 23:05:09 +0800 Subject: drm/amdgpu: fix resource leak on ACP reset timeout When ACP soft reset poll times out, original code returns early without cleanup, leaking MFD child devices, genpd links and all ACP heap allocations. Replace direct early return with goto out to force run all cleanup logic regardless of reset success, preserve timeout error code for caller. Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c index 4c732e0f776e..f04b2d63c59a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -508,6 +508,7 @@ static int acp_hw_fini(struct amdgpu_ip_block *ip_block) u32 val = 0; u32 count = 0; struct amdgpu_device *adev = ip_block->adev; + int ret = 0; /* return early if no ACP */ if (!adev->acp.acp_genpd) { @@ -529,7 +530,8 @@ static int acp_hw_fini(struct amdgpu_ip_block *ip_block) break; if (--count == 0) { dev_err(&adev->pdev->dev, "Failed to reset ACP\n"); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto out; } udelay(100); } @@ -546,11 +548,12 @@ static int acp_hw_fini(struct amdgpu_ip_block *ip_block) break; if (--count == 0) { dev_err(&adev->pdev->dev, "Failed to reset ACP\n"); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto out; } udelay(100); } - +out: device_for_each_child(adev->acp.parent, NULL, acp_genpd_remove_device); @@ -560,7 +563,7 @@ static int acp_hw_fini(struct amdgpu_ip_block *ip_block) kfree(adev->acp.acp_genpd); kfree(adev->acp.acp_cell); - return 0; + return ret; } static int acp_suspend(struct amdgpu_ip_block *ip_block) -- cgit v1.2.3 From cd8650d7a91ee8b768e202354672553faa5cc1f2 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 22 Jun 2026 22:58:16 +0800 Subject: drm/amdgpu: invoke pm_genpd_remove() before freeing genpd Call pm_genpd_remove() to unregister from global list prior to releasing acp_genpd memory, and clear the pointer after free. Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c index f04b2d63c59a..9014678d75ab 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -560,7 +560,9 @@ out: mfd_remove_devices(adev->acp.parent); kfree(adev->acp.i2s_pdata); kfree(adev->acp.acp_res); + pm_genpd_remove(&adev->acp.acp_genpd->gpd); kfree(adev->acp.acp_genpd); + adev->acp.acp_genpd = NULL; kfree(adev->acp.acp_cell); return ret; -- cgit v1.2.3 From 47862766d211e7a6e9c75254182453c23fc5ad1a Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 9 Jun 2026 10:00:56 +0800 Subject: drm/amdgpu/gfx12: handle error interrupts for userqs Call the new userq reset helper, and dispatch KQs first by ring_id before falling back to the user-queue lookup. v2: squash in fixes Co-developed-by: Alex Deucher Signed-off-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index daecc4a5d90d..dad3609992b7 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5008,22 +5008,30 @@ static int gfx_v12_0_set_priv_inst_fault_state(struct amdgpu_device *adev, static void gfx_v12_0_handle_priv_fault(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { - u8 me_id, pipe_id, queue_id; - struct amdgpu_ring *ring; - int i; - - me_id = (entry->ring_id & 0x0c) >> 2; - pipe_id = (entry->ring_id & 0x03) >> 0; - queue_id = (entry->ring_id & 0x70) >> 4; + u32 doorbell_offset = entry->src_data[0] & AMDGPU_CTXID0_DOORBELL_ID_MASK; + /* + * Try KQ first by ring_id; UQ as fallback. KCQ and UQ never share + * a HW slot (compute_hqd_mask contract). + */ if (!adev->gfx.disable_kq) { + u8 me_id, pipe_id, queue_id; + struct amdgpu_ring *ring; + int i; + + me_id = (entry->ring_id & 0x0c) >> 2; + pipe_id = (entry->ring_id & 0x03) >> 0; + queue_id = (entry->ring_id & 0x70) >> 4; + switch (me_id) { case 0: for (i = 0; i < adev->gfx.num_gfx_rings; i++) { ring = &adev->gfx.gfx_ring[i]; if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) + ring->queue == queue_id) { drm_sched_fault(&ring->sched); + return; + } } break; case 1: @@ -5031,8 +5039,10 @@ static void gfx_v12_0_handle_priv_fault(struct amdgpu_device *adev, for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) + ring->queue == queue_id) { drm_sched_fault(&ring->sched); + return; + } } break; default: @@ -5040,6 +5050,11 @@ static void gfx_v12_0_handle_priv_fault(struct amdgpu_device *adev, break; } } + + /* No KQ matched: HW slot is a MES-scheduled user queue. */ + if (adev->enable_mes && doorbell_offset) + amdgpu_userq_process_reset_irq(adev, entry->pasid, + doorbell_offset); } static int gfx_v12_0_priv_reg_irq(struct amdgpu_device *adev, -- cgit v1.2.3 From 88e589cc811ba907209a426c426c469bcb4bb894 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 11 Jun 2026 10:14:32 +0800 Subject: drm/amdgpu/gfx11: fix EOP interrupt routing for KQ and userq Try KQ by ring_id first (KCQ and UQ never share a HW slot); fall back to amdgpu_userq_process_fence_irq() on miss, since KQ EOPs were misrouted into the userq fence path when enable_mes is true. Require a strict (me,pipe,queue) match in the gfx case, then userq gfx EOPs fall through to amdgpu_userq_process_fence_irq(). Suggested-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 43 +++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 30cead1f69d8..b08a0aa5e22b 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6488,25 +6488,33 @@ static int gfx_v11_0_eop_irq(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { u32 doorbell_offset = entry->src_data[0]; - u8 me_id, pipe_id, queue_id; - struct amdgpu_ring *ring; - int i; DRM_DEBUG("IH: CP EOP\n"); - if (adev->enable_mes && doorbell_offset) { - amdgpu_userq_process_fence_irq(adev, doorbell_offset); - } else { - me_id = (entry->ring_id & 0x0c) >> 2; - pipe_id = (entry->ring_id & 0x03) >> 0; - queue_id = (entry->ring_id & 0x70) >> 4; + if (!adev->gfx.disable_kq) { + u8 me_id = (entry->ring_id & 0x0c) >> 2; + u8 pipe_id = (entry->ring_id & 0x03) >> 0; + u8 queue_id = (entry->ring_id & 0x70) >> 4; + struct amdgpu_ring *ring; + int i; switch (me_id) { case 0: - if (pipe_id == 0) - amdgpu_fence_process(&adev->gfx.gfx_ring[0]); - else - amdgpu_fence_process(&adev->gfx.gfx_ring[1]); + /* + * MES splits gfx HQDs per (me,pipe): KGQ owns queue=0, + * userq gfx owns queue>=1 (see amdgpu_mes_get_hqd_mask). + * Require a strict (me,pipe,queue) match so userq gfx + * EOPs fall through to amdgpu_userq_process_fence_irq(). + */ + for (i = 0; i < adev->gfx.num_gfx_rings; i++) { + ring = &adev->gfx.gfx_ring[i]; + if ((ring->me == me_id) && + (ring->pipe == pipe_id) && + (ring->queue == queue_id)) { + amdgpu_fence_process(ring); + return 0; + } + } break; case 1: case 2: @@ -6518,13 +6526,20 @@ static int gfx_v11_0_eop_irq(struct amdgpu_device *adev, */ if ((ring->me == me_id) && (ring->pipe == pipe_id) && - (ring->queue == queue_id)) + (ring->queue == queue_id)) { amdgpu_fence_process(ring); + return 0; + } } break; + default: + break; } } + if (adev->enable_mes && doorbell_offset) + amdgpu_userq_process_fence_irq(adev, doorbell_offset); + return 0; } -- cgit v1.2.3 From 6c1f4f7ff08448e0e18cd7fc4e59d6c96a36f25d Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 11 Jun 2026 10:26:04 +0800 Subject: drm/amdgpu/gfx12: fix EOP interrupt routing for KQ and userq Try KQ by ring_id first (KCQ and UQ never share a HW slot); fall back to amdgpu_userq_process_fence_irq() on miss, since KCQ EOPs were misrouted into the userq fence path when enable_mes is true. Require a strict (me,pipe,queue) match in the gfx case, then userq gfx EOPs fall through to amdgpu_userq_process_fence_irq(). Suggested-by: Alex Deucher Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 43 +++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index dad3609992b7..cd6c1b6f8894 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -4842,25 +4842,33 @@ static int gfx_v12_0_eop_irq(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { u32 doorbell_offset = entry->src_data[0]; - u8 me_id, pipe_id, queue_id; - struct amdgpu_ring *ring; - int i; DRM_DEBUG("IH: CP EOP\n"); - if (adev->enable_mes && doorbell_offset) { - amdgpu_userq_process_fence_irq(adev, doorbell_offset); - } else { - me_id = (entry->ring_id & 0x0c) >> 2; - pipe_id = (entry->ring_id & 0x03) >> 0; - queue_id = (entry->ring_id & 0x70) >> 4; + if (!adev->gfx.disable_kq) { + u8 me_id = (entry->ring_id & 0x0c) >> 2; + u8 pipe_id = (entry->ring_id & 0x03) >> 0; + u8 queue_id = (entry->ring_id & 0x70) >> 4; + struct amdgpu_ring *ring; + int i; switch (me_id) { case 0: - if (pipe_id == 0) - amdgpu_fence_process(&adev->gfx.gfx_ring[0]); - else - amdgpu_fence_process(&adev->gfx.gfx_ring[1]); + /* + * MES splits gfx HQDs per (me,pipe): KGQ owns queue=0, + * userq gfx owns queue>=1 (see amdgpu_mes_get_hqd_mask). + * Require a strict (me,pipe,queue) match so userq gfx + * EOPs fall through to amdgpu_userq_process_fence_irq(). + */ + for (i = 0; i < adev->gfx.num_gfx_rings; i++) { + ring = &adev->gfx.gfx_ring[i]; + if ((ring->me == me_id) && + (ring->pipe == pipe_id) && + (ring->queue == queue_id)) { + amdgpu_fence_process(ring); + return 0; + } + } break; case 1: case 2: @@ -4872,13 +4880,20 @@ static int gfx_v12_0_eop_irq(struct amdgpu_device *adev, */ if ((ring->me == me_id) && (ring->pipe == pipe_id) && - (ring->queue == queue_id)) + (ring->queue == queue_id)) { amdgpu_fence_process(ring); + return 0; + } } break; + default: + break; } } + if (adev->enable_mes && doorbell_offset) + amdgpu_userq_process_fence_irq(adev, doorbell_offset); + return 0; } -- cgit v1.2.3 From a518bbe5315f11e484a88fb2eb9efa749aeb9eb5 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Tue, 23 Jun 2026 23:36:42 -0400 Subject: Revert "drm/amdkfd: Add queue reset support to gfx12.0" This reverts commit 96d745011842e906774aa8523abb78775b008a4e. This patch didn't exclude SRIOV Signed-off-by: Amber Lin Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index af1249165bdb..82b69c9d5007 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2027,15 +2027,14 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) !amdgpu_sriov_vf(dev->gpu->adev)) dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; - if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) { + if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_ALU_OPERATIONS_SUPPORTED; - dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; - } if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 1, 0)) { dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_MEMORY_OPERATIONS_SUPPORTED; + dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; dev->node_props.capability2 |= HSA_CAP2_TRAP_DEBUG_LDS_OUT_OF_ADDR_RANGE_SUPPORTED; } -- cgit v1.2.3 From d41624990ef66b410e95b7fc89b3727a0d297906 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Tue, 23 Jun 2026 10:03:16 -0400 Subject: drm/amdkfd: Add gfx12.0 queue reset support to topology This adds queue reset support in KFD topology for gfx12.0.0 and gfx12.0.1 on non-sriov mode. Signed-off-by: Amber Lin Reviewed-by: Shaoyun Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 82b69c9d5007..35b3abe57b80 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2020,10 +2020,12 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) } else { dev->node_props.debug_prop |= HSA_DBG_WATCH_ADDR_MASK_LO_BIT_GFX10 | HSA_DBG_WATCH_ADDR_MASK_HI_BIT; - /* gfx11 dGPU */ + /* gfx11 dGPU and gfx12.0 */ if ((KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 0) || KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 2) || - KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 3)) && + KFD_GC_VERSION(dev->gpu) == IP_VERSION(11, 0, 3) || + KFD_GC_VERSION(dev->gpu) == IP_VERSION(12, 0, 0) || + KFD_GC_VERSION(dev->gpu) == IP_VERSION(12, 0, 1)) && !amdgpu_sriov_vf(dev->gpu->adev)) dev->node_props.capability |= HSA_CAP_PER_QUEUE_RESET_SUPPORTED; -- cgit v1.2.3 From a17e79d01f22182a9fcbe79fcbe2ad1477d43e0f Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 00:04:33 +0800 Subject: drm/amd/pm: Validate pp_table header before reading size smu_sys_set_pp_table() reads usStructureSize from the uploaded pp_table buffer before validating that the buffer contains a complete ATOM_COMMON_TABLE_HEADER. A short write can therefore make the driver read past the supplied sysfs buffer. Reject empty or header-short uploads before dereferencing the header pointer. Keep the existing structure-size check for the full uploaded table. Signed-off-by: Yang Wang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 5e73594efdf0..9abfac9f81d1 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -667,25 +667,28 @@ static int smu_sys_set_pp_table(void *handle, { struct smu_context *smu = handle; struct smu_table_context *smu_table = &smu->smu_table; - ATOM_COMMON_TABLE_HEADER *header = (ATOM_COMMON_TABLE_HEADER *)buf; + ATOM_COMMON_TABLE_HEADER *header; + void *hardcode_pptable; int ret = 0; if (!smu->pm_enabled || !smu->adev->pm.dpm_enabled) return -EOPNOTSUPP; + if (!buf || size < sizeof(*header)) + return -EINVAL; + + header = (ATOM_COMMON_TABLE_HEADER *)buf; if (header->usStructureSize != size) { dev_err(smu->adev->dev, "pp table size not matched !\n"); return -EIO; } - if (!smu_table->hardcode_pptable || smu_table->power_play_table_size < size) { - kfree(smu_table->hardcode_pptable); - smu_table->hardcode_pptable = kzalloc(size, GFP_KERNEL); - if (!smu_table->hardcode_pptable) - return -ENOMEM; - } + hardcode_pptable = kmemdup(buf, size, GFP_KERNEL); + if (!hardcode_pptable) + return -ENOMEM; - memcpy(smu_table->hardcode_pptable, buf, size); + kfree(smu_table->hardcode_pptable); + smu_table->hardcode_pptable = hardcode_pptable; smu_table->power_play_table = smu_table->hardcode_pptable; smu_table->power_play_table_size = size; -- cgit v1.2.3 From 055a40c32f3a2dcd4d1a6f85ff4c231cf35f1b53 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 11:36:20 +0800 Subject: drm/amd/pm: Use uploaded size for legacy custom PPTable The legacy powerplay path used to allocate hardcode_pp_table from the original VBIOS PPTable size, copy only the uploaded bytes into it, and keep soft_pp_table_size unchanged. If a custom PPTable is shorter than the original table, later code can still treat the stale tail as valid table data. Treat the uploaded buffer as the complete custom PPTable: duplicate the uploaded buffer directly, replace hardcode_pp_table atomically, and set soft_pp_table_size to the uploaded size. Signed-off-by: Yang Wang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c index 6f5c27bdc1e9..7c70e228a5ba 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c +++ b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c @@ -660,25 +660,20 @@ static int amd_powerplay_reset(void *handle) static int pp_dpm_set_pp_table(void *handle, const char *buf, size_t size) { struct pp_hwmgr *hwmgr = handle; + void *hardcode_pp_table; int ret = -ENOMEM; - if (!hwmgr || !hwmgr->pm_en) - return -EINVAL; - - if (size > hwmgr->soft_pp_table_size) + if (!hwmgr || !hwmgr->pm_en || !buf || !size || size > U32_MAX) return -EINVAL; - if (!hwmgr->hardcode_pp_table) { - hwmgr->hardcode_pp_table = kmemdup(hwmgr->soft_pp_table, - hwmgr->soft_pp_table_size, - GFP_KERNEL); - if (!hwmgr->hardcode_pp_table) - return ret; - } - - memcpy(hwmgr->hardcode_pp_table, buf, size); + hardcode_pp_table = kmemdup(buf, size, GFP_KERNEL); + if (!hardcode_pp_table) + return ret; + kfree(hwmgr->hardcode_pp_table); + hwmgr->hardcode_pp_table = hardcode_pp_table; hwmgr->soft_pp_table = hwmgr->hardcode_pp_table; + hwmgr->soft_pp_table_size = size; ret = amd_powerplay_reset(handle); if (ret) -- cgit v1.2.3 From fa1531170d2c96060478d697fe93f1cafc0e7ddd Mon Sep 17 00:00:00 2001 From: Gangliang Xie Date: Tue, 23 Jun 2026 10:42:19 +0800 Subject: drm/amdgpu: add check for xcp id check sel_xcp_id before its use Signed-off-by: Gangliang Xie Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c index cf71b4f55252..7c3e707ff84e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c @@ -576,6 +576,9 @@ static void amdgpu_xcp_gpu_sched_update(struct amdgpu_device *adev, { unsigned int *num_gpu_sched; + if (sel_xcp_id >= MAX_XCP || sel_xcp_id == AMDGPU_XCP_NO_PARTITION) + return; + num_gpu_sched = &adev->xcp_mgr->xcp[sel_xcp_id] .gpu_sched[ring->funcs->type][ring->hw_prio].num_scheds; adev->xcp_mgr->xcp[sel_xcp_id].gpu_sched[ring->funcs->type][ring->hw_prio] -- cgit v1.2.3 From d871e99879cb5fd1fa798b006b4888887e63a17a Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Sun, 14 Jun 2026 12:50:28 +0800 Subject: drm/amdgpu: fix aperture mapping leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_pci_remove() calls drm_dev_unplug() before invoking the driver fini routines. This causes drm_dev_enter() in amdgpu_ttm_fini() to always return false, so iounmap(aper_base_kaddr) never runs on normal driver unload, leaving an orphaned entry in the x86 PAT interval tree. On connected_to_cpu hardware, the aperture is mapped write-back (WB) via ioremap_cache(). On reload, IP discovery calls memremap(..., MEMREMAP_WC) over the same range. The WC vs WB conflict causes: ioremap error for 0x..., requested 0x1, got 0x0 amdgpu: discovery failed: -2 Fix by switching to devres-managed mappings so cleanup is guaranteed regardless of drm_dev_enter() state: - connected_to_cpu path: devm_memremap(MEMREMAP_WB). For IORESOURCE_SYSTEM_RAM ranges this takes the try_ram_remap() shortcut, returning __va(offset) from the existing kernel direct map. No new ioremap VA or PAT entry is created, so there is nothing to orphan. - dGPU path: devm_ioremap_wc() registers iounmap() as a devres action, guaranteeing cleanup at device_del() time. Also remove iounmap(aper_base_kaddr) from amdgpu_device_unmap_mmio() since the mapping is now devres-owned. v2: Remove redundant x86_64 guard (Lijo) Fixes: 9d0af8b4def0 ("drm/amdgpu: pre-map device buffer as cached for A+A config") Signed-off-by: Asad Kamal Reviewed-by: Christian König Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 2 -- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 36 +++++++++++++----------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index b427d963c604..7265de3889e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4189,8 +4189,6 @@ static void amdgpu_device_unmap_mmio(struct amdgpu_device *adev) iounmap(adev->rmmio); adev->rmmio = NULL; - if (adev->mman.aper_base_kaddr) - iounmap(adev->mman.aper_base_kaddr); adev->mman.aper_base_kaddr = NULL; /* Memory manager related */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 16c060badaee..00b5317f77f8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2118,18 +2118,23 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) /* Change the size here instead of the init above so only lpfn is affected */ amdgpu_ttm_disable_buffer_funcs(adev); #ifdef CONFIG_64BIT -#ifdef CONFIG_X86 - if (adev->gmc.xgmi.connected_to_cpu) - adev->mman.aper_base_kaddr = ioremap_cache(adev->gmc.aper_base, - adev->gmc.visible_vram_size); - - else if (adev->gmc.is_app_apu) + if (adev->gmc.xgmi.connected_to_cpu) { + void *kaddr = devm_memremap(adev->dev, adev->gmc.aper_base, + adev->gmc.visible_vram_size, + MEMREMAP_WB); + if (IS_ERR(kaddr)) + return PTR_ERR(kaddr); + adev->mman.aper_base_kaddr = (__force void __iomem *)kaddr; + } else if (adev->gmc.is_app_apu) { DRM_DEBUG_DRIVER( "No need to ioremap when real vram size is 0\n"); - else -#endif - adev->mman.aper_base_kaddr = ioremap_wc(adev->gmc.aper_base, - adev->gmc.visible_vram_size); + } else { + adev->mman.aper_base_kaddr = devm_ioremap_wc(adev->dev, + adev->gmc.aper_base, + adev->gmc.visible_vram_size); + if (!adev->mman.aper_base_kaddr) + return -ENOMEM; + } #endif amdgpu_ttm_init_vram_resv_regions(adev); @@ -2246,8 +2251,6 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) */ void amdgpu_ttm_fini(struct amdgpu_device *adev) { - int idx; - if (!adev->mman.initialized) return; @@ -2270,14 +2273,7 @@ void amdgpu_ttm_fini(struct amdgpu_device *adev) amdgpu_ttm_unmark_vram_reserved(adev, AMDGPU_RESV_FW_VRAM_USAGE); amdgpu_ttm_unmark_vram_reserved(adev, AMDGPU_RESV_DRV_VRAM_USAGE); - if (drm_dev_enter(adev_to_drm(adev), &idx)) { - - if (adev->mman.aper_base_kaddr) - iounmap(adev->mman.aper_base_kaddr); - adev->mman.aper_base_kaddr = NULL; - - drm_dev_exit(idx); - } + adev->mman.aper_base_kaddr = NULL; if (!adev->gmc.is_app_apu) amdgpu_vram_mgr_fini(adev); -- cgit v1.2.3 From 6c2abd0ec09e86c6323010673766f76050e28aa3 Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Tue, 2 Jun 2026 09:47:19 -0400 Subject: drm/amdkfd: clamp v9 CRIU control stack checkpoint copy to BO size CRIU checkpoint copies the MQD control stack using cp_hqd_cntl_stack_size from hardware without bounding it to the allocated BO region. If the HW field is larger than the queue's control stack allocation, memcpy reads past the BO into adjacent GTT memory and can leak kernel data to userspace. Store the page-aligned control stack BO size in mqd_manager and clamp checkpoint copies and reported checkpoint sizes to min(cp_hqd_cntl_stack_size, mm->ctl_stack_size). Apply the same bound for multi-XCC v9.4.3 checkpoint layout. Signed-off-by: Yongqiang Sun Reviewed-by: David Francis Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h | 1 + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h index 140ee1fc5d81..59eff3389d39 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h @@ -127,6 +127,7 @@ struct mqd_manager { struct mutex mqd_mutex; struct kfd_node *dev; uint32_t mqd_size; + uint32_t ctl_stack_size; }; struct mqd_user_context_save_area_header { diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index 9a1edd5b2c69..75e5a9f67d50 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -27,6 +27,7 @@ #include #include "kfd_priv.h" #include "kfd_mqd_manager.h" +#include "kfd_topology.h" #include "v9_structs.h" #include "gc/gc_9_0_offset.h" #include "gc/gc_9_0_sh_mask.h" @@ -397,8 +398,11 @@ static int get_wave_state(struct mqd_manager *mm, void *mqd, static int get_checkpoint_info(struct mqd_manager *mm, void *mqd, u32 *ctl_stack_size) { struct v9_mqd *m = get_mqd(mqd); + u32 per_xcc_size; - if (check_mul_overflow(m->cp_hqd_cntl_stack_size, NUM_XCC(mm->dev->xcc_mask), ctl_stack_size)) + per_xcc_size = min_t(u32, m->cp_hqd_cntl_stack_size, mm->ctl_stack_size); + + if (check_mul_overflow(per_xcc_size, NUM_XCC(mm->dev->xcc_mask), ctl_stack_size)) return -EINVAL; return 0; @@ -407,13 +411,15 @@ static int get_checkpoint_info(struct mqd_manager *mm, void *mqd, u32 *ctl_stack static void checkpoint_mqd(struct mqd_manager *mm, void *mqd, void *mqd_dst, void *ctl_stack_dst) { struct v9_mqd *m; + u32 ctl_stack_copy_size; /* Control stack is located one page after MQD. */ void *ctl_stack = (void *)((uintptr_t)mqd + AMDGPU_GPU_PAGE_SIZE); m = get_mqd(mqd); + ctl_stack_copy_size = min_t(u32, m->cp_hqd_cntl_stack_size, mm->ctl_stack_size); memcpy(mqd_dst, m, sizeof(struct v9_mqd)); - memcpy(ctl_stack_dst, ctl_stack, m->cp_hqd_cntl_stack_size); + memcpy(ctl_stack_dst, ctl_stack, ctl_stack_copy_size); } static void checkpoint_mqd_v9_4_3(struct mqd_manager *mm, @@ -422,15 +428,19 @@ static void checkpoint_mqd_v9_4_3(struct mqd_manager *mm, void *ctl_stack_dst) { struct v9_mqd *m; + u32 ctl_stack_stride; int xcc; uint64_t size = get_mqd(mqd)->cp_mqd_stride_size; + ctl_stack_stride = min_t(u32, get_mqd(mqd)->cp_hqd_cntl_stack_size, + mm->ctl_stack_size); + for (xcc = 0; xcc < NUM_XCC(mm->dev->xcc_mask); xcc++) { m = get_mqd(mqd + size * xcc); checkpoint_mqd(mm, m, (uint8_t *)mqd_dst + sizeof(*m) * xcc, - (uint8_t *)ctl_stack_dst + m->cp_hqd_cntl_stack_size * xcc); + (uint8_t *)ctl_stack_dst + ctl_stack_stride * xcc); } } @@ -984,6 +994,15 @@ struct mqd_manager *mqd_manager_init_v9(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_cp; mqd->get_checkpoint_info = get_checkpoint_info; mqd->mqd_size = sizeof(struct v9_mqd); + if (dev->kfd->cwsr_enabled) { + struct kfd_topology_device *topo_dev; + + topo_dev = kfd_topology_device_by_id(dev->id); + if (topo_dev) + mqd->ctl_stack_size = + ALIGN(topo_dev->node_props.ctl_stack_size, + AMDGPU_GPU_PAGE_SIZE); + } mqd->mqd_stride = mqd_stride_v9; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; -- cgit v1.2.3 From 5d3cc8e388464f485d0944b87b8f9426e637d082 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 23 Jun 2026 00:25:04 +0800 Subject: drm/amd/powerplay: fix VoltageObjectInfo zero-stride loop and OOB read Reject voltage objects whose usSize is smaller than the header or would advance the cursor past the table end, preventing an infinite loop or heap OOB read when the VBIOS supplies a malformed VoltageObjectInfo table. Fixes: c82baa281843 ("drm/amd/powerplay: add Tonga dpm support (v3)") Fixes: 0d2c7569e196 ("drm/amdgpu: add new atomfirmware based helpers for powerplay") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Yang Wang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c | 10 ++++++++-- drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomfwctrl.c | 11 ++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c index ce166a7f8e42..1fff7567bca2 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c @@ -268,15 +268,21 @@ static const ATOM_VOLTAGE_OBJECT_V3 *atomctrl_lookup_voltage_type_v3( unsigned int offset = offsetof(ATOM_VOLTAGE_OBJECT_INFO_V3_1, asVoltageObj[0]); uint8_t *start = (uint8_t *)voltage_object_info_table; - while (offset < size) { + while (offset + sizeof(ATOM_VOLTAGE_OBJECT_HEADER_V3) <= size) { const ATOM_VOLTAGE_OBJECT_V3 *voltage_object = (const ATOM_VOLTAGE_OBJECT_V3 *)(start + offset); + u16 obj_size; + + obj_size = le16_to_cpu(voltage_object->asGpioVoltageObj.sHeader.usSize); + if (obj_size < sizeof(voltage_object->asGpioVoltageObj.sHeader) || + offset + obj_size > size) + break; if (voltage_type == voltage_object->asGpioVoltageObj.sHeader.ucVoltageType && voltage_mode == voltage_object->asGpioVoltageObj.sHeader.ucVoltageMode) return voltage_object; - offset += le16_to_cpu(voltage_object->asGpioVoltageObj.sHeader.usSize); + offset += obj_size; } return NULL; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomfwctrl.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomfwctrl.c index 6120f14caab0..69aee8661d1e 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomfwctrl.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomfwctrl.c @@ -36,16 +36,21 @@ static const union atom_voltage_object_v4 *pp_atomfwctrl_lookup_voltage_type_v4( offsetof(struct atom_voltage_objects_info_v4_1, voltage_object[0]); unsigned long start = (unsigned long)voltage_object_info_table; - while (offset < size) { + while (offset + sizeof(struct atom_voltage_object_header_v4) <= size) { const union atom_voltage_object_v4 *voltage_object = (const union atom_voltage_object_v4 *)(start + offset); + u16 obj_size; + + obj_size = le16_to_cpu(voltage_object->gpio_voltage_obj.header.object_size); + if (obj_size < sizeof(voltage_object->gpio_voltage_obj.header) || + offset + obj_size > size) + break; if (voltage_type == voltage_object->gpio_voltage_obj.header.voltage_type && voltage_mode == voltage_object->gpio_voltage_obj.header.voltage_mode) return voltage_object; - offset += le16_to_cpu(voltage_object->gpio_voltage_obj.header.object_size); - + offset += obj_size; } return NULL; -- cgit v1.2.3 From c42871ba4833855fb3ac1cdc586b3c5345d09e5d Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 23 Jun 2026 00:00:00 +0000 Subject: drm/amdgpu/pm: add pp_entries_max() helper Add a static inline that returns the maximum safe record count for a PowerPlay sub-table, bounded by the lesser of soft_pp_table_size and adev->bios_size. Uses adev->bios directly to avoid a dependency on struct atom_context. Subsequent patches use it to clamp ucNumEntries. Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index ca71efaa1656..7ebc1344023f 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -829,4 +829,21 @@ int smu8_init_function_pointers(struct pp_hwmgr *hwmgr); int vega12_hwmgr_init(struct pp_hwmgr *hwmgr); int vega20_hwmgr_init(struct pp_hwmgr *hwmgr); +static inline uint32_t pp_entries_max(const struct pp_hwmgr *hwmgr, + const void *sub_table, + size_t hdr_size, size_t rec_size) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)hwmgr->adev; + const char *bios_end = (const char *)adev->bios + adev->bios_size; + const char *pp_end = (const char *)hwmgr->soft_pp_table + + hwmgr->soft_pp_table_size; + const char *entries = (const char *)sub_table + hdr_size; + + if (pp_end > bios_end) + return 0; + if (!rec_size || entries >= pp_end) + return 0; + return (uint32_t)((pp_end - entries) / rec_size); +} + #endif /* _HWMGR_H_ */ -- cgit v1.2.3 From f14f99fffce215f7bb4d3400193da01a43086c7b Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 23 Jun 2026 00:00:00 +0000 Subject: drm/amdgpu/pm/powerplay: clamp Tonga/Polaris PP sub-table ucNumEntries ucNumEntries in the Tonga/Polaris PowerPlay sub-tables is used as both the kzalloc count and loop bound without validation, allowing a crafted VBIOS to overflow the destination heap object and read past the VBIOS image. Clamp via pp_entries_max() in get_vddc_lookup_table(), get_mclk_voltage_dependency_table(), get_sclk_voltage_dependency_table() and get_mm_clock_voltage_table(). Fixes: c82baa281843 ("drm/amd/powerplay: add Tonga dpm support (v3)") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../amd/pm/powerplay/hwmgr/process_pptables_v1_0.c | 80 +++++++++++++++++----- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c index 6fcca65bd7d4..d459ae9cf8a6 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c @@ -158,6 +158,7 @@ static int get_vddc_lookup_table( ) { uint32_t i; + uint32_t num_entries; phm_ppt_v1_voltage_lookup_table *table; phm_ppt_v1_voltage_lookup_record *record; ATOM_Tonga_Voltage_Lookup_Record *atom_record; @@ -165,13 +166,22 @@ static int get_vddc_lookup_table( PP_ASSERT_WITH_CODE((0 != vddc_lookup_pp_tables->ucNumEntries), "Invalid CAC Leakage PowerPlay Table!", return 1); - table = kzalloc_flex(*table, entries, max_levels); + num_entries = min_t(uint32_t, vddc_lookup_pp_tables->ucNumEntries, + min_t(uint32_t, max_levels, + pp_entries_max(hwmgr, vddc_lookup_pp_tables, + sizeof(*vddc_lookup_pp_tables), + sizeof(ATOM_Tonga_Voltage_Lookup_Record)))); + if (num_entries < vddc_lookup_pp_tables->ucNumEntries) + pr_warn("amdgpu: VddcLookup table: clamping ucNumEntries %u -> %u\n", + vddc_lookup_pp_tables->ucNumEntries, num_entries); + + table = kzalloc_flex(*table, entries, num_entries); if (!table) return -ENOMEM; - table->count = vddc_lookup_pp_tables->ucNumEntries; + table->count = num_entries; - for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( phm_ppt_v1_voltage_lookup_record, entries, table, i); @@ -363,6 +373,7 @@ static int get_mclk_voltage_dependency_table( ) { uint32_t i; + uint32_t num_entries; phm_ppt_v1_clock_voltage_dependency_table *mclk_table; phm_ppt_v1_clock_voltage_dependency_record *mclk_table_record; ATOM_Tonga_MCLK_Dependency_Record *mclk_dep_record; @@ -370,14 +381,21 @@ static int get_mclk_voltage_dependency_table( PP_ASSERT_WITH_CODE((0 != mclk_dep_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); - mclk_table = kzalloc_flex(*mclk_table, entries, - mclk_dep_table->ucNumEntries); + num_entries = min_t(uint32_t, mclk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, mclk_dep_table, + sizeof(*mclk_dep_table), + sizeof(ATOM_Tonga_MCLK_Dependency_Record))); + if (num_entries < mclk_dep_table->ucNumEntries) + pr_warn("amdgpu: MCLK dependency table: clamping ucNumEntries %u -> %u\n", + mclk_dep_table->ucNumEntries, num_entries); + + mclk_table = kzalloc_flex(*mclk_table, entries, num_entries); if (!mclk_table) return -ENOMEM; - mclk_table->count = (uint32_t)mclk_dep_table->ucNumEntries; + mclk_table->count = num_entries; - for (i = 0; i < mclk_dep_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { mclk_table_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( phm_ppt_v1_clock_voltage_dependency_record, entries, mclk_table, i); @@ -403,6 +421,7 @@ static int get_sclk_voltage_dependency_table( ) { uint32_t i; + uint32_t num_entries; phm_ppt_v1_clock_voltage_dependency_table *sclk_table; phm_ppt_v1_clock_voltage_dependency_record *sclk_table_record; @@ -414,14 +433,21 @@ static int get_sclk_voltage_dependency_table( PP_ASSERT_WITH_CODE((0 != tonga_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); - sclk_table = kzalloc_flex(*sclk_table, entries, - tonga_table->ucNumEntries); + num_entries = min_t(uint32_t, tonga_table->ucNumEntries, + pp_entries_max(hwmgr, tonga_table, + sizeof(*tonga_table), + sizeof(ATOM_Tonga_SCLK_Dependency_Record))); + if (num_entries < tonga_table->ucNumEntries) + pr_warn("amdgpu: Tonga SCLK dependency table: clamping ucNumEntries %u -> %u\n", + tonga_table->ucNumEntries, num_entries); + + sclk_table = kzalloc_flex(*sclk_table, entries, num_entries); if (!sclk_table) return -ENOMEM; - sclk_table->count = (uint32_t)tonga_table->ucNumEntries; + sclk_table->count = num_entries; - for (i = 0; i < tonga_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_SCLK_Dependency_Record, entries, tonga_table, i); @@ -443,14 +469,21 @@ static int get_sclk_voltage_dependency_table( PP_ASSERT_WITH_CODE((0 != polaris_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); - sclk_table = kzalloc_flex(*sclk_table, entries, - polaris_table->ucNumEntries); + num_entries = min_t(uint32_t, polaris_table->ucNumEntries, + pp_entries_max(hwmgr, polaris_table, + sizeof(*polaris_table), + sizeof(ATOM_Polaris_SCLK_Dependency_Record))); + if (num_entries < polaris_table->ucNumEntries) + pr_warn("amdgpu: Polaris SCLK dependency table: clamping ucNumEntries %u -> %u\n", + polaris_table->ucNumEntries, num_entries); + + sclk_table = kzalloc_flex(*sclk_table, entries, num_entries); if (!sclk_table) return -ENOMEM; - sclk_table->count = (uint32_t)polaris_table->ucNumEntries; + sclk_table->count = num_entries; - for (i = 0; i < polaris_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Polaris_SCLK_Dependency_Record, entries, polaris_table, i); @@ -715,20 +748,29 @@ static int get_mm_clock_voltage_table( ) { uint32_t i; + uint32_t num_entries; const ATOM_Tonga_MM_Dependency_Record *mm_dependency_record; phm_ppt_v1_mm_clock_voltage_dependency_table *mm_table; phm_ppt_v1_mm_clock_voltage_dependency_record *mm_table_record; PP_ASSERT_WITH_CODE((0 != mm_dependency_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); - mm_table = kzalloc_flex(*mm_table, entries, - mm_dependency_table->ucNumEntries); + + num_entries = min_t(uint32_t, mm_dependency_table->ucNumEntries, + pp_entries_max(hwmgr, mm_dependency_table, + sizeof(*mm_dependency_table), + sizeof(ATOM_Tonga_MM_Dependency_Record))); + if (num_entries < mm_dependency_table->ucNumEntries) + pr_warn("amdgpu: MM dependency table: clamping ucNumEntries %u -> %u\n", + mm_dependency_table->ucNumEntries, num_entries); + + mm_table = kzalloc_flex(*mm_table, entries, num_entries); if (!mm_table) return -ENOMEM; - mm_table->count = mm_dependency_table->ucNumEntries; + mm_table->count = num_entries; - for (i = 0; i < mm_dependency_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { mm_dependency_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_MM_Dependency_Record, entries, mm_dependency_table, i); -- cgit v1.2.3 From 33d3ae96964cc27c112b79d93964f4c88e232136 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 23 Jun 2026 00:00:00 +0000 Subject: drm/amdgpu/pm/powerplay: clamp Vega10 PP sub-table ucNumEntries Same write-OOB and read-OOB as the Tonga fix, across seven Vega10 sub-table parsers: get_vddc_lookup_table(), get_mm_clock_voltage_table(), get_socclk/mclk/gfxclk/pixclk/dcefclk_voltage_dependency_table(). The GFXCLK table selects the correct record size per revision. Fixes: f83a9991648b ("drm/amd/powerplay: add Vega10 powerplay support (v5)") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../pm/powerplay/hwmgr/vega10_processpptables.c | 138 +++++++++++++++------ 1 file changed, 102 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c index 052d139584fd..d32c8166f703 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c @@ -344,20 +344,28 @@ static int get_mm_clock_voltage_table( const ATOM_Vega10_MM_Dependency_Table *mm_dependency_table) { uint32_t i; + uint32_t num_entries; const ATOM_Vega10_MM_Dependency_Record *mm_dependency_record; phm_ppt_v1_mm_clock_voltage_dependency_table *mm_table; PP_ASSERT_WITH_CODE((mm_dependency_table->ucNumEntries != 0), "Invalid PowerPlay Table!", return -1); - mm_table = kzalloc_flex(*mm_table, entries, - mm_dependency_table->ucNumEntries); + num_entries = min_t(uint32_t, mm_dependency_table->ucNumEntries, + pp_entries_max(hwmgr, mm_dependency_table, + sizeof(*mm_dependency_table), + sizeof(ATOM_Vega10_MM_Dependency_Record))); + if (num_entries < mm_dependency_table->ucNumEntries) + pr_warn("amdgpu: Vega10 MM dependency table: clamping ucNumEntries %u -> %u\n", + mm_dependency_table->ucNumEntries, num_entries); + + mm_table = kzalloc_flex(*mm_table, entries, num_entries); if (!mm_table) return -ENOMEM; - mm_table->count = mm_dependency_table->ucNumEntries; + mm_table->count = num_entries; - for (i = 0; i < mm_dependency_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { mm_dependency_record = &mm_dependency_table->entries[i]; mm_table->entries[i].vddcInd = mm_dependency_record->ucVddcInd; mm_table->entries[i].samclock = @@ -568,19 +576,27 @@ static int get_socclk_voltage_dependency_table( const ATOM_Vega10_SOCCLK_Dependency_Table *clk_dep_table) { uint32_t i; + uint32_t num_entries; phm_ppt_v1_clock_voltage_dependency_table *clk_table; PP_ASSERT_WITH_CODE(clk_dep_table->ucNumEntries, "Invalid PowerPlay Table!", return -1); - clk_table = kzalloc_flex(*clk_table, entries, - clk_dep_table->ucNumEntries); + num_entries = min_t(uint32_t, clk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, clk_dep_table, + sizeof(*clk_dep_table), + sizeof(ATOM_Vega10_CLK_Dependency_Record))); + if (num_entries < clk_dep_table->ucNumEntries) + pr_warn("amdgpu: Vega10 SOCCLK dependency table: clamping ucNumEntries %u -> %u\n", + clk_dep_table->ucNumEntries, num_entries); + + clk_table = kzalloc_flex(*clk_table, entries, num_entries); if (!clk_table) return -ENOMEM; - clk_table->count = (uint32_t)clk_dep_table->ucNumEntries; + clk_table->count = num_entries; - for (i = 0; i < clk_dep_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { clk_table->entries[i].vddInd = clk_dep_table->entries[i].ucVddInd; clk_table->entries[i].clk = @@ -598,19 +614,27 @@ static int get_mclk_voltage_dependency_table( const ATOM_Vega10_MCLK_Dependency_Table *mclk_dep_table) { uint32_t i; + uint32_t num_entries; phm_ppt_v1_clock_voltage_dependency_table *mclk_table; PP_ASSERT_WITH_CODE(mclk_dep_table->ucNumEntries, "Invalid PowerPlay Table!", return -1); - mclk_table = kzalloc_flex(*mclk_table, entries, - mclk_dep_table->ucNumEntries); + num_entries = min_t(uint32_t, mclk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, mclk_dep_table, + sizeof(*mclk_dep_table), + sizeof(ATOM_Vega10_MCLK_Dependency_Record))); + if (num_entries < mclk_dep_table->ucNumEntries) + pr_warn("amdgpu: Vega10 MCLK dependency table: clamping ucNumEntries %u -> %u\n", + mclk_dep_table->ucNumEntries, num_entries); + + mclk_table = kzalloc_flex(*mclk_table, entries, num_entries); if (!mclk_table) return -ENOMEM; - mclk_table->count = (uint32_t)mclk_dep_table->ucNumEntries; + mclk_table->count = num_entries; - for (i = 0; i < mclk_dep_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { mclk_table->entries[i].vddInd = mclk_dep_table->entries[i].ucVddInd; mclk_table->entries[i].vddciInd = @@ -633,6 +657,7 @@ static int get_gfxclk_voltage_dependency_table( const ATOM_Vega10_GFXCLK_Dependency_Table *clk_dep_table) { uint32_t i; + uint32_t num_entries; struct phm_ppt_v1_clock_voltage_dependency_table *clk_table; ATOM_Vega10_GFXCLK_Dependency_Record_V2 *patom_record_v2; @@ -640,15 +665,34 @@ static int get_gfxclk_voltage_dependency_table( PP_ASSERT_WITH_CODE((clk_dep_table->ucNumEntries != 0), "Invalid PowerPlay Table!", return -1); - clk_table = kzalloc_flex(*clk_table, entries, - clk_dep_table->ucNumEntries); + if (clk_dep_table->ucRevId == 0) { + num_entries = min_t(uint32_t, clk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, clk_dep_table, + sizeof(*clk_dep_table), + sizeof(ATOM_Vega10_GFXCLK_Dependency_Record))); + } else if (clk_dep_table->ucRevId == 1) { + num_entries = min_t(uint32_t, clk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, clk_dep_table, + sizeof(*clk_dep_table), + sizeof(ATOM_Vega10_GFXCLK_Dependency_Record_V2))); + } else { + PP_ASSERT_WITH_CODE(false, + "Unsupported GFXClockDependencyTable Revision!", + return -EINVAL); + } + + if (num_entries < clk_dep_table->ucNumEntries) + pr_warn("amdgpu: Vega10 GFXCLK dependency table: clamping ucNumEntries %u -> %u\n", + clk_dep_table->ucNumEntries, num_entries); + + clk_table = kzalloc_flex(*clk_table, entries, num_entries); if (!clk_table) return -ENOMEM; - clk_table->count = clk_dep_table->ucNumEntries; + clk_table->count = num_entries; if (clk_dep_table->ucRevId == 0) { - for (i = 0; i < clk_table->count; i++) { + for (i = 0; i < num_entries; i++) { clk_table->entries[i].vddInd = clk_dep_table->entries[i].ucVddInd; clk_table->entries[i].clk = @@ -661,9 +705,9 @@ static int get_gfxclk_voltage_dependency_table( clk_table->entries[i].sclk_offset = le16_to_cpu(clk_dep_table->entries[i].usAVFSOffset); } - } else if (clk_dep_table->ucRevId == 1) { + } else { patom_record_v2 = (ATOM_Vega10_GFXCLK_Dependency_Record_V2 *)clk_dep_table->entries; - for (i = 0; i < clk_table->count; i++) { + for (i = 0; i < num_entries; i++) { clk_table->entries[i].vddInd = patom_record_v2->ucVddInd; clk_table->entries[i].clk = @@ -677,11 +721,6 @@ static int get_gfxclk_voltage_dependency_table( le16_to_cpu(patom_record_v2->usAVFSOffset); patom_record_v2++; } - } else { - kfree(clk_table); - PP_ASSERT_WITH_CODE(false, - "Unsupported GFXClockDependencyTable Revision!", - return -EINVAL); } *pp_vega10_clk_dep_table = clk_table; @@ -696,20 +735,28 @@ static int get_pix_clk_voltage_dependency_table( const ATOM_Vega10_PIXCLK_Dependency_Table *clk_dep_table) { uint32_t i; + uint32_t num_entries; struct phm_ppt_v1_clock_voltage_dependency_table *clk_table; PP_ASSERT_WITH_CODE((clk_dep_table->ucNumEntries != 0), "Invalid PowerPlay Table!", return -1); - clk_table = kzalloc_flex(*clk_table, entries, - clk_dep_table->ucNumEntries); + num_entries = min_t(uint32_t, clk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, clk_dep_table, + sizeof(*clk_dep_table), + sizeof(ATOM_Vega10_CLK_Dependency_Record))); + if (num_entries < clk_dep_table->ucNumEntries) + pr_warn("amdgpu: Vega10 PIXCLK dependency table: clamping ucNumEntries %u -> %u\n", + clk_dep_table->ucNumEntries, num_entries); + + clk_table = kzalloc_flex(*clk_table, entries, num_entries); if (!clk_table) return -ENOMEM; - clk_table->count = clk_dep_table->ucNumEntries; + clk_table->count = num_entries; - for (i = 0; i < clk_table->count; i++) { + for (i = 0; i < num_entries; i++) { clk_table->entries[i].vddInd = clk_dep_table->entries[i].ucVddInd; clk_table->entries[i].clk = @@ -728,6 +775,7 @@ static int get_dcefclk_voltage_dependency_table( const ATOM_Vega10_DCEFCLK_Dependency_Table *clk_dep_table) { uint32_t i; + uint32_t safe_entries; uint8_t num_entries; struct phm_ppt_v1_clock_voltage_dependency_table *clk_table; @@ -738,6 +786,14 @@ static int get_dcefclk_voltage_dependency_table( PP_ASSERT_WITH_CODE((clk_dep_table->ucNumEntries != 0), "Invalid PowerPlay Table!", return -1); + safe_entries = min_t(uint32_t, clk_dep_table->ucNumEntries, + pp_entries_max(hwmgr, clk_dep_table, + sizeof(*clk_dep_table), + sizeof(ATOM_Vega10_CLK_Dependency_Record))); + if (safe_entries < clk_dep_table->ucNumEntries) + pr_warn("amdgpu: Vega10 DCEFCLK dependency table: clamping ucNumEntries %u -> %u\n", + clk_dep_table->ucNumEntries, safe_entries); + /* * workaround needed to add another DPM level for pioneer cards * as VBIOS is locked down. @@ -747,12 +803,12 @@ static int get_dcefclk_voltage_dependency_table( dev_id = adev->pdev->device; rev_id = adev->pdev->revision; - if (dev_id == 0x6863 && rev_id == 0 && - clk_dep_table->entries[clk_dep_table->ucNumEntries - 1].ulClk < 90000) - num_entries = clk_dep_table->ucNumEntries + 1 > NUM_DSPCLK_LEVELS ? - NUM_DSPCLK_LEVELS : clk_dep_table->ucNumEntries + 1; + if (dev_id == 0x6863 && rev_id == 0 && safe_entries > 0 && + clk_dep_table->entries[safe_entries - 1].ulClk < 90000) + num_entries = safe_entries + 1 > NUM_DSPCLK_LEVELS ? + NUM_DSPCLK_LEVELS : safe_entries + 1; else - num_entries = clk_dep_table->ucNumEntries; + num_entries = safe_entries; clk_table = kzalloc_flex(*clk_table, entries, num_entries); @@ -761,7 +817,7 @@ static int get_dcefclk_voltage_dependency_table( clk_table->count = (uint32_t)num_entries; - for (i = 0; i < clk_dep_table->ucNumEntries; i++) { + for (i = 0; i < safe_entries; i++) { clk_table->entries[i].vddInd = clk_dep_table->entries[i].ucVddInd; clk_table->entries[i].clk = @@ -1034,18 +1090,28 @@ static int get_vddc_lookup_table( uint32_t max_levels) { uint32_t i; + uint32_t num_entries; phm_ppt_v1_voltage_lookup_table *table; PP_ASSERT_WITH_CODE((vddc_lookup_pp_tables->ucNumEntries != 0), "Invalid SOC_VDDD Lookup Table!", return 1); - table = kzalloc_flex(*table, entries, max_levels); + num_entries = min_t(uint32_t, vddc_lookup_pp_tables->ucNumEntries, + min_t(uint32_t, max_levels, + pp_entries_max(hwmgr, vddc_lookup_pp_tables, + sizeof(*vddc_lookup_pp_tables), + sizeof(ATOM_Vega10_Voltage_Lookup_Record)))); + if (num_entries < vddc_lookup_pp_tables->ucNumEntries) + pr_warn("amdgpu: Vega10 VddcLookup table: clamping ucNumEntries %u -> %u\n", + vddc_lookup_pp_tables->ucNumEntries, num_entries); + + table = kzalloc_flex(*table, entries, num_entries); if (!table) return -ENOMEM; - table->count = vddc_lookup_pp_tables->ucNumEntries; + table->count = num_entries; - for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++) + for (i = 0; i < num_entries; i++) table->entries[i].us_vdd = le16_to_cpu(vddc_lookup_pp_tables->entries[i].usVdd); -- cgit v1.2.3 From a24019f6480fad5c077b5956eed942c8960323d6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 10 Jun 2026 17:18:17 +0200 Subject: drm/amd/display: Handle struct drm_plane_state.ignore_damage_clips The mode-setting pipeline can disabled damage clippings for a commit by setting ignore_damage_clips in struct drm_plane_state. The commit will then do a full display update. Test the flag in DCN code and do a full update in DCN code if it has been set. Commit 35ed38d58257 ("drm: Allow drivers to indicate the damage helpers to ignore damage clips") introduced ignore_damage_clips to selectively ignore damage clipping in certain framebuffer changes. This driver does not do that, but DRM's damage iterator will soon rely on the flag. Therefore supporting it here as well make sense for consistency. Signed-off-by: Thomas Zimmermann Fixes: 35ed38d58257 ("drm: Allow drivers to indicate the damage helpers to ignore damage clips") Cc: Javier Martinez Canillas Cc: Thomas Zimmermann Cc: Zack Rusin Cc: dri-devel@lists.freedesktop.org Reviewed-by: Javier Martinez Canillas Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 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 ec14a0f3a34b..6bcd447f4f5d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3266,8 +3266,8 @@ static void fill_dc_dirty_rects(struct drm_plane *plane, { struct dm_crtc_state *dm_crtc_state = to_dm_crtc_state(crtc_state); struct rect *dirty_rects = flip_addrs->dirty_rects; - u32 num_clips; - struct drm_mode_rect *clips; + u32 num_clips = 0; + struct drm_mode_rect *clips = NULL; bool bb_changed; bool fb_changed; u32 i = 0; @@ -3283,8 +3283,10 @@ static void fill_dc_dirty_rects(struct drm_plane *plane, if (new_plane_state->rotation != DRM_MODE_ROTATE_0) goto ffu; - num_clips = drm_plane_get_damage_clips_count(new_plane_state); - clips = drm_plane_get_damage_clips(new_plane_state); + if (!new_plane_state->ignore_damage_clips) { + num_clips = drm_plane_get_damage_clips_count(new_plane_state); + clips = drm_plane_get_damage_clips(new_plane_state); + } if (num_clips && (!amdgpu_damage_clips || (amdgpu_damage_clips < 0 && is_psr_su))) -- cgit v1.2.3 From c4a5160e3be079848d5f9b8da6463c7b5156c626 Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Tue, 9 Jun 2026 14:43:48 +0200 Subject: drm/amd/display: Remove unnecessary SIGNAL_TYPE_HDMI_TYPE_A check Remove unnecessary SIGNAL_TYPE_HDMI_TYPE_A check that was performed in the drm_mode_is_420_only() case, but not in the drm_mode_is_420_also() && force_yuv420_output case. Without further knowledge if YCbCr 4:2:0 is supported outside of HDMI, there is no reason to use RGB when the display reports drm_mode_is_420_only() even on a non HDMI connection. This patch also moves both checks in the same if-case. This eliminates an extra else-if-case. Signed-off-by: Werner Sembach Signed-off-by: Andri Yngvason Tested-by: Andri Yngvason Reviewed-by: Daniel Stone Signed-off-by: Nicolas Frattaroli Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 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 300ee26f26ff..959c843fb77c 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 @@ -824,16 +824,11 @@ static void fill_stream_properties_from_drm_display_mode( timing_out->v_border_top = 0; timing_out->v_border_bottom = 0; /* TODO: un-hardcode */ - if (drm_mode_is_420_only(info, mode_in) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if (drm_mode_is_420_also(info, mode_in) - && aconnector - && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 - || aconnector->force_yuv420_output)) + if (drm_mode_is_420_only(info, mode_in) || + (aconnector && + (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 || + aconnector->force_yuv420_output) && + drm_mode_is_420_also(info, mode_in))) timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) && aconnector -- cgit v1.2.3 From 1ac24df78c566d767a5ef05a1fe0ecc55bac248d Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 11:36:39 +0800 Subject: drm/amd/pm: Validate Tonga PowerPlay state array bounds process_pptables_v1_0.c builds the Tonga state array pointer from usStateArrayOffset before checking that the table buffer covers the referenced data. A truncated PowerPlay table can therefore lead to out-of-bounds reads while validating the state array. Validate the fixed table size first, then check the state array offset and entry range before dereferencing the state array. Signed-off-by: Yang Wang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../amd/pm/powerplay/hwmgr/process_pptables_v1_0.c | 62 ++++++++++++++-------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c index d459ae9cf8a6..da77b2c03e24 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c @@ -1152,15 +1152,17 @@ static int init_thermal_controller( * @powerplay_table: Pointer to the PowerPlay Table. * Exception: 2 if the powerplay table is incorrect. */ -static int check_powerplay_tables( - struct pp_hwmgr *hwmgr, - const ATOM_Tonga_POWERPLAYTABLE *powerplay_table - ) +static int get_tonga_state_array(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_State_Array **state_array) { const ATOM_Tonga_State_Array *state_arrays; + u16 state_array_offset; + size_t state_array_size; + size_t table_size = hwmgr->soft_pp_table_size; - state_arrays = (ATOM_Tonga_State_Array *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usStateArrayOffset)); + PP_ASSERT_WITH_CODE((table_size >= sizeof(*powerplay_table)), + "Invalid PowerPlay Table!", return -1); PP_ASSERT_WITH_CODE((ATOM_Tonga_TABLE_REVISION_TONGA <= powerplay_table->sHeader.ucTableFormatRevision), @@ -1169,12 +1171,34 @@ static int check_powerplay_tables( "State table is not set!", return -1); PP_ASSERT_WITH_CODE((0 < powerplay_table->sHeader.usStructureSize), "Invalid PowerPlay Table!", return -1); + + state_array_offset = le16_to_cpu(powerplay_table->usStateArrayOffset); + PP_ASSERT_WITH_CODE((state_array_offset <= + table_size - sizeof(*state_arrays)), + "Invalid PowerPlay Table!", return -1); + + state_arrays = (ATOM_Tonga_State_Array *)(((unsigned long)powerplay_table) + + state_array_offset); PP_ASSERT_WITH_CODE((0 < state_arrays->ucNumEntries), "Invalid PowerPlay Table!", return -1); + state_array_size = struct_size(state_arrays, entries, state_arrays->ucNumEntries); + PP_ASSERT_WITH_CODE((state_array_size <= table_size - state_array_offset), + "Invalid PowerPlay Table!", return -1); + + *state_array = state_arrays; + return 0; } +static int check_powerplay_tables(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table) +{ + const ATOM_Tonga_State_Array *state_arrays; + + return get_tonga_state_array(hwmgr, powerplay_table, &state_arrays); +} + static int pp_tables_v1_0_initialize(struct pp_hwmgr *hwmgr) { int result = 0; @@ -1278,17 +1302,16 @@ const struct pp_table_func pptable_v1_0_funcs = { int get_number_of_powerplay_table_entries_v1_0(struct pp_hwmgr *hwmgr) { - ATOM_Tonga_State_Array const *state_arrays; + const ATOM_Tonga_State_Array *state_arrays; const ATOM_Tonga_POWERPLAYTABLE *pp_table = get_powerplay_table(hwmgr); + int result; PP_ASSERT_WITH_CODE((NULL != pp_table), "Missing PowerPlay Table!", return -1); - PP_ASSERT_WITH_CODE((pp_table->sHeader.ucTableFormatRevision >= - ATOM_Tonga_TABLE_REVISION_TONGA), - "Incorrect PowerPlay table revision!", return -1); - state_arrays = (ATOM_Tonga_State_Array *)(((unsigned long)pp_table) + - le16_to_cpu(pp_table->usStateArrayOffset)); + result = get_tonga_state_array(hwmgr, pp_table, &state_arrays); + PP_ASSERT_WITH_CODE((result == 0), + "Invalid PowerPlay Table State Array.", return result); return (uint32_t)(state_arrays->ucNumEntries); } @@ -1419,15 +1442,11 @@ int get_powerplay_table_entry_v1_0(struct pp_hwmgr *hwmgr, if (pp_table->sHeader.ucTableFormatRevision >= ATOM_Tonga_TABLE_REVISION_TONGA) { - state_arrays = (ATOM_Tonga_State_Array *)(((unsigned long)pp_table) + - le16_to_cpu(pp_table->usStateArrayOffset)); - - PP_ASSERT_WITH_CODE((0 < pp_table->usStateArrayOffset), - "Invalid PowerPlay Table State Array Offset.", return -1); - PP_ASSERT_WITH_CODE((0 < state_arrays->ucNumEntries), - "Invalid PowerPlay Table State Array.", return -1); - PP_ASSERT_WITH_CODE((entry_index <= state_arrays->ucNumEntries), - "Invalid PowerPlay Table State Array Entry.", return -1); + result = get_tonga_state_array(hwmgr, pp_table, &state_arrays); + PP_ASSERT_WITH_CODE((result == 0), + "Invalid PowerPlay Table State Array.", return result); + PP_ASSERT_WITH_CODE((entry_index < state_arrays->ucNumEntries), + "Invalid PowerPlay Table State Array Entry.", return -1); state_entry = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_State, entries, @@ -1453,4 +1472,3 @@ int get_powerplay_table_entry_v1_0(struct pp_hwmgr *hwmgr, return result; } - -- cgit v1.2.3 From 3a8d8e0b7f61cd759d5d4870b6220161f8a5b114 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 11:37:23 +0800 Subject: drm/amd/pm: Validate Vega hwmgr PowerPlay table bounds The Vega hwmgr PowerPlay table parsers read fixed table fields, state array entries, or SMC PPT fields before validating that the VBIOS table buffer covers those structures. A truncated table can therefore lead to out-of-bounds reads during hwmgr initialization. Reject tables smaller than the fixed PowerPlay table. For Vega10, also validate the state array offset and entry range before dereferencing the state array. Signed-off-by: Yang Wang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../pm/powerplay/hwmgr/vega10_processpptables.c | 59 ++++++++++++++-------- .../pm/powerplay/hwmgr/vega12_processpptables.c | 7 +++ .../pm/powerplay/hwmgr/vega20_processpptables.c | 7 +++ 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c index d32c8166f703..f1fd6d4520c8 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c @@ -63,14 +63,17 @@ static const void *get_powerplay_table(struct pp_hwmgr *hwmgr) return table_address; } -static int check_powerplay_tables( - struct pp_hwmgr *hwmgr, - const ATOM_Vega10_POWERPLAYTABLE *powerplay_table) +static int get_vega10_state_array(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const ATOM_Vega10_State_Array **state_array) { const ATOM_Vega10_State_Array *state_arrays; + u16 state_array_offset; + size_t state_array_size; + size_t table_size = hwmgr->soft_pp_table_size; - state_arrays = (ATOM_Vega10_State_Array *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usStateArrayOffset)); + PP_ASSERT_WITH_CODE((table_size >= sizeof(*powerplay_table)), + "Invalid PowerPlay Table!", return -1); PP_ASSERT_WITH_CODE((powerplay_table->sHeader.format_revision >= ATOM_Vega10_TABLE_REVISION_VEGA10), @@ -79,12 +82,34 @@ static int check_powerplay_tables( "State table is not set!", return -1); PP_ASSERT_WITH_CODE(powerplay_table->sHeader.structuresize > 0, "Invalid PowerPlay Table!", return -1); + + state_array_offset = le16_to_cpu(powerplay_table->usStateArrayOffset); + PP_ASSERT_WITH_CODE((state_array_offset <= + table_size - sizeof(*state_arrays)), + "Invalid PowerPlay Table!", return -1); + + state_arrays = (ATOM_Vega10_State_Array *)(((unsigned long)powerplay_table) + + state_array_offset); PP_ASSERT_WITH_CODE(state_arrays->ucNumEntries > 0, "Invalid PowerPlay Table!", return -1); + state_array_size = struct_size(state_arrays, states, state_arrays->ucNumEntries); + PP_ASSERT_WITH_CODE((state_array_size <= table_size - state_array_offset), + "Invalid PowerPlay Table!", return -1); + + *state_array = state_arrays; + return 0; } +static int check_powerplay_tables(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table) +{ + const ATOM_Vega10_State_Array *state_arrays; + + return get_vega10_state_array(hwmgr, powerplay_table, &state_arrays); +} + static int set_platform_caps(struct pp_hwmgr *hwmgr, uint32_t powerplay_caps) { set_hw_cap( @@ -1313,15 +1338,14 @@ int vega10_get_number_of_powerplay_table_entries(struct pp_hwmgr *hwmgr) { const ATOM_Vega10_State_Array *state_arrays; const ATOM_Vega10_POWERPLAYTABLE *pp_table = get_powerplay_table(hwmgr); + int result; PP_ASSERT_WITH_CODE((pp_table != NULL), "Missing PowerPlay Table!", return -1); - PP_ASSERT_WITH_CODE((pp_table->sHeader.format_revision >= - ATOM_Vega10_TABLE_REVISION_VEGA10), - "Incorrect PowerPlay table revision!", return -1); - state_arrays = (ATOM_Vega10_State_Array *)(((unsigned long)pp_table) + - le16_to_cpu(pp_table->usStateArrayOffset)); + result = get_vega10_state_array(hwmgr, pp_table, &state_arrays); + PP_ASSERT_WITH_CODE((result == 0), + "Invalid PowerPlay Table State Array.", return result); return (uint32_t)(state_arrays->ucNumEntries); } @@ -1372,17 +1396,11 @@ int vega10_get_powerplay_table_entry(struct pp_hwmgr *hwmgr, if (pp_table->sHeader.format_revision >= ATOM_Vega10_TABLE_REVISION_VEGA10) { - state_arrays = (ATOM_Vega10_State_Array *) - (((unsigned long)pp_table) + - le16_to_cpu(pp_table->usStateArrayOffset)); - - PP_ASSERT_WITH_CODE(pp_table->usStateArrayOffset > 0, - "Invalid PowerPlay Table State Array Offset.", - return -1); - PP_ASSERT_WITH_CODE(state_arrays->ucNumEntries > 0, + result = get_vega10_state_array(hwmgr, pp_table, &state_arrays); + PP_ASSERT_WITH_CODE((result == 0), "Invalid PowerPlay Table State Array.", - return -1); - PP_ASSERT_WITH_CODE((entry_index <= state_arrays->ucNumEntries), + return result); + PP_ASSERT_WITH_CODE((entry_index < state_arrays->ucNumEntries), "Invalid PowerPlay Table State Array Entry.", return -1); @@ -1424,4 +1442,3 @@ int vega10_baco_set_cap(struct pp_hwmgr *hwmgr) PHM_PlatformCaps_BACO); return result; } - diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_processpptables.c index 55e13f376039..dcb9c749eba3 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_processpptables.c @@ -64,6 +64,13 @@ static int check_powerplay_tables( struct pp_hwmgr *hwmgr, const ATOM_Vega12_POWERPLAYTABLE *powerplay_table) { + size_t smc_pptable_size = + offsetofend(ATOM_Vega12_POWERPLAYTABLE, smcPPTable); + size_t table_size = hwmgr->soft_pp_table_size; + + PP_ASSERT_WITH_CODE((table_size >= smc_pptable_size), + "Invalid PowerPlay Table!", return -1); + PP_ASSERT_WITH_CODE((powerplay_table->sHeader.format_revision >= ATOM_VEGA12_TABLE_REVISION_VEGA12), "Unsupported PPTable format!", return -1); diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_processpptables.c index 36cb7aa80d07..a0c884c2341d 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_processpptables.c @@ -66,6 +66,13 @@ static int check_powerplay_tables( struct pp_hwmgr *hwmgr, const ATOM_Vega20_POWERPLAYTABLE *powerplay_table) { + size_t smc_pptable_size = + offsetofend(ATOM_Vega20_POWERPLAYTABLE, smcPPTable); + size_t table_size = hwmgr->soft_pp_table_size; + + PP_ASSERT_WITH_CODE((table_size >= smc_pptable_size), + "Invalid PowerPlay Table!", return -1); + PP_ASSERT_WITH_CODE((powerplay_table->sHeader.format_revision >= ATOM_VEGA20_TABLE_REVISION_VEGA20), "Unsupported PPTable format!", return -1); -- cgit v1.2.3 From 0ceb6bc43e62f0e73b282c9de8f4bc4d5498f281 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 11:37:41 +0800 Subject: drm/amd/pm: Validate legacy hwmgr PP table offsets processpptables.c walks several variable-length PPLIB tables by using offsets from the VBIOS PowerPlay table. Some paths dereference extended headers, state arrays, clock arrays, non-clock arrays, or VCE records before checking that the referenced data is inside the table buffer. Add local bounds helpers and validate the relevant offsets and entry sizes before dereferencing them. This prevents truncated or malformed legacy PowerPlay tables from driving out-of-bounds reads during hwmgr initialization and table entry lookup. Signed-off-by: Yang Wang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../drm/amd/pm/powerplay/hwmgr/processpptables.c | 446 +++++++++++++++------ 1 file changed, 316 insertions(+), 130 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c index bfd8fbb0b49d..56926eec6820 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c @@ -47,26 +47,53 @@ #define NUM_BITS_CLOCK_INFO_ARRAY_INDEX 6 +static bool pp_table_has_space(struct pp_hwmgr *hwmgr, size_t offset, + size_t size) +{ + size_t table_size = hwmgr->soft_pp_table_size; + + return offset <= table_size && size <= table_size - offset; +} + +static const ATOM_PPLIB_EXTENDEDHEADER * +get_extended_header(struct pp_hwmgr *hwmgr, + const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table, + size_t min_size) +{ + const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; + u16 offset; + + if (le16_to_cpu(powerplay_table->usTableSize) < + sizeof(ATOM_PPLIB_POWERPLAYTABLE3) || + !pp_table_has_space(hwmgr, 0, sizeof(ATOM_PPLIB_POWERPLAYTABLE3))) + return NULL; + + powerplay_table3 = (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; + offset = le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset); + if (!offset || !pp_table_has_space(hwmgr, offset, + sizeof(extended_header->usSize))) + return NULL; + + extended_header = (const ATOM_PPLIB_EXTENDEDHEADER *) + (((unsigned long)powerplay_table) + offset); + if (le16_to_cpu(extended_header->usSize) < min_size || + !pp_table_has_space(hwmgr, offset, min_size)) + return NULL; + + return extended_header; +} + static uint16_t get_vce_table_offset(struct pp_hwmgr *hwmgr, const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t vce_table_offset = 0; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; - if (le16_to_cpu(powerplay_table->usTableSize) >= - sizeof(ATOM_PPLIB_POWERPLAYTABLE3)) { - const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3 = - (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; - - if (powerplay_table3->usExtendendedHeaderOffset > 0) { - const ATOM_PPLIB_EXTENDEDHEADER *extended_header = - (const ATOM_PPLIB_EXTENDEDHEADER *) - (((unsigned long)powerplay_table3) + - le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); - if (le16_to_cpu(extended_header->usSize) >= - SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V2) - vce_table_offset = le16_to_cpu(extended_header->usVCETableOffset); - } - } + extended_header = get_extended_header(hwmgr, powerplay_table, + SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V2); + if (extended_header) + vce_table_offset = le16_to_cpu(extended_header->usVCETableOffset); return vce_table_offset; } @@ -93,7 +120,14 @@ static uint16_t get_vce_clock_info_array_size(struct pp_hwmgr *hwmgr, if (table_offset > 0) { const VCEClockInfoArray *p = (const VCEClockInfoArray *) (((unsigned long) powerplay_table) + table_offset); - table_size = sizeof(uint8_t) + p->ucNumEntries * sizeof(VCEClockInfo); + size_t size; + + if (!pp_table_has_space(hwmgr, table_offset, sizeof(p->ucNumEntries))) + return 0; + + size = sizeof(uint8_t) + p->ucNumEntries * sizeof(VCEClockInfo); + if (pp_table_has_space(hwmgr, table_offset, size)) + table_size = size; } return table_size; @@ -104,10 +138,13 @@ static uint16_t get_vce_clock_voltage_limit_table_offset(struct pp_hwmgr *hwmgr, { uint16_t table_offset = get_vce_clock_info_array_offset(hwmgr, powerplay_table); + u16 table_size; - if (table_offset > 0) - return table_offset + get_vce_clock_info_array_size(hwmgr, - powerplay_table); + if (table_offset > 0) { + table_size = get_vce_clock_info_array_size(hwmgr, powerplay_table); + if (table_size) + return table_offset + table_size; + } return 0; } @@ -121,8 +158,15 @@ static uint16_t get_vce_clock_voltage_limit_table_size(struct pp_hwmgr *hwmgr, if (table_offset > 0) { const ATOM_PPLIB_VCE_Clock_Voltage_Limit_Table *ptable = (const ATOM_PPLIB_VCE_Clock_Voltage_Limit_Table *)(((unsigned long) powerplay_table) + table_offset); + size_t size; - table_size = sizeof(uint8_t) + ptable->numEntries * sizeof(ATOM_PPLIB_VCE_Clock_Voltage_Limit_Record); + if (!pp_table_has_space(hwmgr, table_offset, sizeof(ptable->numEntries))) + return 0; + + size = sizeof(uint8_t) + + ptable->numEntries * sizeof(ATOM_PPLIB_VCE_Clock_Voltage_Limit_Record); + if (pp_table_has_space(hwmgr, table_offset, size)) + table_size = size; } return table_size; } @@ -130,9 +174,13 @@ static uint16_t get_vce_clock_voltage_limit_table_size(struct pp_hwmgr *hwmgr, static uint16_t get_vce_state_table_offset(struct pp_hwmgr *hwmgr, const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t table_offset = get_vce_clock_voltage_limit_table_offset(hwmgr, powerplay_table); + u16 table_size; - if (table_offset > 0) - return table_offset + get_vce_clock_voltage_limit_table_size(hwmgr, powerplay_table); + if (table_offset > 0) { + table_size = get_vce_clock_voltage_limit_table_size(hwmgr, powerplay_table); + if (table_size) + return table_offset + table_size; + } return 0; } @@ -143,8 +191,12 @@ static const ATOM_PPLIB_VCE_State_Table *get_vce_state_table( { uint16_t table_offset = get_vce_state_table_offset(hwmgr, powerplay_table); - if (table_offset > 0) - return (const ATOM_PPLIB_VCE_State_Table *)(((unsigned long) powerplay_table) + table_offset); + if (table_offset > 0) { + if (pp_table_has_space(hwmgr, table_offset, + sizeof(((ATOM_PPLIB_VCE_State_Table *)0)->numEntries))) + return (const ATOM_PPLIB_VCE_State_Table *) + (((unsigned long)powerplay_table) + table_offset); + } return NULL; } @@ -153,21 +205,13 @@ static uint16_t get_uvd_table_offset(struct pp_hwmgr *hwmgr, const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t uvd_table_offset = 0; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; + + extended_header = get_extended_header(hwmgr, powerplay_table, + SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V3); + if (extended_header) + uvd_table_offset = le16_to_cpu(extended_header->usUVDTableOffset); - if (le16_to_cpu(powerplay_table->usTableSize) >= - sizeof(ATOM_PPLIB_POWERPLAYTABLE3)) { - const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3 = - (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; - if (powerplay_table3->usExtendendedHeaderOffset > 0) { - const ATOM_PPLIB_EXTENDEDHEADER *extended_header = - (const ATOM_PPLIB_EXTENDEDHEADER *) - (((unsigned long)powerplay_table3) + - le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); - if (le16_to_cpu(extended_header->usSize) >= - SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V3) - uvd_table_offset = le16_to_cpu(extended_header->usUVDTableOffset); - } - } return uvd_table_offset; } @@ -193,8 +237,14 @@ static uint16_t get_uvd_clock_info_array_size(struct pp_hwmgr *hwmgr, const UVDClockInfoArray *p = (const UVDClockInfoArray *) (((unsigned long) powerplay_table) + table_offset); - table_size = sizeof(UCHAR) + - p->ucNumEntries * sizeof(UVDClockInfo); + size_t size; + + if (!pp_table_has_space(hwmgr, table_offset, sizeof(p->ucNumEntries))) + return 0; + + size = sizeof(UCHAR) + p->ucNumEntries * sizeof(UVDClockInfo); + if (pp_table_has_space(hwmgr, table_offset, size)) + table_size = size; } return table_size; @@ -206,10 +256,13 @@ static uint16_t get_uvd_clock_voltage_limit_table_offset( { uint16_t table_offset = get_uvd_clock_info_array_offset(hwmgr, powerplay_table); + u16 table_size; - if (table_offset > 0) - return table_offset + - get_uvd_clock_info_array_size(hwmgr, powerplay_table); + if (table_offset > 0) { + table_size = get_uvd_clock_info_array_size(hwmgr, powerplay_table); + if (table_size) + return table_offset + table_size; + } return 0; } @@ -218,21 +271,12 @@ static uint16_t get_samu_table_offset(struct pp_hwmgr *hwmgr, const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t samu_table_offset = 0; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; - if (le16_to_cpu(powerplay_table->usTableSize) >= - sizeof(ATOM_PPLIB_POWERPLAYTABLE3)) { - const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3 = - (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; - if (powerplay_table3->usExtendendedHeaderOffset > 0) { - const ATOM_PPLIB_EXTENDEDHEADER *extended_header = - (const ATOM_PPLIB_EXTENDEDHEADER *) - (((unsigned long)powerplay_table3) + - le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); - if (le16_to_cpu(extended_header->usSize) >= - SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V4) - samu_table_offset = le16_to_cpu(extended_header->usSAMUTableOffset); - } - } + extended_header = get_extended_header(hwmgr, powerplay_table, + SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V4); + if (extended_header) + samu_table_offset = le16_to_cpu(extended_header->usSAMUTableOffset); return samu_table_offset; } @@ -254,21 +298,12 @@ static uint16_t get_acp_table_offset(struct pp_hwmgr *hwmgr, const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t acp_table_offset = 0; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; - if (le16_to_cpu(powerplay_table->usTableSize) >= - sizeof(ATOM_PPLIB_POWERPLAYTABLE3)) { - const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3 = - (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; - if (powerplay_table3->usExtendendedHeaderOffset > 0) { - const ATOM_PPLIB_EXTENDEDHEADER *pExtendedHeader = - (const ATOM_PPLIB_EXTENDEDHEADER *) - (((unsigned long)powerplay_table3) + - le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); - if (le16_to_cpu(pExtendedHeader->usSize) >= - SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V6) - acp_table_offset = le16_to_cpu(pExtendedHeader->usACPTableOffset); - } - } + extended_header = get_extended_header(hwmgr, powerplay_table, + SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V6); + if (extended_header) + acp_table_offset = le16_to_cpu(extended_header->usACPTableOffset); return acp_table_offset; } @@ -290,21 +325,12 @@ static uint16_t get_cacp_tdp_table_offset( const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t cacTdpTableOffset = 0; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; - if (le16_to_cpu(powerplay_table->usTableSize) >= - sizeof(ATOM_PPLIB_POWERPLAYTABLE3)) { - const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3 = - (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; - if (powerplay_table3->usExtendendedHeaderOffset > 0) { - const ATOM_PPLIB_EXTENDEDHEADER *pExtendedHeader = - (const ATOM_PPLIB_EXTENDEDHEADER *) - (((unsigned long)powerplay_table3) + - le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); - if (le16_to_cpu(pExtendedHeader->usSize) >= - SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V7) - cacTdpTableOffset = le16_to_cpu(pExtendedHeader->usPowerTuneTableOffset); - } - } + extended_header = get_extended_header(hwmgr, powerplay_table, + SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V7); + if (extended_header) + cacTdpTableOffset = le16_to_cpu(extended_header->usPowerTuneTableOffset); return cacTdpTableOffset; } @@ -341,22 +367,13 @@ static uint16_t get_sclk_vdd_gfx_table_offset(struct pp_hwmgr *hwmgr, const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table) { uint16_t sclk_vdd_gfx_table_offset = 0; + const ATOM_PPLIB_EXTENDEDHEADER *extended_header; - if (le16_to_cpu(powerplay_table->usTableSize) >= - sizeof(ATOM_PPLIB_POWERPLAYTABLE3)) { - const ATOM_PPLIB_POWERPLAYTABLE3 *powerplay_table3 = - (const ATOM_PPLIB_POWERPLAYTABLE3 *)powerplay_table; - if (powerplay_table3->usExtendendedHeaderOffset > 0) { - const ATOM_PPLIB_EXTENDEDHEADER *pExtendedHeader = - (const ATOM_PPLIB_EXTENDEDHEADER *) - (((unsigned long)powerplay_table3) + - le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); - if (le16_to_cpu(pExtendedHeader->usSize) >= - SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V8) - sclk_vdd_gfx_table_offset = - le16_to_cpu(pExtendedHeader->usSclkVddgfxTableOffset); - } - } + extended_header = get_extended_header(hwmgr, powerplay_table, + SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V8); + if (extended_header) + sclk_vdd_gfx_table_offset = + le16_to_cpu(extended_header->usSclkVddgfxTableOffset); return sclk_vdd_gfx_table_offset; } @@ -769,20 +786,37 @@ static ULONG size_of_entry_v2(ULONG num_dpm_levels) } static const ATOM_PPLIB_STATE_V2 *get_state_entry_v2( + struct pp_hwmgr *hwmgr, const StateArray * pstate_arrays, + u16 state_array_offset, ULONG entry_index) { ULONG i; const ATOM_PPLIB_STATE_V2 *pstate; + size_t entry_offset; + size_t entry_size; + + if (entry_index >= pstate_arrays->ucNumEntries) + return NULL; + entry_offset = state_array_offset + sizeof(pstate_arrays->ucNumEntries); pstate = pstate_arrays->states; - if (entry_index <= pstate_arrays->ucNumEntries) { - for (i = 0; i < entry_index; i++) - pstate = (ATOM_PPLIB_STATE_V2 *)( - (unsigned long)pstate + - size_of_entry_v2(pstate->ucNumDPMLevels)); + for (i = 0; i <= entry_index; i++) { + if (!pp_table_has_space(hwmgr, entry_offset, sizeof(*pstate))) + return NULL; + + entry_size = size_of_entry_v2(pstate->ucNumDPMLevels); + if (!pp_table_has_space(hwmgr, entry_offset, entry_size)) + return NULL; + + if (i == entry_index) + return pstate; + + entry_offset += entry_size; + pstate = (ATOM_PPLIB_STATE_V2 *)((unsigned long)pstate + entry_size); } - return pstate; + + return NULL; } static const unsigned char soft_dummy_pp_table[] = { @@ -850,6 +884,8 @@ int pp_tables_get_response_times(struct pp_hwmgr *hwmgr, PP_ASSERT_WITH_CODE(NULL != powerplay_tab, "Missing PowerPlay Table!", return -EINVAL); + PP_ASSERT_WITH_CODE(pp_table_has_space(hwmgr, 0, sizeof(*powerplay_tab)), + "Invalid PowerPlay Table!", return -EINVAL); *vol_rep_time = (uint32_t)le16_to_cpu(powerplay_tab->usVoltageTime); *bb_rep_time = (uint32_t)le16_to_cpu(powerplay_tab->usBackbiasTime); @@ -862,13 +898,21 @@ int pp_tables_get_num_of_entries(struct pp_hwmgr *hwmgr, { const StateArray *pstate_arrays; const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table = get_powerplay_table(hwmgr); + u16 state_array_offset; if (powerplay_table == NULL) return -1; + if (!pp_table_has_space(hwmgr, 0, sizeof(*powerplay_table))) + return -1; if (powerplay_table->sHeader.ucTableFormatRevision >= 6) { + state_array_offset = le16_to_cpu(powerplay_table->usStateArrayOffset); + if (!pp_table_has_space(hwmgr, state_array_offset, + sizeof(pstate_arrays->ucNumEntries))) + return -1; + pstate_arrays = (StateArray *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usStateArrayOffset)); + state_array_offset); *num_of_entries = (unsigned long)(pstate_arrays->ucNumEntries); } else @@ -895,49 +939,128 @@ int pp_tables_get_entry(struct pp_hwmgr *hwmgr, const NonClockInfoArray *pnon_clock_arrays; const ATOM_PPLIB_STATE *pstate_entry; + u16 state_array_offset; + u16 clock_info_array_offset; + u16 non_clock_info_array_offset; + size_t clock_info_offset; + size_t non_clock_info_offset; + size_t state_entry_offset; if (powerplay_table == NULL) return -1; + if (!pp_table_has_space(hwmgr, 0, sizeof(*powerplay_table))) + return -1; ps->classification.bios_index = entry_index; if (powerplay_table->sHeader.ucTableFormatRevision >= 6) { + state_array_offset = le16_to_cpu(powerplay_table->usStateArrayOffset); + if (!pp_table_has_space(hwmgr, state_array_offset, + sizeof(pstate_arrays->ucNumEntries))) + return -1; + pstate_arrays = (StateArray *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usStateArrayOffset)); + state_array_offset); + + if (entry_index >= pstate_arrays->ucNumEntries) + return -1; - if (entry_index > pstate_arrays->ucNumEntries) + pstate_entry_v2 = get_state_entry_v2(hwmgr, pstate_arrays, + state_array_offset, + entry_index); + if (!pstate_entry_v2) + return -1; + + clock_info_array_offset = + le16_to_cpu(powerplay_table->usClockInfoArrayOffset); + if (!pp_table_has_space(hwmgr, clock_info_array_offset, + sizeof(*pclock_arrays))) return -1; - pstate_entry_v2 = get_state_entry_v2(pstate_arrays, entry_index); pclock_arrays = (ClockInfoArray *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usClockInfoArrayOffset)); + clock_info_array_offset); + if (!pclock_arrays->ucEntrySize) + return -1; + + non_clock_info_array_offset = + le16_to_cpu(powerplay_table->usNonClockInfoArrayOffset); + if (!pp_table_has_space(hwmgr, non_clock_info_array_offset, + sizeof(*pnon_clock_arrays))) + return -1; pnon_clock_arrays = (NonClockInfoArray *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usNonClockInfoArrayOffset)); + non_clock_info_array_offset); + if (!pnon_clock_arrays->ucEntrySize || + pnon_clock_arrays->ucEntrySize < ATOM_PPLIB_NONCLOCKINFO_VER1 || + (pnon_clock_arrays->ucEntrySize > ATOM_PPLIB_NONCLOCKINFO_VER1 && + pnon_clock_arrays->ucEntrySize < ATOM_PPLIB_NONCLOCKINFO_VER2) || + pstate_entry_v2->nonClockInfoIndex >= pnon_clock_arrays->ucNumEntries) + return -1; + non_clock_info_offset = non_clock_info_array_offset + + offsetof(NonClockInfoArray, nonClockInfo) + + pstate_entry_v2->nonClockInfoIndex * pnon_clock_arrays->ucEntrySize; + if (!pp_table_has_space(hwmgr, non_clock_info_offset, + pnon_clock_arrays->ucEntrySize)) + return -1; pnon_clock_info = (ATOM_PPLIB_NONCLOCK_INFO *)((unsigned long)(pnon_clock_arrays->nonClockInfo) + (pstate_entry_v2->nonClockInfoIndex * pnon_clock_arrays->ucEntrySize)); result = init_non_clock_fields(hwmgr, ps, pnon_clock_arrays->ucEntrySize, pnon_clock_info); for (i = 0; i < pstate_entry_v2->ucNumDPMLevels; i++) { - const void *pclock_info = (const void *)( - (unsigned long)(pclock_arrays->clockInfo) + - (pstate_entry_v2->clockInfoIndex[i] * pclock_arrays->ucEntrySize)); + const void *pclock_info; + + if (pstate_entry_v2->clockInfoIndex[i] >= + pclock_arrays->ucNumEntries) + return -1; + + clock_info_offset = clock_info_array_offset + + offsetof(ClockInfoArray, clockInfo) + + pstate_entry_v2->clockInfoIndex[i] * pclock_arrays->ucEntrySize; + if (!pp_table_has_space(hwmgr, clock_info_offset, + pclock_arrays->ucEntrySize)) + return -1; + + pclock_info = (const void *) + ((unsigned long)(pclock_arrays->clockInfo) + + (pstate_entry_v2->clockInfoIndex[i] * + pclock_arrays->ucEntrySize)); res = func(hwmgr, &ps->hardware, i, pclock_info); if ((0 == result) && (0 != res)) result = res; } } else { - if (entry_index > powerplay_table->ucNumStates) + if (entry_index >= powerplay_table->ucNumStates || + !powerplay_table->ucStateEntrySize || + !powerplay_table->ucNonClockSize || + powerplay_table->ucNonClockSize < ATOM_PPLIB_NONCLOCKINFO_VER1 || + (powerplay_table->ucNonClockSize > ATOM_PPLIB_NONCLOCKINFO_VER1 && + powerplay_table->ucNonClockSize < ATOM_PPLIB_NONCLOCKINFO_VER2) || + !powerplay_table->ucClockInfoSize) + return -1; + + state_array_offset = le16_to_cpu(powerplay_table->usStateArrayOffset); + state_entry_offset = state_array_offset + + entry_index * powerplay_table->ucStateEntrySize; + if (!pp_table_has_space(hwmgr, state_entry_offset, + powerplay_table->ucStateEntrySize)) return -1; pstate_entry = (ATOM_PPLIB_STATE *)((unsigned long)powerplay_table + - le16_to_cpu(powerplay_table->usStateArrayOffset) + + state_array_offset + entry_index * powerplay_table->ucStateEntrySize); + non_clock_info_array_offset = + le16_to_cpu(powerplay_table->usNonClockInfoArrayOffset); + non_clock_info_offset = non_clock_info_array_offset + + pstate_entry->ucNonClockStateIndex * powerplay_table->ucNonClockSize; + if (!pp_table_has_space(hwmgr, non_clock_info_offset, + powerplay_table->ucNonClockSize)) + return -1; + pnon_clock_info = (ATOM_PPLIB_NONCLOCK_INFO *)((unsigned long)powerplay_table + - le16_to_cpu(powerplay_table->usNonClockInfoArrayOffset) + + non_clock_info_array_offset + pstate_entry->ucNonClockStateIndex * powerplay_table->ucNonClockSize); @@ -946,12 +1069,23 @@ int pp_tables_get_entry(struct pp_hwmgr *hwmgr, pnon_clock_info); for (i = 0; i < powerplay_table->ucStateEntrySize-1; i++) { - const void *pclock_info = (const void *)((unsigned long)powerplay_table + - le16_to_cpu(powerplay_table->usClockInfoArrayOffset) + + const void *pclock_info; + + clock_info_array_offset = + le16_to_cpu(powerplay_table->usClockInfoArrayOffset); + clock_info_offset = clock_info_array_offset + + pstate_entry->ucClockStateIndices[i] * + powerplay_table->ucClockInfoSize; + if (!pp_table_has_space(hwmgr, clock_info_offset, + powerplay_table->ucClockInfoSize)) + return -1; + + pclock_info = (const void *)((unsigned long)powerplay_table + + clock_info_array_offset + pstate_entry->ucClockStateIndices[i] * powerplay_table->ucClockInfoSize); - int res = func(hwmgr, &ps->hardware, i, pclock_info); + res = func(hwmgr, &ps->hardware, i, pclock_info); if ((0 == result) && (0 != res)) result = res; @@ -1661,21 +1795,70 @@ static int get_vce_state_table_entry(struct pp_hwmgr *hwmgr, unsigned long *flag) { const ATOM_PPLIB_POWERPLAYTABLE *powerplay_table = get_powerplay_table(hwmgr); + const ATOM_PPLIB_VCE_State_Table *vce_state_table; + const ATOM_PPLIB_VCE_State_Record *record; + const VCEClockInfoArray *vce_clock_info_array; + const VCEClockInfo *vce_clock_info; + const ClockInfoArray *clock_arrays; + u16 vce_state_table_offset; + u16 vce_clock_info_array_offset; + u16 clock_info_array_offset; + unsigned long clockInfoIndex; + size_t record_offset; + size_t vce_clock_info_offset; + size_t clock_info_offset; + + if (!powerplay_table || !pp_table_has_space(hwmgr, 0, sizeof(*powerplay_table))) + return -1; - const ATOM_PPLIB_VCE_State_Table *vce_state_table = get_vce_state_table(hwmgr, powerplay_table); + vce_state_table_offset = get_vce_state_table_offset(hwmgr, powerplay_table); + vce_state_table = get_vce_state_table(hwmgr, powerplay_table); + if (!vce_state_table || i >= vce_state_table->numEntries) + return -1; + + record_offset = vce_state_table_offset + + offsetof(ATOM_PPLIB_VCE_State_Table, entries) + + i * sizeof(*record); + if (!pp_table_has_space(hwmgr, record_offset, sizeof(*record))) + return -1; + + record = &vce_state_table->entries[i]; + + vce_clock_info_array_offset = + get_vce_clock_info_array_offset(hwmgr, powerplay_table); + if (!pp_table_has_space(hwmgr, vce_clock_info_array_offset, + sizeof(vce_clock_info_array->ucNumEntries))) + return -1; - unsigned short vce_clock_info_array_offset = get_vce_clock_info_array_offset(hwmgr, powerplay_table); + vce_clock_info_array = (const VCEClockInfoArray *) + (((unsigned long)powerplay_table) + vce_clock_info_array_offset); + if (record->ucVCEClockInfoIndex >= vce_clock_info_array->ucNumEntries) + return -1; - const VCEClockInfoArray *vce_clock_info_array = (const VCEClockInfoArray *)(((unsigned long) powerplay_table) + vce_clock_info_array_offset); + vce_clock_info_offset = vce_clock_info_array_offset + + offsetof(VCEClockInfoArray, entries) + + record->ucVCEClockInfoIndex * sizeof(*vce_clock_info); + if (!pp_table_has_space(hwmgr, vce_clock_info_offset, sizeof(*vce_clock_info))) + return -1; - const ClockInfoArray *clock_arrays = (ClockInfoArray *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usClockInfoArrayOffset)); + vce_clock_info = &vce_clock_info_array->entries[record->ucVCEClockInfoIndex]; - const ATOM_PPLIB_VCE_State_Record *record = &vce_state_table->entries[i]; + clock_info_array_offset = le16_to_cpu(powerplay_table->usClockInfoArrayOffset); + if (!pp_table_has_space(hwmgr, clock_info_array_offset, + sizeof(*clock_arrays))) + return -1; - const VCEClockInfo *vce_clock_info = &vce_clock_info_array->entries[record->ucVCEClockInfoIndex]; + clock_arrays = (ClockInfoArray *)(((unsigned long)powerplay_table) + + clock_info_array_offset); + clockInfoIndex = record->ucClockInfoIndex & 0x3F; + if (!clock_arrays->ucEntrySize || clockInfoIndex >= clock_arrays->ucNumEntries) + return -1; - unsigned long clockInfoIndex = record->ucClockInfoIndex & 0x3F; + clock_info_offset = clock_info_array_offset + + offsetof(ClockInfoArray, clockInfo) + + clockInfoIndex * clock_arrays->ucEntrySize; + if (!pp_table_has_space(hwmgr, clock_info_offset, clock_arrays->ucEntrySize)) + return -1; *flag = (record->ucClockInfoIndex >> NUM_BITS_CLOCK_INFO_ARRAY_INDEX); @@ -1699,6 +1882,10 @@ static int pp_tables_initialize(struct pp_hwmgr *hwmgr) hwmgr->need_pp_table_upload = true; powerplay_table = get_powerplay_table(hwmgr); + PP_ASSERT_WITH_CODE((powerplay_table), + "Missing PowerPlay Table!", return -1); + PP_ASSERT_WITH_CODE(pp_table_has_space(hwmgr, 0, sizeof(*powerplay_table)), + "Invalid PowerPlay Table!", return -1); result = init_powerplay_tables(hwmgr, powerplay_table); @@ -1801,4 +1988,3 @@ const struct pp_table_func pptable_funcs = { .pptable_get_vce_state_table_entry = get_vce_state_table_entry, }; - -- cgit v1.2.3 From 9914149f99fd2ddcfd2a8c1053a57d96ec43448e Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 16:36:21 +0800 Subject: drm/amd/pm: Validate Vega10 PPTable subtable bounds v1: Vega10 PPTable parsing uses VBIOS-provided offsets, revision fields and entry counts to locate subtables. Malformed data can otherwise drive out-of-bounds reads from soft_pp_table_size, and voltage lookup tables can overrun their fixed-size destination arrays. Add shared bounds helpers and validate fixed-size subtables, dynamic entry arrays and revision-specific layouts before consuming thermal, fan, power-tune, clock dependency, PCIE, hard-limit and voltage lookup data. v2: if ucRevId is not matched, fallback to default table size instead of returning -EINVAL. Signed-off-by: Yang Wang Reviewed-by: Asad Kamal Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- .../pm/powerplay/hwmgr/vega10_processpptables.c | 564 +++++++++++++++++---- 1 file changed, 459 insertions(+), 105 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c index f1fd6d4520c8..62b1e068f90d 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_processpptables.c @@ -63,6 +63,46 @@ static const void *get_powerplay_table(struct pp_hwmgr *hwmgr) return table_address; } +static bool vega10_pp_table_has_space(struct pp_hwmgr *hwmgr, size_t offset, + size_t size) +{ + size_t table_size = hwmgr->soft_pp_table_size; + + return offset <= table_size && size <= table_size - offset; +} + +static int get_vega10_subtable(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + u16 table_offset, size_t table_size, const void **table) +{ + PP_ASSERT_WITH_CODE((table_offset != 0), + "Invalid PowerPlay Table!", return -1); + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *table = (const void *)(((unsigned long)powerplay_table) + table_offset); + + return 0; +} + +static int validate_vega10_table_entries(struct pp_hwmgr *hwmgr, + u16 table_offset, size_t entries_offset, + u8 num_entries, size_t entry_size) +{ + size_t table_size; + + PP_ASSERT_WITH_CODE((num_entries != 0), + "Invalid PowerPlay Table!", return -1); + + table_size = entries_offset + num_entries * entry_size; + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + return 0; +} + static int get_vega10_state_array(struct pp_hwmgr *hwmgr, const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, const ATOM_Vega10_State_Array **state_array) @@ -102,6 +142,293 @@ static int get_vega10_state_array(struct pp_hwmgr *hwmgr, return 0; } +static int get_vega10_gfxclk_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const ATOM_Vega10_GFXCLK_Dependency_Table **gfxclk_dep_table) +{ + const ATOM_Vega10_GFXCLK_Dependency_Table *table; + u16 table_offset; + size_t table_size; + size_t entry_size; + + table_offset = le16_to_cpu(powerplay_table->usGfxclkDependencyTableOffset); + if (!table_offset) + return -EINVAL; + + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + sizeof(*table))), + "Invalid PowerPlay Table!", return -1); + + table = (const ATOM_Vega10_GFXCLK_Dependency_Table *) + (((unsigned long)powerplay_table) + table_offset); + PP_ASSERT_WITH_CODE((table->ucNumEntries != 0), + "Invalid PowerPlay Table!", return -1); + + if (table->ucRevId == 0) + entry_size = sizeof(ATOM_Vega10_GFXCLK_Dependency_Record); + else if (table->ucRevId == 1) + entry_size = sizeof(ATOM_Vega10_GFXCLK_Dependency_Record_V2); + else + PP_ASSERT_WITH_CODE(false, + "Unsupported GFXClockDependencyTable Revision!", + return -EINVAL); + + table_size = offsetof(ATOM_Vega10_GFXCLK_Dependency_Table, entries) + + table->ucNumEntries * entry_size; + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *gfxclk_dep_table = table; + + return 0; +} + +static int get_vega10_clk_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + u16 table_offset, + const ATOM_Vega10_SOCCLK_Dependency_Table **clk_dep_table) +{ + const ATOM_Vega10_SOCCLK_Dependency_Table *table; + int ret; + + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_vega10_table_entries(hwmgr, table_offset, + offsetof(ATOM_Vega10_SOCCLK_Dependency_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Vega10_CLK_Dependency_Record)); + if (ret) + return ret; + + *clk_dep_table = table; + + return 0; +} + +static int get_vega10_mclk_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const ATOM_Vega10_MCLK_Dependency_Table **mclk_dep_table) +{ + const ATOM_Vega10_MCLK_Dependency_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usMclkDependencyTableOffset); + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_vega10_table_entries(hwmgr, table_offset, + offsetof(ATOM_Vega10_MCLK_Dependency_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Vega10_MCLK_Dependency_Record)); + if (ret) + return ret; + + *mclk_dep_table = table; + + return 0; +} + +static int get_vega10_mm_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const ATOM_Vega10_MM_Dependency_Table **mm_dep_table) +{ + const ATOM_Vega10_MM_Dependency_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usMMDependencyTableOffset); + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_vega10_table_entries(hwmgr, table_offset, + offsetof(ATOM_Vega10_MM_Dependency_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Vega10_MM_Dependency_Record)); + if (ret) + return ret; + + *mm_dep_table = table; + + return 0; +} + +static int get_vega10_pcie_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const Vega10_PPTable_Generic_SubTable_Header **pcie_table) +{ + const ATOM_Vega10_PCIE_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usPCIETableOffset); + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + if (!table->ucNumEntries) { + *pcie_table = (const Vega10_PPTable_Generic_SubTable_Header *)table; + return 0; + } + + ret = validate_vega10_table_entries(hwmgr, table_offset, + offsetof(ATOM_Vega10_PCIE_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Vega10_PCIE_Record)); + if (ret) + return ret; + + *pcie_table = (const Vega10_PPTable_Generic_SubTable_Header *)table; + + return 0; +} + +static int get_vega10_hard_limit_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const ATOM_Vega10_Hard_Limit_Table **hard_limit_table) +{ + const ATOM_Vega10_Hard_Limit_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usHardLimitTableOffset); + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_vega10_table_entries(hwmgr, table_offset, + offsetof(ATOM_Vega10_Hard_Limit_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Vega10_Hard_Limit_Record)); + if (ret) + return ret; + + *hard_limit_table = table; + + return 0; +} + +static int get_vega10_thermal_controller_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const ATOM_Vega10_Thermal_Controller **thermal_controller) +{ + u16 table_offset; + + table_offset = le16_to_cpu(powerplay_table->usThermalControllerOffset); + + return get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(**thermal_controller), + (const void **)thermal_controller); +} + +static int get_vega10_fan_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const Vega10_PPTable_Generic_SubTable_Header **fan_table) +{ + const Vega10_PPTable_Generic_SubTable_Header *header; + u16 table_offset; + size_t table_size; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usFanTableOffset); + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*header), (const void **)&header); + if (ret) + return ret; + + if (header->ucRevId == 10) + table_size = sizeof(ATOM_Vega10_Fan_Table); + else if (header->ucRevId == 0xb) + table_size = sizeof(ATOM_Vega10_Fan_Table_V2); + else if (header->ucRevId > 0xb) + table_size = sizeof(ATOM_Vega10_Fan_Table_V3); + else + table_size = sizeof(*header); + + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *fan_table = header; + + return 0; +} + +static int get_vega10_power_tune_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + const Vega10_PPTable_Generic_SubTable_Header **power_tune_table) +{ + const Vega10_PPTable_Generic_SubTable_Header *header; + u16 table_offset; + size_t table_size; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usPowerTuneTableOffset); + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*header), (const void **)&header); + if (ret) + return ret; + + if (header->ucRevId == 5) + table_size = sizeof(ATOM_Vega10_PowerTune_Table); + else if (header->ucRevId == 6) + table_size = sizeof(ATOM_Vega10_PowerTune_Table_V2); + else + table_size = sizeof(ATOM_Vega10_PowerTune_Table_V3); + + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *power_tune_table = header; + + return 0; +} + +static int get_vega10_voltage_lookup_table(struct pp_hwmgr *hwmgr, + const ATOM_Vega10_POWERPLAYTABLE *powerplay_table, + u16 table_offset, uint32_t max_levels, + const ATOM_Vega10_Voltage_Lookup_Table **lookup_table) +{ + const ATOM_Vega10_Voltage_Lookup_Table *table; + size_t table_size; + int ret; + + ret = get_vega10_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + PP_ASSERT_WITH_CODE((table->ucNumEntries != 0 && + table->ucNumEntries <= max_levels), + "Invalid PowerPlay Table!", return -1); + + table_size = offsetof(ATOM_Vega10_Voltage_Lookup_Table, entries) + + table->ucNumEntries * sizeof(ATOM_Vega10_Voltage_Lookup_Record); + PP_ASSERT_WITH_CODE((vega10_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *lookup_table = table; + + return 0; +} + static int check_powerplay_tables(struct pp_hwmgr *hwmgr, const ATOM_Vega10_POWERPLAYTABLE *powerplay_table) { @@ -149,14 +476,16 @@ static int init_thermal_controller( const ATOM_Vega10_Fan_Table *fan_table_v1; const ATOM_Vega10_Fan_Table_V2 *fan_table_v2; const ATOM_Vega10_Fan_Table_V3 *fan_table_v3; - - thermal_controller = (ATOM_Vega10_Thermal_Controller *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usThermalControllerOffset)); + int ret; PP_ASSERT_WITH_CODE((powerplay_table->usThermalControllerOffset != 0), "Thermal controller table not set!", return -EINVAL); + ret = get_vega10_thermal_controller_table(hwmgr, powerplay_table, + &thermal_controller); + if (ret) + return ret; + hwmgr->thermal_controller.ucType = thermal_controller->ucType; hwmgr->thermal_controller.ucI2cLine = thermal_controller->ucI2cLine; hwmgr->thermal_controller.ucI2cAddress = thermal_controller->ucI2cAddress; @@ -185,9 +514,9 @@ static int init_thermal_controller( if (!powerplay_table->usFanTableOffset) return 0; - header = (const Vega10_PPTable_Generic_SubTable_Header *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usFanTableOffset)); + ret = get_vega10_fan_table(hwmgr, powerplay_table, &header); + if (ret) + return ret; if (header->ucRevId == 10) { fan_table_v1 = (ATOM_Vega10_Fan_Table *)header; @@ -332,12 +661,15 @@ static int init_over_drive_limits( struct pp_hwmgr *hwmgr, const ATOM_Vega10_POWERPLAYTABLE *powerplay_table) { - const ATOM_Vega10_GFXCLK_Dependency_Table *gfxclk_dep_table = - (const ATOM_Vega10_GFXCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usGfxclkDependencyTableOffset)); + const ATOM_Vega10_GFXCLK_Dependency_Table *gfxclk_dep_table; bool is_acg_enabled = false; ATOM_Vega10_GFXCLK_Dependency_Record_V2 *patom_record_v2; + int ret; + + ret = get_vega10_gfxclk_dependency_table(hwmgr, powerplay_table, + &gfxclk_dep_table); + if (ret) + return ret; if (gfxclk_dep_table->ucRevId == 1) { patom_record_v2 = @@ -954,51 +1286,13 @@ static int init_powerplay_extended_tables( int result = 0; struct phm_ppt_v2_information *pp_table_info = (struct phm_ppt_v2_information *)(hwmgr->pptable); - - const ATOM_Vega10_MM_Dependency_Table *mm_dependency_table = - (const ATOM_Vega10_MM_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usMMDependencyTableOffset)); - const Vega10_PPTable_Generic_SubTable_Header *power_tune_table = - (const Vega10_PPTable_Generic_SubTable_Header *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usPowerTuneTableOffset)); - const ATOM_Vega10_SOCCLK_Dependency_Table *socclk_dep_table = - (const ATOM_Vega10_SOCCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usSocclkDependencyTableOffset)); - const ATOM_Vega10_GFXCLK_Dependency_Table *gfxclk_dep_table = - (const ATOM_Vega10_GFXCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usGfxclkDependencyTableOffset)); - const ATOM_Vega10_DCEFCLK_Dependency_Table *dcefclk_dep_table = - (const ATOM_Vega10_DCEFCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usDcefclkDependencyTableOffset)); - const ATOM_Vega10_MCLK_Dependency_Table *mclk_dep_table = - (const ATOM_Vega10_MCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usMclkDependencyTableOffset)); - const ATOM_Vega10_Hard_Limit_Table *hard_limits = - (const ATOM_Vega10_Hard_Limit_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usHardLimitTableOffset)); - const Vega10_PPTable_Generic_SubTable_Header *pcie_table = - (const Vega10_PPTable_Generic_SubTable_Header *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usPCIETableOffset)); - const ATOM_Vega10_PIXCLK_Dependency_Table *pixclk_dep_table = - (const ATOM_Vega10_PIXCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usPixclkDependencyTableOffset)); - const ATOM_Vega10_PHYCLK_Dependency_Table *phyclk_dep_table = - (const ATOM_Vega10_PHYCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usPhyClkDependencyTableOffset)); - const ATOM_Vega10_DISPCLK_Dependency_Table *dispclk_dep_table = - (const ATOM_Vega10_DISPCLK_Dependency_Table *) - (((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usDispClkDependencyTableOffset)); + const ATOM_Vega10_MM_Dependency_Table *mm_dependency_table; + const Vega10_PPTable_Generic_SubTable_Header *power_tune_table; + const ATOM_Vega10_GFXCLK_Dependency_Table *gfxclk_dep_table; + const ATOM_Vega10_MCLK_Dependency_Table *mclk_dep_table; + const ATOM_Vega10_Hard_Limit_Table *hard_limits; + const Vega10_PPTable_Generic_SubTable_Header *pcie_table; + const ATOM_Vega10_SOCCLK_Dependency_Table *clk_dep_table; pp_table_info->vdd_dep_on_socclk = NULL; pp_table_info->vdd_dep_on_sclk = NULL; @@ -1010,63 +1304,114 @@ static int init_powerplay_extended_tables( pp_table_info->vdd_dep_on_phyclk = NULL; pp_table_info->vdd_dep_on_dispclk = NULL; - if (powerplay_table->usMMDependencyTableOffset) - result = get_mm_clock_voltage_table(hwmgr, + if (powerplay_table->usMMDependencyTableOffset) { + result = get_vega10_mm_dependency_table(hwmgr, powerplay_table, + &mm_dependency_table); + if (!result) + result = get_mm_clock_voltage_table(hwmgr, &pp_table_info->mm_dep_table, mm_dependency_table); + } - if (!result && powerplay_table->usPowerTuneTableOffset) - result = get_tdp_table(hwmgr, + if (!result && powerplay_table->usPowerTuneTableOffset) { + result = get_vega10_power_tune_table(hwmgr, powerplay_table, + &power_tune_table); + if (!result) + result = get_tdp_table(hwmgr, &pp_table_info->tdp_table, power_tune_table); + } - if (!result && powerplay_table->usSocclkDependencyTableOffset) - result = get_socclk_voltage_dependency_table(hwmgr, + if (!result && powerplay_table->usSocclkDependencyTableOffset) { + result = get_vega10_clk_dependency_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usSocclkDependencyTableOffset), + &clk_dep_table); + if (!result) + result = get_socclk_voltage_dependency_table(hwmgr, &pp_table_info->vdd_dep_on_socclk, - socclk_dep_table); + (const ATOM_Vega10_SOCCLK_Dependency_Table *) + clk_dep_table); + } - if (!result && powerplay_table->usGfxclkDependencyTableOffset) - result = get_gfxclk_voltage_dependency_table(hwmgr, - &pp_table_info->vdd_dep_on_sclk, - gfxclk_dep_table); + if (!result && powerplay_table->usGfxclkDependencyTableOffset) { + result = get_vega10_gfxclk_dependency_table(hwmgr, + powerplay_table, &gfxclk_dep_table); + if (!result) + result = get_gfxclk_voltage_dependency_table(hwmgr, + &pp_table_info->vdd_dep_on_sclk, + gfxclk_dep_table); + } - if (!result && powerplay_table->usPixclkDependencyTableOffset) - result = get_pix_clk_voltage_dependency_table(hwmgr, + if (!result && powerplay_table->usPixclkDependencyTableOffset) { + result = get_vega10_clk_dependency_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usPixclkDependencyTableOffset), + &clk_dep_table); + if (!result) + result = get_pix_clk_voltage_dependency_table(hwmgr, &pp_table_info->vdd_dep_on_pixclk, (const ATOM_Vega10_PIXCLK_Dependency_Table *) - pixclk_dep_table); + clk_dep_table); + } - if (!result && powerplay_table->usPhyClkDependencyTableOffset) - result = get_pix_clk_voltage_dependency_table(hwmgr, + if (!result && powerplay_table->usPhyClkDependencyTableOffset) { + result = get_vega10_clk_dependency_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usPhyClkDependencyTableOffset), + &clk_dep_table); + if (!result) + result = get_pix_clk_voltage_dependency_table(hwmgr, &pp_table_info->vdd_dep_on_phyclk, (const ATOM_Vega10_PIXCLK_Dependency_Table *) - phyclk_dep_table); + clk_dep_table); + } - if (!result && powerplay_table->usDispClkDependencyTableOffset) - result = get_pix_clk_voltage_dependency_table(hwmgr, + if (!result && powerplay_table->usDispClkDependencyTableOffset) { + result = get_vega10_clk_dependency_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usDispClkDependencyTableOffset), + &clk_dep_table); + if (!result) + result = get_pix_clk_voltage_dependency_table(hwmgr, &pp_table_info->vdd_dep_on_dispclk, (const ATOM_Vega10_PIXCLK_Dependency_Table *) - dispclk_dep_table); + clk_dep_table); + } - if (!result && powerplay_table->usDcefclkDependencyTableOffset) - result = get_dcefclk_voltage_dependency_table(hwmgr, + if (!result && powerplay_table->usDcefclkDependencyTableOffset) { + result = get_vega10_clk_dependency_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usDcefclkDependencyTableOffset), + &clk_dep_table); + if (!result) + result = get_dcefclk_voltage_dependency_table(hwmgr, &pp_table_info->vdd_dep_on_dcefclk, - dcefclk_dep_table); + (const ATOM_Vega10_DCEFCLK_Dependency_Table *) + clk_dep_table); + } - if (!result && powerplay_table->usMclkDependencyTableOffset) - result = get_mclk_voltage_dependency_table(hwmgr, + if (!result && powerplay_table->usMclkDependencyTableOffset) { + result = get_vega10_mclk_dependency_table(hwmgr, powerplay_table, + &mclk_dep_table); + if (!result) + result = get_mclk_voltage_dependency_table(hwmgr, &pp_table_info->vdd_dep_on_mclk, mclk_dep_table); + } - if (!result && powerplay_table->usPCIETableOffset) - result = get_pcie_table(hwmgr, + if (!result && powerplay_table->usPCIETableOffset) { + result = get_vega10_pcie_table(hwmgr, powerplay_table, + &pcie_table); + if (!result) + result = get_pcie_table(hwmgr, &pp_table_info->pcie_table, pcie_table); + } - if (!result && powerplay_table->usHardLimitTableOffset) - result = get_hard_limits(hwmgr, + if (!result && powerplay_table->usHardLimitTableOffset) { + result = get_vega10_hard_limit_table(hwmgr, powerplay_table, + &hard_limits); + if (!result) + result = get_hard_limits(hwmgr, &pp_table_info->max_clock_voltage_on_dc, hard_limits); + } hwmgr->dyn_state.max_clock_voltage_on_dc.sclk = pp_table_info->max_clock_voltage_on_dc.sclk; @@ -1204,30 +1549,39 @@ static int init_dpm_2_parameters( } if (powerplay_table->usVddcLookupTableOffset) { - const ATOM_Vega10_Voltage_Lookup_Table *vddc_table = - (ATOM_Vega10_Voltage_Lookup_Table *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usVddcLookupTableOffset)); - result = get_vddc_lookup_table(hwmgr, - &pp_table_info->vddc_lookup_table, vddc_table, 8); + const ATOM_Vega10_Voltage_Lookup_Table *vddc_table; + + result = get_vega10_voltage_lookup_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usVddcLookupTableOffset), + 8, &vddc_table); + if (!result) + result = get_vddc_lookup_table(hwmgr, + &pp_table_info->vddc_lookup_table, + vddc_table, 8); } - if (powerplay_table->usVddmemLookupTableOffset) { - const ATOM_Vega10_Voltage_Lookup_Table *vdd_mem_table = - (ATOM_Vega10_Voltage_Lookup_Table *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usVddmemLookupTableOffset)); - result = get_vddc_lookup_table(hwmgr, - &pp_table_info->vddmem_lookup_table, vdd_mem_table, 4); + if (!result && powerplay_table->usVddmemLookupTableOffset) { + const ATOM_Vega10_Voltage_Lookup_Table *vdd_mem_table; + + result = get_vega10_voltage_lookup_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usVddmemLookupTableOffset), + 4, &vdd_mem_table); + if (!result) + result = get_vddc_lookup_table(hwmgr, + &pp_table_info->vddmem_lookup_table, + vdd_mem_table, 4); } - if (powerplay_table->usVddciLookupTableOffset) { - const ATOM_Vega10_Voltage_Lookup_Table *vddci_table = - (ATOM_Vega10_Voltage_Lookup_Table *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usVddciLookupTableOffset)); - result = get_vddc_lookup_table(hwmgr, - &pp_table_info->vddci_lookup_table, vddci_table, 4); + if (!result && powerplay_table->usVddciLookupTableOffset) { + const ATOM_Vega10_Voltage_Lookup_Table *vddci_table; + + result = get_vega10_voltage_lookup_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usVddciLookupTableOffset), + 4, &vddci_table); + if (!result) + result = get_vddc_lookup_table(hwmgr, + &pp_table_info->vddci_lookup_table, + vddci_table, 4); } return result; -- cgit v1.2.3 From 53ef33c084c5778cc2dcd1efff25e31b6e231141 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 23 Jun 2026 16:37:07 +0800 Subject: drm/amd/pm: Validate Tonga PPTable subtable bounds v1: Tonga PPTable parsing also relies on VBIOS offsets, revision fields and entry counts for several subtables. Malformed data can cause out-of-bounds reads, while voltage lookup tables can overrun their fixed-size destination arrays. Add common bounds helpers and validate fixed subtables, dynamic entry arrays and revision-specific layouts before consuming voltage lookup, dependency, PCIE, power-tune, hard-limit, thermal, fan, GPIO, PPM and VCE state data. v2: correct to handle get_tonga_ppm_table() return value. Signed-off-by: Yang Wang Reviewed-by: Asad Kamal Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- .../amd/pm/powerplay/hwmgr/process_pptables_v1_0.c | 606 ++++++++++++++++++--- 1 file changed, 521 insertions(+), 85 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c index da77b2c03e24..71017ca154f0 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c @@ -150,6 +150,368 @@ static const void *get_powerplay_table(struct pp_hwmgr *hwmgr) return table_address; } +static bool tonga_pp_table_has_space(struct pp_hwmgr *hwmgr, size_t offset, + size_t size) +{ + size_t table_size = hwmgr->soft_pp_table_size; + + return offset <= table_size && size <= table_size - offset; +} + +static int get_tonga_subtable(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + u16 table_offset, size_t table_size, const void **table) +{ + PP_ASSERT_WITH_CODE((table_offset != 0), + "Invalid PowerPlay Table!", return -1); + PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *table = (const void *)(((unsigned long)powerplay_table) + table_offset); + + return 0; +} + +static int validate_tonga_table_entries(struct pp_hwmgr *hwmgr, + u16 table_offset, size_t entries_offset, + u8 num_entries, size_t entry_size) +{ + size_t table_size; + + PP_ASSERT_WITH_CODE((num_entries != 0), + "Invalid PowerPlay Table!", return -1); + + table_size = entries_offset + num_entries * entry_size; + PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + return 0; +} + +static int get_tonga_voltage_lookup_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + u16 table_offset, uint32_t max_levels, + const ATOM_Tonga_Voltage_Lookup_Table **lookup_table) +{ + const ATOM_Tonga_Voltage_Lookup_Table *table; + size_t table_size; + int ret; + + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + PP_ASSERT_WITH_CODE((table->ucNumEntries != 0 && + table->ucNumEntries <= max_levels), + "Invalid PowerPlay Table!", return -1); + + table_size = offsetof(ATOM_Tonga_Voltage_Lookup_Table, entries) + + table->ucNumEntries * sizeof(ATOM_Tonga_Voltage_Lookup_Record); + PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *lookup_table = table; + + return 0; +} + +static int get_tonga_mclk_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_MCLK_Dependency_Table **mclk_dep_table) +{ + const ATOM_Tonga_MCLK_Dependency_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usMclkDependencyTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_tonga_table_entries(hwmgr, table_offset, + offsetof(ATOM_Tonga_MCLK_Dependency_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Tonga_MCLK_Dependency_Record)); + if (ret) + return ret; + + *mclk_dep_table = table; + + return 0; +} + +static int get_tonga_mm_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_MM_Dependency_Table **mm_dep_table) +{ + const ATOM_Tonga_MM_Dependency_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usMMDependencyTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_tonga_table_entries(hwmgr, table_offset, + offsetof(ATOM_Tonga_MM_Dependency_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Tonga_MM_Dependency_Record)); + if (ret) + return ret; + + *mm_dep_table = table; + + return 0; +} + +static int get_tonga_sclk_dependency_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const PPTable_Generic_SubTable_Header **sclk_dep_table) +{ + const PPTable_Generic_SubTable_Header *header; + u16 table_offset; + size_t entries_offset; + size_t entry_size; + u8 num_entries; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usSclkDependencyTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*header), (const void **)&header); + if (ret) + return ret; + + if (header->ucRevId < 1) { + const ATOM_Tonga_SCLK_Dependency_Table *table = + (const ATOM_Tonga_SCLK_Dependency_Table *)header; + + entries_offset = offsetof(ATOM_Tonga_SCLK_Dependency_Table, entries); + entry_size = sizeof(ATOM_Tonga_SCLK_Dependency_Record); + num_entries = table->ucNumEntries; + } else { + const ATOM_Polaris_SCLK_Dependency_Table *table = + (const ATOM_Polaris_SCLK_Dependency_Table *)header; + + entries_offset = offsetof(ATOM_Polaris_SCLK_Dependency_Table, entries); + entry_size = sizeof(ATOM_Polaris_SCLK_Dependency_Record); + num_entries = table->ucNumEntries; + } + + ret = validate_tonga_table_entries(hwmgr, table_offset, entries_offset, + num_entries, entry_size); + if (ret) + return ret; + + *sclk_dep_table = header; + + return 0; +} + +static int get_tonga_pcie_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const PPTable_Generic_SubTable_Header **pcie_table) +{ + const PPTable_Generic_SubTable_Header *header; + u16 table_offset; + size_t entries_offset; + size_t entry_size; + u8 num_entries; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usPCIETableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*header), (const void **)&header); + if (ret) + return ret; + + if (header->ucRevId < 1) { + const ATOM_Tonga_PCIE_Table *table = + (const ATOM_Tonga_PCIE_Table *)header; + + entries_offset = offsetof(ATOM_Tonga_PCIE_Table, entries); + entry_size = sizeof(ATOM_Tonga_PCIE_Record); + num_entries = table->ucNumEntries; + } else { + const ATOM_Polaris10_PCIE_Table *table = + (const ATOM_Polaris10_PCIE_Table *)header; + + entries_offset = offsetof(ATOM_Polaris10_PCIE_Table, entries); + entry_size = sizeof(ATOM_Polaris10_PCIE_Record); + num_entries = table->ucNumEntries; + } + + ret = validate_tonga_table_entries(hwmgr, table_offset, entries_offset, + num_entries, entry_size); + if (ret) + return ret; + + *pcie_table = header; + + return 0; +} + +static int get_tonga_hard_limit_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_Hard_Limit_Table **hard_limit_table) +{ + const ATOM_Tonga_Hard_Limit_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usHardLimitTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_tonga_table_entries(hwmgr, table_offset, + offsetof(ATOM_Tonga_Hard_Limit_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Tonga_Hard_Limit_Record)); + if (ret) + return ret; + + *hard_limit_table = table; + + return 0; +} + +static int get_tonga_thermal_controller_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_Thermal_Controller **thermal_controller) +{ + u16 table_offset; + + table_offset = le16_to_cpu(powerplay_table->usThermalControllerOffset); + + return get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(**thermal_controller), + (const void **)thermal_controller); +} + +static int get_tonga_fan_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const PPTable_Generic_SubTable_Header **fan_table) +{ + const PPTable_Generic_SubTable_Header *header; + u16 table_offset; + size_t table_size; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usFanTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*header), (const void **)&header); + if (ret) + return ret; + + if (header->ucRevId < 8) + table_size = sizeof(ATOM_Tonga_Fan_Table); + else if (header->ucRevId == 8) + table_size = sizeof(ATOM_Fiji_Fan_Table); + else + table_size = sizeof(ATOM_Polaris_Fan_Table); + + PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *fan_table = header; + + return 0; +} + +static int get_tonga_power_tune_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const PPTable_Generic_SubTable_Header **power_tune_table) +{ + const PPTable_Generic_SubTable_Header *header; + u16 table_offset; + size_t table_size; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usPowerTuneTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*header), (const void **)&header); + if (ret) + return ret; + + if (header->ucRevId < 3) + table_size = sizeof(ATOM_Tonga_PowerTune_Table); + else if (header->ucRevId < 4) + table_size = sizeof(ATOM_Fiji_PowerTune_Table); + else + table_size = sizeof(ATOM_Polaris_PowerTune_Table); + + PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset, + table_size)), + "Invalid PowerPlay Table!", return -1); + + *power_tune_table = header; + + return 0; +} + +static int get_tonga_ppm_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_PPM_Table **ppm_table) +{ + u16 table_offset; + + table_offset = le16_to_cpu(powerplay_table->usPPMTableOffset); + + return get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(**ppm_table), (const void **)ppm_table); +} + +static int get_tonga_gpio_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_GPIO_Table **gpio_table) +{ + u16 table_offset; + + table_offset = le16_to_cpu(powerplay_table->usGPIOTableOffset); + + return get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(**gpio_table), (const void **)gpio_table); +} + +static int get_tonga_vce_state_table(struct pp_hwmgr *hwmgr, + const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, + const ATOM_Tonga_VCE_State_Table **vce_state_table) +{ + const ATOM_Tonga_VCE_State_Table *table; + u16 table_offset; + int ret; + + table_offset = le16_to_cpu(powerplay_table->usVCEStateTableOffset); + ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset, + sizeof(*table), (const void **)&table); + if (ret) + return ret; + + ret = validate_tonga_table_entries(hwmgr, table_offset, + offsetof(ATOM_Tonga_VCE_State_Table, + entries), + table->ucNumEntries, + sizeof(ATOM_Tonga_VCE_State_Record)); + if (ret) + return ret; + + *vce_state_table = table; + + return 0; +} + static int get_vddc_lookup_table( struct pp_hwmgr *hwmgr, phm_ppt_v1_voltage_lookup_table **lookup_table, @@ -208,7 +570,7 @@ static int get_vddc_lookup_table( */ static int get_platform_power_management_table( struct pp_hwmgr *hwmgr, - ATOM_Tonga_PPM_Table *atom_ppm_table) + const ATOM_Tonga_PPM_Table *atom_ppm_table) { struct phm_ppm_table *ptr = kzalloc_obj(*ptr); struct phm_ppt_v1_information *pp_table_information = @@ -256,7 +618,7 @@ static int init_dpm_2_parameters( { int result = 0; struct phm_ppt_v1_information *pp_table_information = (struct phm_ppt_v1_information *)(hwmgr->pptable); - ATOM_Tonga_PPM_Table *atom_ppm_table; + const ATOM_Tonga_PPM_Table *atom_ppm_table; uint32_t disable_ppm = 0; uint32_t disable_power_control = 0; @@ -285,30 +647,39 @@ static int init_dpm_2_parameters( } if (0 != powerplay_table->usVddcLookupTableOffset) { - const ATOM_Tonga_Voltage_Lookup_Table *pVddcCACTable = - (ATOM_Tonga_Voltage_Lookup_Table *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usVddcLookupTableOffset)); - - result = get_vddc_lookup_table(hwmgr, - &pp_table_information->vddc_lookup_table, pVddcCACTable, 16); + const ATOM_Tonga_Voltage_Lookup_Table *pVddcCACTable; + + result = get_tonga_voltage_lookup_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usVddcLookupTableOffset), + 16, &pVddcCACTable); + if (!result) + result = get_vddc_lookup_table(hwmgr, + &pp_table_information->vddc_lookup_table, + pVddcCACTable, 16); } - if (0 != powerplay_table->usVddgfxLookupTableOffset) { - const ATOM_Tonga_Voltage_Lookup_Table *pVddgfxCACTable = - (ATOM_Tonga_Voltage_Lookup_Table *)(((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usVddgfxLookupTableOffset)); + if (!result && 0 != powerplay_table->usVddgfxLookupTableOffset) { + const ATOM_Tonga_Voltage_Lookup_Table *pVddgfxCACTable; - result = get_vddc_lookup_table(hwmgr, - &pp_table_information->vddgfx_lookup_table, pVddgfxCACTable, 16); + result = get_tonga_voltage_lookup_table(hwmgr, powerplay_table, + le16_to_cpu(powerplay_table->usVddgfxLookupTableOffset), + 16, &pVddgfxCACTable); + if (!result) + result = get_vddc_lookup_table(hwmgr, + &pp_table_information->vddgfx_lookup_table, + pVddgfxCACTable, 16); } disable_ppm = 0; if (0 == disable_ppm) { - atom_ppm_table = (ATOM_Tonga_PPM_Table *) - (((unsigned long)powerplay_table) + le16_to_cpu(powerplay_table->usPPMTableOffset)); - if (0 != powerplay_table->usPPMTableOffset) { - if (get_platform_power_management_table(hwmgr, atom_ppm_table) == 0) { + int ret; + + ret = get_tonga_ppm_table(hwmgr, powerplay_table, + &atom_ppm_table); + if (!ret && + get_platform_power_management_table(hwmgr, + atom_ppm_table) == 0) { phm_cap_set(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_EnablePlatformPowerManagement); } @@ -831,28 +1202,13 @@ static int init_clock_voltage_dependency( int result = 0; struct phm_ppt_v1_information *pp_table_information = (struct phm_ppt_v1_information *)(hwmgr->pptable); - - const ATOM_Tonga_MM_Dependency_Table *mm_dependency_table = - (const ATOM_Tonga_MM_Dependency_Table *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usMMDependencyTableOffset)); - const PPTable_Generic_SubTable_Header *pPowerTuneTable = - (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usPowerTuneTableOffset)); - const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table = - (const ATOM_Tonga_MCLK_Dependency_Table *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usMclkDependencyTableOffset)); - const PPTable_Generic_SubTable_Header *sclk_dep_table = - (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usSclkDependencyTableOffset)); - const ATOM_Tonga_Hard_Limit_Table *pHardLimits = - (const ATOM_Tonga_Hard_Limit_Table *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usHardLimitTableOffset)); - const PPTable_Generic_SubTable_Header *pcie_table = - (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usPCIETableOffset)); - const ATOM_Tonga_GPIO_Table *gpio_table = - (const ATOM_Tonga_GPIO_Table *)(((unsigned long) powerplay_table) + - le16_to_cpu(powerplay_table->usGPIOTableOffset)); + const ATOM_Tonga_MM_Dependency_Table *mm_dependency_table; + const PPTable_Generic_SubTable_Header *pPowerTuneTable; + const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table; + const PPTable_Generic_SubTable_Header *sclk_dep_table; + const ATOM_Tonga_Hard_Limit_Table *pHardLimits; + const PPTable_Generic_SubTable_Header *pcie_table; + const ATOM_Tonga_GPIO_Table *gpio_table; pp_table_information->vdd_dep_on_sclk = NULL; pp_table_information->vdd_dep_on_mclk = NULL; @@ -860,29 +1216,58 @@ static int init_clock_voltage_dependency( pp_table_information->pcie_table = NULL; pp_table_information->gpio_table = NULL; - if (powerplay_table->usMMDependencyTableOffset != 0) - result = get_mm_clock_voltage_table(hwmgr, - &pp_table_information->mm_dep_table, mm_dependency_table); + if (powerplay_table->usMMDependencyTableOffset != 0) { + result = get_tonga_mm_dependency_table(hwmgr, powerplay_table, + &mm_dependency_table); + if (!result) + result = get_mm_clock_voltage_table(hwmgr, + &pp_table_information->mm_dep_table, + mm_dependency_table); + } - if (result == 0 && powerplay_table->usPowerTuneTableOffset != 0) - result = get_cac_tdp_table(hwmgr, - &pp_table_information->cac_dtp_table, pPowerTuneTable); + if (result == 0 && powerplay_table->usPowerTuneTableOffset != 0) { + result = get_tonga_power_tune_table(hwmgr, powerplay_table, + &pPowerTuneTable); + if (!result) + result = get_cac_tdp_table(hwmgr, + &pp_table_information->cac_dtp_table, + pPowerTuneTable); + } - if (result == 0 && powerplay_table->usSclkDependencyTableOffset != 0) - result = get_sclk_voltage_dependency_table(hwmgr, - &pp_table_information->vdd_dep_on_sclk, sclk_dep_table); + if (result == 0 && powerplay_table->usSclkDependencyTableOffset != 0) { + result = get_tonga_sclk_dependency_table(hwmgr, powerplay_table, + &sclk_dep_table); + if (!result) + result = get_sclk_voltage_dependency_table(hwmgr, + &pp_table_information->vdd_dep_on_sclk, + sclk_dep_table); + } - if (result == 0 && powerplay_table->usMclkDependencyTableOffset != 0) - result = get_mclk_voltage_dependency_table(hwmgr, - &pp_table_information->vdd_dep_on_mclk, mclk_dep_table); + if (result == 0 && powerplay_table->usMclkDependencyTableOffset != 0) { + result = get_tonga_mclk_dependency_table(hwmgr, powerplay_table, + &mclk_dep_table); + if (!result) + result = get_mclk_voltage_dependency_table(hwmgr, + &pp_table_information->vdd_dep_on_mclk, + mclk_dep_table); + } - if (result == 0 && powerplay_table->usPCIETableOffset != 0) - result = get_pcie_table(hwmgr, - &pp_table_information->pcie_table, pcie_table); + if (result == 0 && powerplay_table->usPCIETableOffset != 0) { + result = get_tonga_pcie_table(hwmgr, powerplay_table, + &pcie_table); + if (!result) + result = get_pcie_table(hwmgr, + &pp_table_information->pcie_table, pcie_table); + } - if (result == 0 && powerplay_table->usHardLimitTableOffset != 0) - result = get_hard_limits(hwmgr, - &pp_table_information->max_clock_voltage_on_dc, pHardLimits); + if (result == 0 && powerplay_table->usHardLimitTableOffset != 0) { + result = get_tonga_hard_limit_table(hwmgr, powerplay_table, + &pHardLimits); + if (!result) + result = get_hard_limits(hwmgr, + &pp_table_information->max_clock_voltage_on_dc, + pHardLimits); + } hwmgr->dyn_state.max_clock_voltage_on_dc.sclk = pp_table_information->max_clock_voltage_on_dc.sclk; @@ -903,9 +1288,13 @@ static int init_clock_voltage_dependency( result = get_valid_clk(hwmgr, &pp_table_information->valid_sclk_values, pp_table_information->vdd_dep_on_sclk); - if (!result && gpio_table) - result = get_gpio_table(hwmgr, &pp_table_information->gpio_table, - gpio_table); + if (!result && powerplay_table->usGPIOTableOffset) { + result = get_tonga_gpio_table(hwmgr, powerplay_table, + &gpio_table); + if (!result) + result = get_gpio_table(hwmgr, + &pp_table_information->gpio_table, gpio_table); + } return result; } @@ -950,14 +1339,17 @@ static int init_thermal_controller( ) { const PPTable_Generic_SubTable_Header *fan_table; - ATOM_Tonga_Thermal_Controller *thermal_controller; + const ATOM_Tonga_Thermal_Controller *thermal_controller; + int ret; - thermal_controller = (ATOM_Tonga_Thermal_Controller *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usThermalControllerOffset)); PP_ASSERT_WITH_CODE((0 != powerplay_table->usThermalControllerOffset), "Thermal controller table not set!", return -1); + ret = get_tonga_thermal_controller_table(hwmgr, powerplay_table, + &thermal_controller); + if (ret) + return ret; + hwmgr->thermal_controller.ucType = thermal_controller->ucType; hwmgr->thermal_controller.ucI2cLine = thermal_controller->ucI2cLine; hwmgr->thermal_controller.ucI2cAddress = thermal_controller->ucI2cAddress; @@ -985,12 +1377,13 @@ static int init_thermal_controller( return 0; } - fan_table = (const PPTable_Generic_SubTable_Header *) - (((unsigned long)powerplay_table) + - le16_to_cpu(powerplay_table->usFanTableOffset)); - PP_ASSERT_WITH_CODE((0 != powerplay_table->usFanTableOffset), "Fan table not set!", return -1); + + ret = get_tonga_fan_table(hwmgr, powerplay_table, &fan_table); + if (ret) + return ret; + PP_ASSERT_WITH_CODE((0 < fan_table->ucRevId), "Unsupported fan table format!", return -1); @@ -1352,13 +1745,15 @@ static int ppt_get_num_of_vce_state_table_entries_v1_0(struct pp_hwmgr *hwmgr) { const ATOM_Tonga_POWERPLAYTABLE *pp_table = get_powerplay_table(hwmgr); const ATOM_Tonga_VCE_State_Table *vce_state_table; + int ret; if (pp_table == NULL) return 0; - vce_state_table = (void *)pp_table + - le16_to_cpu(pp_table->usVCEStateTableOffset); + ret = get_tonga_vce_state_table(hwmgr, pp_table, &vce_state_table); + if (ret) + return 0; return vce_state_table->ucNumEntries; } @@ -1367,18 +1762,39 @@ static int ppt_get_vce_state_table_entry_v1_0(struct pp_hwmgr *hwmgr, uint32_t i struct amd_vce_state *vce_state, void **clock_info, uint32_t *flag) { const ATOM_Tonga_VCE_State_Record *vce_state_record; - ATOM_Tonga_SCLK_Dependency_Record *sclk_dep_record; + ATOM_Tonga_SCLK_Dependency_Record *sclk_dep_record = NULL; + ATOM_Polaris_SCLK_Dependency_Record *polaris_sclk_dep_record = NULL; ATOM_Tonga_MCLK_Dependency_Record *mclk_dep_record; ATOM_Tonga_MM_Dependency_Record *mm_dep_record; const ATOM_Tonga_POWERPLAYTABLE *pptable = get_powerplay_table(hwmgr); - const ATOM_Tonga_VCE_State_Table *vce_state_table = (ATOM_Tonga_VCE_State_Table *)(((unsigned long)pptable) - + le16_to_cpu(pptable->usVCEStateTableOffset)); - const ATOM_Tonga_SCLK_Dependency_Table *sclk_dep_table = (ATOM_Tonga_SCLK_Dependency_Table *)(((unsigned long)pptable) - + le16_to_cpu(pptable->usSclkDependencyTableOffset)); - const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table = (ATOM_Tonga_MCLK_Dependency_Table *)(((unsigned long)pptable) - + le16_to_cpu(pptable->usMclkDependencyTableOffset)); - const ATOM_Tonga_MM_Dependency_Table *mm_dep_table = (ATOM_Tonga_MM_Dependency_Table *)(((unsigned long)pptable) - + le16_to_cpu(pptable->usMMDependencyTableOffset)); + const ATOM_Tonga_VCE_State_Table *vce_state_table; + const PPTable_Generic_SubTable_Header *sclk_dep_table_header; + const ATOM_Tonga_SCLK_Dependency_Table *sclk_dep_table; + const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table; + const ATOM_Tonga_MM_Dependency_Table *mm_dep_table; + int ret; + + if (!pptable) + return -EINVAL; + + ret = get_tonga_vce_state_table(hwmgr, pptable, &vce_state_table); + if (ret) + return ret; + + ret = get_tonga_sclk_dependency_table(hwmgr, pptable, + &sclk_dep_table_header); + if (ret) + return ret; + sclk_dep_table = (const ATOM_Tonga_SCLK_Dependency_Table *) + sclk_dep_table_header; + + ret = get_tonga_mclk_dependency_table(hwmgr, pptable, &mclk_dep_table); + if (ret) + return ret; + + ret = get_tonga_mm_dependency_table(hwmgr, pptable, &mm_dep_table); + if (ret) + return ret; PP_ASSERT_WITH_CODE((i < vce_state_table->ucNumEntries), "Requested state entry ID is out of range!", @@ -1387,10 +1803,27 @@ static int ppt_get_vce_state_table_entry_v1_0(struct pp_hwmgr *hwmgr, uint32_t i vce_state_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_VCE_State_Record, entries, vce_state_table, i); - sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_SCLK_Dependency_Record, - entries, sclk_dep_table, - vce_state_record->ucSCLKIndex); + PP_ASSERT_WITH_CODE((vce_state_record->ucSCLKIndex < + sclk_dep_table->ucNumEntries), + "Invalid PowerPlay Table!", return -EINVAL); + PP_ASSERT_WITH_CODE((vce_state_record->ucVCEClockIndex < + mm_dep_table->ucNumEntries), + "Invalid PowerPlay Table!", return -EINVAL); + PP_ASSERT_WITH_CODE((mclk_dep_table->ucNumEntries != 0), + "Invalid PowerPlay Table!", return -EINVAL); + + if (sclk_dep_table_header->ucRevId < 1) + sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( + ATOM_Tonga_SCLK_Dependency_Record, + entries, sclk_dep_table, + vce_state_record->ucSCLKIndex); + else + polaris_sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( + ATOM_Polaris_SCLK_Dependency_Record, + entries, + (ATOM_Polaris_SCLK_Dependency_Table *) + sclk_dep_table_header, + vce_state_record->ucSCLKIndex); mm_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_MM_Dependency_Record, entries, mm_dep_table, @@ -1399,7 +1832,10 @@ static int ppt_get_vce_state_table_entry_v1_0(struct pp_hwmgr *hwmgr, uint32_t i vce_state->evclk = le32_to_cpu(mm_dep_record->ulEClk); vce_state->ecclk = le32_to_cpu(mm_dep_record->ulEClk); - vce_state->sclk = le32_to_cpu(sclk_dep_record->ulSclk); + if (sclk_dep_record) + vce_state->sclk = le32_to_cpu(sclk_dep_record->ulSclk); + else + vce_state->sclk = le32_to_cpu(polaris_sclk_dep_record->ulSclk); if (vce_state_record->ucMCLKIndex >= mclk_dep_table->ucNumEntries) mclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( -- cgit v1.2.3 From 3a8a05477cda6c8293e2b629495b42981dcaba32 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 23 Jun 2026 00:00:00 +0000 Subject: drm/amdgpu/pm/powerplay: bounds-check voltage index in SMU7 lookup vddInd and vddcInd fields from VBIOS-parsed tables are used to index into voltage lookup tables without a bounds check. Return -EINVAL when any index is out of range. Fixes: c82baa281843 ("drm/amd/powerplay: add Tonga dpm support (v3)") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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 39d745f3fb5b..1f1bb274685a 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -2216,12 +2216,24 @@ static int smu7_patch_voltage_dependency_tables_with_lookup_table( if (data->vdd_gfx_control == SMU7_VOLTAGE_CONTROL_BY_SVID2) { for (entry_id = 0; entry_id < sclk_table->count; ++entry_id) { voltage_id = sclk_table->entries[entry_id].vddInd; + if (voltage_id >= table_info->vddgfx_lookup_table->count) { + pr_err("amdgpu: sclk[%u] vddgfx index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddgfx_lookup_table->count); + return -EINVAL; + } sclk_table->entries[entry_id].vddgfx = table_info->vddgfx_lookup_table->entries[voltage_id].us_vdd; } } else { for (entry_id = 0; entry_id < sclk_table->count; ++entry_id) { voltage_id = sclk_table->entries[entry_id].vddInd; + if (voltage_id >= table_info->vddc_lookup_table->count) { + pr_err("amdgpu: sclk[%u] vddc index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddc_lookup_table->count); + return -EINVAL; + } sclk_table->entries[entry_id].vddc = table_info->vddc_lookup_table->entries[voltage_id].us_vdd; } @@ -2229,12 +2241,24 @@ static int smu7_patch_voltage_dependency_tables_with_lookup_table( for (entry_id = 0; entry_id < mclk_table->count; ++entry_id) { voltage_id = mclk_table->entries[entry_id].vddInd; + if (voltage_id >= table_info->vddc_lookup_table->count) { + pr_err("amdgpu: mclk[%u] vddc index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddc_lookup_table->count); + return -EINVAL; + } mclk_table->entries[entry_id].vddc = table_info->vddc_lookup_table->entries[voltage_id].us_vdd; } for (entry_id = 0; entry_id < mm_table->count; ++entry_id) { voltage_id = mm_table->entries[entry_id].vddcInd; + if (voltage_id >= table_info->vddc_lookup_table->count) { + pr_err("amdgpu: mm[%u] vddc index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddc_lookup_table->count); + return -EINVAL; + } mm_table->entries[entry_id].vddc = table_info->vddc_lookup_table->entries[voltage_id].us_vdd; } -- cgit v1.2.3 From 6fa33f594e46e775a94097f71b486d7b006b6917 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Tue, 23 Jun 2026 00:00:00 +0000 Subject: drm/amdgpu/pm/powerplay: bounds-check voltage index in Vega10 lookup vddInd, vddciInd and mvddInd from VBIOS-parsed tables index into vddc, vddci and vddmem lookup tables without bounds checks across nine sites. Return -EINVAL when any index is out of range. Fixes: f83a9991648b ("drm/amd/powerplay: add Vega10 powerplay support (v5)") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 35 +++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) 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 629815f0c5d4..0e237feb1629 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -685,10 +685,18 @@ static int vega10_patch_voltage_dependency_tables_with_lookup_table( case 3: vdt = table_info->vdd_dep_on_pixclk; break; case 4: vdt = table_info->vdd_dep_on_dispclk; break; case 5: vdt = table_info->vdd_dep_on_phyclk; break; + default: + continue; } for (entry_id = 0; entry_id < vdt->count; entry_id++) { voltage_id = vdt->entries[entry_id].vddInd; + if (voltage_id >= table_info->vddc_lookup_table->count) { + pr_err("amdgpu: clk_dep[%u][%u] vddc index %u out of bounds (%u)\n", + i, entry_id, voltage_id, + table_info->vddc_lookup_table->count); + return -EINVAL; + } vdt->entries[entry_id].vddc = table_info->vddc_lookup_table->entries[voltage_id].us_vdd; } @@ -696,23 +704,48 @@ static int vega10_patch_voltage_dependency_tables_with_lookup_table( for (entry_id = 0; entry_id < mm_table->count; ++entry_id) { voltage_id = mm_table->entries[entry_id].vddcInd; + if (voltage_id >= table_info->vddc_lookup_table->count) { + pr_err("amdgpu: mm[%u] vddc index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddc_lookup_table->count); + return -EINVAL; + } mm_table->entries[entry_id].vddc = table_info->vddc_lookup_table->entries[voltage_id].us_vdd; } for (entry_id = 0; entry_id < mclk_table->count; ++entry_id) { voltage_id = mclk_table->entries[entry_id].vddInd; + if (voltage_id >= table_info->vddc_lookup_table->count) { + pr_err("amdgpu: mclk[%u] vddc index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddc_lookup_table->count); + return -EINVAL; + } mclk_table->entries[entry_id].vddc = table_info->vddc_lookup_table->entries[voltage_id].us_vdd; + voltage_id = mclk_table->entries[entry_id].vddciInd; + if (voltage_id >= table_info->vddci_lookup_table->count) { + pr_err("amdgpu: mclk[%u] vddci index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddci_lookup_table->count); + return -EINVAL; + } mclk_table->entries[entry_id].vddci = table_info->vddci_lookup_table->entries[voltage_id].us_vdd; + voltage_id = mclk_table->entries[entry_id].mvddInd; + if (voltage_id >= table_info->vddmem_lookup_table->count) { + pr_err("amdgpu: mclk[%u] vddmem index %u out of bounds (%u)\n", + entry_id, voltage_id, + table_info->vddmem_lookup_table->count); + return -EINVAL; + } mclk_table->entries[entry_id].mvdd = table_info->vddmem_lookup_table->entries[voltage_id].us_vdd; } - return 0; } -- cgit v1.2.3 From 4a33d82e224c82e8f493b94b017b1466556db39e Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Tue, 23 Jun 2026 10:31:10 +0800 Subject: drm/amdgpu: protect XCP scheduler selection amdgpu_xcp_select_scheds() reads the per-XCP scheduler list. Partition switching rebuilds the same table under xcp_lock. Take xcp_lock around XCP scheduler selection and release. This prevents readers from observing partially rebuilt state. Also revalidate the selected XCP id before indexing the table. An open file can outlive a switch to another partition mode. Signed-off-by: Xiang Liu Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c index 7c3e707ff84e..35faea0ff17f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c @@ -469,15 +469,18 @@ void amdgpu_xcp_release_sched(struct amdgpu_device *adev, { struct drm_gpu_scheduler *sched = container_of(entity->entity.rq, typeof(*sched), rq); + struct amdgpu_xcp_mgr *xcp_mgr = adev->xcp_mgr; - if (!adev->xcp_mgr) + if (!xcp_mgr) return; if (drm_sched_wqueue_ready(sched)) { struct amdgpu_ring *ring = to_amdgpu_ring(sched); - if (ring->xcp_id < MAX_XCP) - atomic_dec(&adev->xcp_mgr->xcp[ring->xcp_id].ref_cnt); + mutex_lock(&xcp_mgr->xcp_lock); + if (ring->xcp_id < xcp_mgr->num_xcps && xcp_mgr->xcp[ring->xcp_id].valid) + atomic_dec(&xcp_mgr->xcp[ring->xcp_id].ref_cnt); + mutex_unlock(&xcp_mgr->xcp_lock); } } @@ -490,7 +493,9 @@ int amdgpu_xcp_select_scheds(struct amdgpu_device *adev, u32 sel_xcp_id; int i; struct amdgpu_xcp_mgr *xcp_mgr = adev->xcp_mgr; + int r = 0; + mutex_lock(&xcp_mgr->xcp_lock); if (fpriv->xcp_id == AMDGPU_XCP_NO_PARTITION) { u32 least_ref_cnt = ~0; @@ -507,19 +512,27 @@ int amdgpu_xcp_select_scheds(struct amdgpu_device *adev, } sel_xcp_id = fpriv->xcp_id; + if (sel_xcp_id >= xcp_mgr->num_xcps || !xcp_mgr->xcp[sel_xcp_id].valid) { + dev_err(adev->dev, "Selected partition #%d is not valid.", sel_xcp_id); + r = -ENODEV; + goto out; + } + if (xcp_mgr->xcp[sel_xcp_id].gpu_sched[hw_ip][hw_prio].num_scheds) { *num_scheds = - xcp_mgr->xcp[fpriv->xcp_id].gpu_sched[hw_ip][hw_prio].num_scheds; + xcp_mgr->xcp[sel_xcp_id].gpu_sched[hw_ip][hw_prio].num_scheds; *scheds = - xcp_mgr->xcp[fpriv->xcp_id].gpu_sched[hw_ip][hw_prio].sched; - atomic_inc(&adev->xcp_mgr->xcp[sel_xcp_id].ref_cnt); + xcp_mgr->xcp[sel_xcp_id].gpu_sched[hw_ip][hw_prio].sched; + atomic_inc(&xcp_mgr->xcp[sel_xcp_id].ref_cnt); dev_dbg(adev->dev, "Selected partition #%d", sel_xcp_id); } else { dev_err(adev->dev, "Failed to schedule partition #%d.", sel_xcp_id); - return -ENOENT; + r = -ENOENT; } - return 0; +out: + mutex_unlock(&xcp_mgr->xcp_lock); + return r; } static void amdgpu_set_xcp_id(struct amdgpu_device *adev, -- cgit v1.2.3 From 6244eae22966350db52faf9c1369d3b2ffc5de4e Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Wed, 24 Jun 2026 15:52:35 +0800 Subject: drm/amdgpu: reject mapping a reserved doorbell to a new queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When creating an user-queue, the user space provides a doorbell BO handle and an offset within the bo to obtain a doorbell. However current implementation using xa_store_irq() to store a doorbell, which allows a later queue created with the same BO and offset parameters to overwrite an existing queue and doorbell mapping. This can cause problems like misrouting fence IRQ processing to a wrong queue, and mislead the cleanup process of one queue erasing the mapping of another queue. This commit fixes this issue by replacing xa_store_irq with xa_insert_irq, which rejects mapping a reserved doorbell to a newly created queue Signed-off-by: Zhu Lingshan Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index fb4cc6bfb5ac..82c8809d1d9c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -702,8 +702,8 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) /* Update VM owner at userq submit-time for page-fault attribution. */ amdgpu_vm_set_task_info(&fpriv->vm); - r = xa_err(xa_store_irq(&adev->userq_doorbell_xa, index, queue, - GFP_KERNEL)); + r = xa_insert_irq(&adev->userq_doorbell_xa, index, queue, + GFP_KERNEL); if (r) goto clean_mqd; -- cgit v1.2.3 From 294403fde5ba8e972d1bab88ea56be0fa2ff1f3e Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Mon, 22 Jun 2026 15:21:59 +0530 Subject: drm/amdgpu: bounds check VBIOS name extraction Bound atom_get_vbios_name() by the BIOS size to avoid out-of-bounds reads. Signed-off-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/atom.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/atom.c b/drivers/gpu/drm/amd/amdgpu/atom.c index 23f5cd52f9fc..e0e585f280e2 100644 --- a/drivers/gpu/drm/amd/amdgpu/atom.c +++ b/drivers/gpu/drm/amd/amdgpu/atom.c @@ -1358,6 +1358,7 @@ static void atom_index_iio(struct atom_context *ctx, int base) static void atom_get_vbios_name(struct atom_context *ctx) { unsigned char *p_rom; + unsigned char *p_end; unsigned char str_num; unsigned short off_to_vbios_str; unsigned char *c_ptr; @@ -1368,39 +1369,48 @@ static void atom_get_vbios_name(struct atom_context *ctx) char *back; p_rom = ctx->bios; + p_end = p_rom + ctx->bios_size; + + if (p_rom + OFFSET_TO_GET_ATOMBIOS_STRING_START + 1 >= p_end) + goto no_name; str_num = *(p_rom + OFFSET_TO_GET_ATOMBIOS_NUMBER_OF_STRINGS); - if (str_num != 0) { - off_to_vbios_str = - *(unsigned short *)(p_rom + OFFSET_TO_GET_ATOMBIOS_STRING_START); + if (!str_num) + goto no_name; - c_ptr = (unsigned char *)(p_rom + off_to_vbios_str); - } else { - /* do not know where to find name */ - memcpy(ctx->name, na, 7); - ctx->name[7] = 0; - return; - } + off_to_vbios_str = + *(unsigned short *)(p_rom + OFFSET_TO_GET_ATOMBIOS_STRING_START); + + c_ptr = (unsigned char *)(p_rom + off_to_vbios_str); + if (c_ptr >= p_end) + goto no_name; /* * skip the atombios strings, usually 4 * 1st is P/N, 2nd is ASIC, 3rd is PCI type, 4th is Memory type */ for (i = 0; i < str_num; i++) { - while (*c_ptr != 0) + while (c_ptr < p_end && *c_ptr != 0) c_ptr++; c_ptr++; } /* skip the following 2 chars: 0x0D 0x0A */ c_ptr += 2; + if (c_ptr >= p_end) + goto no_name; - name_size = strnlen(c_ptr, STRLEN_LONG - 1); + name_size = strnlen(c_ptr, min(STRLEN_LONG - 1, (int)(p_end - c_ptr))); memcpy(ctx->name, c_ptr, name_size); back = ctx->name + name_size; while ((*--back) == ' ') ; *(back + 1) = '\0'; + return; + +no_name: + /* do not know where to find name */ + strscpy(ctx->name, na, sizeof(ctx->name)); } static void atom_get_vbios_date(struct atom_context *ctx) -- cgit v1.2.3 From 991e0516a8072f2292681c6ae98a924ab0e32575 Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Thu, 25 Jun 2026 16:23:47 +0800 Subject: drm/amd/display: use kvzalloc to allocate struct dc struct dc has grown large over time (most of it the two inlined dc_scratch_space copies) and now sits close to the page allocator's 4 MiB contiguous allocation limit. Its actual size is not fixed by the source alone, it also depends on the compiler and the .config, so it can easily cross 4 MiB, e.g. with a newer GCC or a config change. dc_create() allocates it with kzalloc(). Once struct dc exceeds 4 MiB the request is rounded up to order 11 (8 MiB), which is above MAX_PAGE_ORDER, so the page allocator warns and returns NULL. dc_create() then fails, DM init fails and amdgpu probe aborts with -EINVAL: WARNING: mm/page_alloc.c:5197 at __alloc_frozen_pages_noprof+0x2f9/0x380 dc_create+0x38/0x660 [amdgpu] amdgpu_dm_init+0x2d9/0x510 [amdgpu] dm_hw_init+0x1b/0x90 [amdgpu] amdgpu_device_init.cold+0x150d/0x1e13 [amdgpu] amdgpu_driver_load_kms+0x19/0x80 [amdgpu] amdgpu_pci_probe+0x1e2/0x4c0 [amdgpu] dc_create() then returns NULL and DM init fails, which aborts the whole GPU init and makes amdgpu probe fail with -EINVAL ("hw_init of IP block failed -22"), leaving the display unusable. The subsequent amdgpu_irq_put() warnings during teardown are just fallout of unwinding a half-initialized device. struct dc is a software-only bookkeeping structure that is never handed to hardware DMA and is only ever kept as an opaque pointer, so it does not require physically contiguous memory. Allocate it with kvzalloc() (and free it with kvfree()) so that the allocator can fall back to vmalloc() when a contiguous allocation of that size is not available, which also avoids the MAX_PAGE_ORDER warning entirely. v2: - Rebase to amd-staging-drm-next. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5406 Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Honglei Huang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 0ecb025e76fa..3aa95410006a 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -1509,7 +1509,7 @@ static void disable_vbios_mode_if_required( struct dc *dc_create(const struct dc_init_data *init_params) { - struct dc *dc = kzalloc_obj(*dc); + struct dc *dc = kvzalloc_obj(*dc); unsigned int full_pipe_count; if (!dc) @@ -1557,7 +1557,7 @@ struct dc *dc_create(const struct dc_init_data *init_params) destruct_dc: dc_destruct(dc); - kfree(dc); + kvfree(dc); return NULL; } @@ -1606,7 +1606,7 @@ void dc_deinit_callbacks(struct dc *dc) void dc_destroy(struct dc **dc) { dc_destruct(*dc); - kfree(*dc); + kvfree(*dc); *dc = NULL; } -- cgit v1.2.3 From f8f759426b9e21a91266e0fc3ecf17677992bcad Mon Sep 17 00:00:00 2001 From: James Zhu Date: Thu, 25 Sep 2025 16:13:58 -0400 Subject: drm/amdkfd: Add domain parameter to kernel BO mapping function This change allows amdgpu_amdkfd_gpuvm_map_bo_to_kernel() to pin buffers in either GTT or VRAM based on caller specification, providing flexibility for different memory placement requirements across various kernel buffers. The domain parameter accepts AMDGPU_GEM_DOMAIN_GTT, AMDGPU_GEM_DOMAIN_VRAM, or their combination (GTT|VRAM) to let amdgpu_bo_pin() choose the optimal placement via amdgpu_bo_get_preferred_domain(). This flexible validation allows callers to specify their preference while delegating final placement decisions to the driver when appropriate. CPU visibility is automatically enforced by amdgpu_bo_pin() regardless of the domain parameter (see amdgpu_bo_pin() line 975-976 which sets AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED for kernel mappings). -v3: update amdgpu_amdkfd_gpuvm_map_bo_to_kernel description Signed-off-by: James Zhu Reviewed-by: Vladimir Indic Reviewed-by: Philip Yang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 6 +++--- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 20 ++++++++++++++------ drivers/gpu/drm/amd/amdkfd/kfd_events.c | 5 +++-- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 11 ++++++----- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index 5b49fa50a47d..338412a750ed 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -336,9 +336,9 @@ int amdgpu_amdkfd_gpuvm_unmap_memory_from_gpu( int amdgpu_amdkfd_gpuvm_dmaunmap_mem(struct kgd_mem *mem, void *drm_priv); int amdgpu_amdkfd_gpuvm_sync_memory( struct amdgpu_device *adev, struct kgd_mem *mem, bool intr); -int amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel(struct kgd_mem *mem, - void **kptr, uint64_t *size); -void amdgpu_amdkfd_gpuvm_unmap_gtt_bo_from_kernel(struct kgd_mem *mem); +int amdgpu_amdkfd_gpuvm_map_bo_to_kernel(struct kgd_mem *mem, void **kptr, + u64 *size, u32 domain); +void amdgpu_amdkfd_gpuvm_unmap_bo_from_kernel(struct kgd_mem *mem); int amdgpu_amdkfd_map_gtt_bo_to_gart(struct amdgpu_bo *bo, struct amdgpu_bo **bo_gart); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 35fe2c974699..20831dbebc31 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -2271,11 +2271,14 @@ err_reserve_bo_failed: return ret; } -/** amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel() - Map a GTT BO for kernel CPU access +/** amdgpu_amdkfd_gpuvm_map_bo_to_kernel() - Map GTT or VRAM BO for kernel CPU access * * @mem: Buffer object to be mapped for CPU access * @kptr[out]: pointer in kernel CPU address space * @size[out]: size of the buffer + * @domain[IN]: domain for pinning (AMDGPU_GEM_DOMAIN_GTT, AMDGPU_GEM_DOMAIN_VRAM, + * or their combination to let the driver choose). CPU visibility is + * automatically enforced by amdgpu_bo_pin() * * Pins the BO and maps it for kernel CPU access. The eviction fence is removed * from the BO, since pinned BOs cannot be evicted. The bo must remain on the @@ -2284,8 +2287,8 @@ err_reserve_bo_failed: * * Return: 0 on success, error code on failure */ -int amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel(struct kgd_mem *mem, - void **kptr, uint64_t *size) +int amdgpu_amdkfd_gpuvm_map_bo_to_kernel(struct kgd_mem *mem, void **kptr, + u64 *size, u32 domain) { int ret; struct amdgpu_bo *bo = mem->bo; @@ -2295,6 +2298,11 @@ int amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel(struct kgd_mem *mem, return -EINVAL; } + if (!(domain & (AMDGPU_GEM_DOMAIN_GTT | AMDGPU_GEM_DOMAIN_VRAM))) { + pr_debug("Invalid domain 0x%x for kernel mapping\n", domain); + return -EINVAL; + } + mutex_lock(&mem->process_info->lock); ret = amdgpu_bo_reserve(bo, true); @@ -2303,7 +2311,7 @@ int amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel(struct kgd_mem *mem, goto bo_reserve_failed; } - ret = amdgpu_bo_pin(bo, AMDGPU_GEM_DOMAIN_GTT); + ret = amdgpu_bo_pin(bo, domain); if (ret) { pr_err("Failed to pin bo. ret %d\n", ret); goto pin_failed; @@ -2336,7 +2344,7 @@ bo_reserve_failed: return ret; } -/** amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel() - Unmap a GTT BO for kernel CPU access +/** amdgpu_amdkfd_gpuvm_unmap_bo_from_kernel() - Unmap GTT or VRAM BO for kernel CPU access * * @mem: Buffer object to be unmapped for CPU access * @@ -2344,7 +2352,7 @@ bo_reserve_failed: * eviction fence, so this function should only be used for cleanup before the * BO is destroyed. */ -void amdgpu_amdkfd_gpuvm_unmap_gtt_bo_from_kernel(struct kgd_mem *mem) +void amdgpu_amdkfd_gpuvm_unmap_bo_from_kernel(struct kgd_mem *mem) { struct amdgpu_bo *bo = mem->bo; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index 43a04365a8c4..dae01e2bb464 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -314,7 +314,8 @@ int kfd_kmap_event_page(struct kfd_process *p, uint64_t event_page_offset) return -EINVAL; } - err = amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel(mem, &kern_addr, &size); + err = amdgpu_amdkfd_gpuvm_map_bo_to_kernel(mem, &kern_addr, &size, + AMDGPU_GEM_DOMAIN_GTT); if (err) { pr_err("Failed to map event page to kernel\n"); return err; @@ -323,7 +324,7 @@ int kfd_kmap_event_page(struct kfd_process *p, uint64_t event_page_offset) err = kfd_event_page_set(p, kern_addr, size, event_page_offset); if (err) { pr_err("Failed to set event page\n"); - amdgpu_amdkfd_gpuvm_unmap_gtt_bo_from_kernel(mem); + amdgpu_amdkfd_gpuvm_unmap_bo_from_kernel(mem); return err; } return err; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index c52a93c66256..cc88d70dc7f0 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -736,7 +736,7 @@ static void kfd_process_free_gpuvm(struct kgd_mem *mem, struct kfd_node *dev = pdd->dev; if (kptr && *kptr) { - amdgpu_amdkfd_gpuvm_unmap_gtt_bo_from_kernel(mem); + amdgpu_amdkfd_gpuvm_unmap_bo_from_kernel(mem); *kptr = NULL; } @@ -776,10 +776,11 @@ static int kfd_process_alloc_gpuvm(struct kfd_process_device *pdd, } if (kptr) { - err = amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel( - (struct kgd_mem *)*mem, kptr, NULL); + err = amdgpu_amdkfd_gpuvm_map_bo_to_kernel((struct kgd_mem *)*mem, + kptr, NULL, + AMDGPU_GEM_DOMAIN_GTT); if (err) { - pr_debug("Map GTT BO to kernel failed\n"); + pr_debug("Map BO to kernel failed err %d\n", err); goto sync_memory_failed; } } @@ -1134,7 +1135,7 @@ static void kfd_process_kunmap_signal_bo(struct kfd_process *p) if (!mem) goto out; - amdgpu_amdkfd_gpuvm_unmap_gtt_bo_from_kernel(mem); + amdgpu_amdkfd_gpuvm_unmap_bo_from_kernel(mem); out: mutex_unlock(&p->mutex); -- cgit v1.2.3 From fe5966d4fdcbed91e6b3478ea6c89d9915d6ed4a Mon Sep 17 00:00:00 2001 From: James Zhu Date: Wed, 3 Sep 2025 17:21:00 -0400 Subject: drm/amdkfd: move TBA/TMA from system to device memory for GFX9.4.2 and above. -v2: keep APU with GTT allocation -v3: use dev->adev->apu_prefer_gtt instead Signed-off-by: James Zhu Reviewed-by: Vladimir Indic Reviewed-by: Harish Kasiviswanathan Reviewed-by: Philip Yang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index cc88d70dc7f0..767c2cc8e29e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -776,9 +776,14 @@ static int kfd_process_alloc_gpuvm(struct kfd_process_device *pdd, } if (kptr) { + u32 domain; + + if (flags & KFD_IOC_ALLOC_MEM_FLAGS_VRAM) + domain = AMDGPU_GEM_DOMAIN_VRAM; + else + domain = AMDGPU_GEM_DOMAIN_GTT; err = amdgpu_amdkfd_gpuvm_map_bo_to_kernel((struct kgd_mem *)*mem, - kptr, NULL, - AMDGPU_GEM_DOMAIN_GTT); + kptr, NULL, domain); if (err) { pr_debug("Map BO to kernel failed err %d\n", err); goto sync_memory_failed; @@ -1484,8 +1489,7 @@ static int kfd_process_device_init_cwsr_dgpu(struct kfd_process_device *pdd) { struct kfd_node *dev = pdd->dev; struct qcm_process_device *qpd = &pdd->qpd; - uint32_t flags = KFD_IOC_ALLOC_MEM_FLAGS_GTT - | KFD_IOC_ALLOC_MEM_FLAGS_NO_SUBSTITUTE + u32 flags = KFD_IOC_ALLOC_MEM_FLAGS_NO_SUBSTITUTE | KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE; struct kgd_mem *mem; void *kaddr; @@ -1494,7 +1498,12 @@ static int kfd_process_device_init_cwsr_dgpu(struct kfd_process_device *pdd) if (!dev->kfd->cwsr_enabled || qpd->cwsr_kaddr || !qpd->cwsr_base) return 0; - /* cwsr_base is only set for dGPU */ + if (KFD_GC_VERSION(dev) >= IP_VERSION(9, 4, 2) && !dev->adev->apu_prefer_gtt) + flags |= KFD_IOC_ALLOC_MEM_FLAGS_VRAM; + else + flags |= KFD_IOC_ALLOC_MEM_FLAGS_GTT; + + /* Allocate CWSR TBA/TMA buffers */ ret = kfd_process_alloc_gpuvm(pdd, qpd->cwsr_base, KFD_CWSR_TBA_TMA_SIZE, flags, &mem, &kaddr); if (ret) -- cgit v1.2.3 From 808481e5fb8fff13fc8890c259b0d16cf363328d Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 25 Jun 2026 13:28:54 +0800 Subject: Revert "drm/amdgpu: defer KCQ remap until after MES resume in reset flow" This reverts commit 36b6c723d82c07dbbeae95d5883d4ecf0a643727. It introduced a regression on gfx11: the kfd negative test failed. Signed-off-by: Jesse Zhang Reviewed-by: Amber Lin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 70 ++++++++------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 1 - 2 files changed, 16 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index a5b835d0c166..982b41606d48 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -1989,24 +1989,10 @@ static ssize_t amdgpu_gfx_get_compute_reset_mask(struct device *dev, return amdgpu_show_reset_mask(buf, adev->gfx.compute_supported_reset); } -static int amdgpu_gfx_mes_reset_queue_reinit(struct amdgpu_ring *ring) -{ - struct amdgpu_device *adev = ring->adev; - int r; - - amdgpu_gfx_mqd_reset_restore(ring); - - r = amdgpu_mes_map_legacy_queue(adev, ring, 0); - if (r) - dev_err(adev->dev, "failed to remap kgq\n"); - - return r; -} - static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence, - bool use_mmio, bool *need_reinit) + bool use_mmio) { struct amdgpu_device *adev = ring->adev; bool reinit_queue; @@ -2021,9 +2007,6 @@ static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, else reinit_queue = use_mmio; - if (need_reinit) - *need_reinit = false; - amdgpu_ring_reset_helper_begin(ring, timedout_fence); r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio, 0); @@ -2035,9 +2018,13 @@ static int amdgpu_gfx_mes_reset_queue_start(struct amdgpu_ring *ring, RESET_QUEUES, 0, 0, 0); if (r) return r; + amdgpu_gfx_mqd_reset_restore(ring); - if (need_reinit) - *need_reinit = true; + r = amdgpu_mes_map_legacy_queue(adev, ring, 0); + if (r) { + dev_err(adev->dev, "failed to remap kgq\n"); + return r; + } } return 0; } @@ -2047,19 +2034,12 @@ int amdgpu_gfx_mes_reset_queue(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence, bool use_mmio) { - bool need_reinit; int r; - /* Single-queue reset (no suspend/resume): re-add the queue inline. */ r = amdgpu_gfx_mes_reset_queue_start(ring, vmid, timedout_fence, - use_mmio, &need_reinit); + use_mmio); if (r) return r; - if (need_reinit) { - r = amdgpu_gfx_mes_reset_queue_reinit(ring); - if (r) - return r; - } return amdgpu_ring_reset_helper_end(ring, timedout_fence); } @@ -2259,8 +2239,7 @@ static int amdgpu_gfx_reset_mes_kcq(struct amdgpu_device *adev, struct amdgpu_ring *guilty_ring, unsigned int db, struct amdgpu_ring **out_ring, - struct amdgpu_fence **out_fence, - bool *out_reinit) + struct amdgpu_fence **out_fence) { bool use_mmio = adev->gfx.mec.use_mmio_for_reset; struct amdgpu_fence *fence; @@ -2269,16 +2248,14 @@ static int amdgpu_gfx_reset_mes_kcq(struct amdgpu_device *adev, *out_ring = NULL; *out_fence = NULL; - *out_reinit = false; for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; if (ring == guilty_ring) continue; if (ring->doorbell_index == db) { fence = amdgpu_ring_find_guilty_fence(ring); - /* reset + unmap now; re-add (map) is deferred to after resume */ r = amdgpu_gfx_mes_reset_queue_start(ring, 0, fence, - use_mmio, out_reinit); + use_mmio); if (r) return r; *out_ring = ring; @@ -2329,16 +2306,12 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, fence_reset: /* reset the queue this came from if specified */ if (ring) { - bool reinit = false; - - /* reset + unmap now; re-add (map) is deferred to after resume */ r = amdgpu_gfx_mes_reset_queue_start(ring, 0, guilty_fence, - use_mmio, &reinit); + use_mmio); if (r) goto out; deferred_end[n_deferred].ring = ring; deferred_end[n_deferred].fence = guilty_fence; - deferred_end[n_deferred].reinit = reinit; n_deferred++; } if (uq) { @@ -2349,7 +2322,6 @@ fence_reset: for (i = 0; i < num_hung; i++) { struct amdgpu_ring *hr = NULL; struct amdgpu_fence *hf = NULL; - bool hr_reinit = false; pipe = hqd_info[i].pipe_index; queue = hqd_info[i].queue_index; @@ -2358,13 +2330,12 @@ fence_reset: /* reset any KCQs */ r = amdgpu_gfx_reset_mes_kcq(adev, ring, adev->gfx.mec.mes_hung_db_array[i], - &hr, &hf, &hr_reinit); + &hr, &hf); if (r) goto out; if (hr) { deferred_end[n_deferred].ring = hr; deferred_end[n_deferred].fence = hf; - deferred_end[n_deferred].reinit = hr_reinit; n_deferred++; } /* reset any KFD queues */ @@ -2401,21 +2372,12 @@ out: /* resume all will enable the non-hung queues */ amdgpu_mes_resume(adev, 0); - /* Now CP is running again — for queues that were unmapped during the - * reset, re-add (map) them only now that MES is resumed and back to a - * normal state, then replay backed-up commands and ring doorbells on - * each reset queue. + /* Now CP is running again — replay backed-up commands and ring + * doorbells on each reset queue. */ for (i = 0; i < n_deferred; i++) { - int er; - - if (deferred_end[i].reinit) { - er = amdgpu_gfx_mes_reset_queue_reinit(deferred_end[i].ring); - if (er && !r) - r = er; - } - er = amdgpu_ring_reset_helper_end(deferred_end[i].ring, - deferred_end[i].fence); + int er = amdgpu_ring_reset_helper_end(deferred_end[i].ring, + deferred_end[i].fence); if (er && !r) r = er; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index 9432107c96a1..aefd4f03b443 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -550,7 +550,6 @@ struct amdgpu_gfx { struct amdgpu_gfx_deferred_entry { struct amdgpu_ring *ring; struct amdgpu_fence *fence; - bool reinit; }; struct amdgpu_gfx_ras_reg_entry { -- cgit v1.2.3 From 1a0aa3c4cb207fcbf7ebd2d9ed9f1cfb4560bd2c Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Tue, 23 Jun 2026 15:24:11 -0400 Subject: drm/amdkfd: use node XCC count for v9 CRIU control stack restore set_queue_properties_from_criu() divided the checkpointed control stack size by NUM_XCC(adev->gfx.xcc_mask) (whole GPU), while the checkpoint size was recorded, the MQD buffer allocated, and the control stack restored using the per-node mask NUM_XCC(mm->dev->xcc_mask). On spatially partitioned GFX9.4.3 (CPX/QPX) these differ, so the per-XCC control stack size used for the restore memcpy could exceed the region sized for the MQD allocation, writing past the BO into adjacent kernel memory; it also broke legitimate restore on partitioned parts. Divide by the per-node XCC count so allocation and copy agree, leaving kfd_queue_acquire_buffers() to bound the size against the node's advertised control stack size. Signed-off-by: Yongqiang Sun Reviewed-by: David Francis 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 0ac35789b239..d723b07379b3 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c @@ -1040,7 +1040,7 @@ int kfd_criu_restore_queue(struct kfd_process *p, ctl_stack = mqd + q_data->mqd_size; memset(&qp, 0, sizeof(qp)); - set_queue_properties_from_criu(&qp, q_data, NUM_XCC(pdd->dev->adev->gfx.xcc_mask)); + set_queue_properties_from_criu(&qp, q_data, NUM_XCC(pdd->dev->xcc_mask)); ret = kfd_queue_acquire_buffers(pdd, &qp); if (ret) { -- cgit v1.2.3 From d37d3555ddec6a8f9ec91a7c204e3358446dc86f Mon Sep 17 00:00:00 2001 From: Relja Vojvodic Date: Tue, 9 Jun 2026 16:44:25 -0400 Subject: drm/amd/display: Update link bw [Why & How] - Added link bw to switch case Reviewed-by: Wenjing Liu Signed-off-by: Relja Vojvodic Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index d47aefecfc2d..3f185ba2846f 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -181,6 +181,12 @@ uint32_t link_bw_kbps_from_raw_frl_link_rate_data(uint8_t bw) return 40000000; case 0b110: return 48000000; + case 0b111: + return 64000000; + case 0b1000: + return 80000000; + case 0b1001: + return 96000000; } return 0; -- cgit v1.2.3 From 7a39b1c3b2e6b27f4230a20ccf9ac5a2737fa8b0 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Tue, 9 Jun 2026 22:21:16 -0400 Subject: drm/amd/display: Replace repeated no-native-i2c checks with force_i2c_over_aux field [Why] The compound condition checking dp_connector_no_native_i2c and no_ddc_pin was duplicated across many files, obscuring intent at every call site. [How] Add bool force_i2c_over_aux to struct dc_link, initialized once during link creation. Add link_get_ddc_aux_inst() helper to select the correct aux instance. Wire into link_service via construct_link_service_ddc(). Replace all duplicated condition checks and aux instance selection blocks with the new field and helper. No functional change. Reviewed-by: Nevenko Stupar Signed-off-by: Wenjing Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 4 ++++ drivers/gpu/drm/amd/display/dc/dce/dce_aux.c | 8 ++++---- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 2 +- drivers/gpu/drm/amd/display/dc/inc/link_service.h | 1 + drivers/gpu/drm/amd/display/dc/link/link_factory.c | 7 +++++-- .../gpu/drm/amd/display/dc/link/protocols/link_ddc.c | 20 +++++++++++++++++--- .../gpu/drm/amd/display/dc/link/protocols/link_ddc.h | 2 ++ .../display/dc/link/protocols/link_dp_capability.c | 2 +- .../display/dc/link/protocols/link_dp_panel_replay.c | 7 ++----- .../dc/link/protocols/link_edp_panel_control.c | 13 +++---------- drivers/gpu/drm/amd/display/modules/power/power.c | 7 +------ .../gpu/drm/amd/display/modules/power/power_abm.c | 14 ++------------ 12 files changed, 43 insertions(+), 44 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 0e115b1aac5f..b323f7826451 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1845,6 +1845,10 @@ struct dc_scratch_space { * of ddc_pin to know which aux instance is associated with link. */ bool no_ddc_pin; + /** When set, forces all native I2C communication on this DP connector + * to use the I2C-over-AUX protocol instead of native I2C signaling. + */ + bool force_to_use_aux; enum gpio_ddc_line aux_hw_inst; enum gpio_ddc_line ddc_hw_inst; diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c b/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c index 72ad3ee3d6a5..fa0d63de1aa4 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c @@ -529,7 +529,7 @@ static uint32_t dce_aux_configure_timeout(struct ddc_service *ddc, uint32_t prev_timeout_val = 0; struct ddc *ddc_pin = ddc->ddc_pin; - if (ddc->ctx->dc->config.dp_connector_no_native_i2c && ddc->link->no_ddc_pin) + if (ddc->link->force_to_use_aux) return dce_aux_configure_timeout_without_ddc_pin(ddc, timeout_in_us); struct dce_aux *aux_engine = ddc->ctx->dc->res_pool->engines[ddc_pin->pin_data->en]; @@ -652,7 +652,7 @@ int dce_aux_transfer_raw(struct ddc_service *ddc, struct aux_payload *payload, enum aux_return_code_type *operation_result) { - if (ddc->ctx->dc->config.dp_connector_no_native_i2c && ddc->link->no_ddc_pin) { + if (ddc->link->force_to_use_aux) { /* Check whether aux to be processed via dmub or dcn directly */ if (ddc->ctx->dc->debug.enable_dmub_aux_for_legacy_ddc) { return dce_aux_transfer_dmub_raw(ddc, payload, operation_result); @@ -795,7 +795,7 @@ int dce_aux_transfer_dmub_raw(struct ddc_service *ddc, release_engine(aux_engine); } - if (ddc->ctx->dc->config.dp_connector_no_native_i2c && ddc->link->no_ddc_pin) { + if (ddc->link->force_to_use_aux) { struct dce_aux *aux_engine = ddc->ctx->dc->res_pool->engines[ddc->link->aux_hw_inst]; if (!acquire_aux_engine_without_ddc_pin(aux_engine, ddc_pin)) { @@ -893,7 +893,7 @@ bool dce_aux_transfer_with_retries(struct ddc_service *ddc, aux110 = FROM_AUX_ENGINE(aux_engine); } - if (ddc->ctx->dc->config.dp_connector_no_native_i2c && ddc->link->no_ddc_pin) { + if (ddc->link->force_to_use_aux) { aux_engine = ddc->ctx->dc->res_pool->engines[ddc->link->aux_hw_inst]; aux110 = FROM_AUX_ENGINE(aux_engine); } 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 9107493cdcda..af83286c6114 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 @@ -321,7 +321,7 @@ void dcn401_init_hw(struct dc *dc) user_level = link->panel_cntl->stored_backlight_registers.USER_LEVEL; } - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { + if (link->force_to_use_aux) { struct graphics_object_i2c_info i2c_info; struct ddc *ddc_pin; struct gpio_ddc_hw_info hw_info; 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 23202c2114bb..addeb3e3b25a 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/link_service.h +++ b/drivers/gpu/drm/amd/display/dc/inc/link_service.h @@ -193,6 +193,7 @@ struct link_service { struct aux_payload *payload); bool (*is_in_aux_transaction_mode)(struct ddc_service *ddc); uint32_t (*get_aux_defer_delay)(struct ddc_service *ddc); + uint8_t (*get_ddc_aux_inst)(const struct dc_link *link); /*************************** DP Capability ****************************/ diff --git a/drivers/gpu/drm/amd/display/dc/link/link_factory.c b/drivers/gpu/drm/amd/display/dc/link/link_factory.c index b6262e43ca02..67ce8d95bbd6 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_factory.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_factory.c @@ -143,6 +143,7 @@ static void construct_link_service_ddc(struct link_service *link_srv) link_aux_transfer_with_retries_no_mutex; link_srv->is_in_aux_transaction_mode = link_is_in_aux_transaction_mode; link_srv->get_aux_defer_delay = link_get_aux_defer_delay; + link_srv->get_ddc_aux_inst = link_get_ddc_aux_inst; } /* link dp capability implements dp specific link capability retrieval sequence. @@ -441,7 +442,7 @@ static enum channel_id get_ddc_line(struct dc_link *link) channel = CHANNEL_ID_UNKNOWN; - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { + if (link->force_to_use_aux) { channel = link->aux_hw_inst + 1; } else { ddc = get_ddc_pin(link->ddc); @@ -576,6 +577,8 @@ static bool construct_phy(struct dc_link *link, link->is_internal_display = (disp_connect_caps_info.INTERNAL_DISPLAY != 0); DC_LOG_DC("BIOS object table - is_internal_display: %d", link->is_internal_display); link->no_ddc_pin = disp_connect_caps_info.NO_DDC_PIN != 0; + link->force_to_use_aux = link->dc->config.dp_connector_no_native_i2c + && link->no_ddc_pin; } if (link->link_id.type != OBJECT_TYPE_CONNECTOR) { @@ -598,7 +601,7 @@ static bool construct_phy(struct dc_link *link, goto ddc_create_fail; } - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { + if (link->force_to_use_aux) { link->ddc_hw_inst = link->aux_hw_inst; } else { /* Embedded display connectors such as LVDS may not have DDC. */ diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c index ead71f6d116d..f9d5a2441e38 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c @@ -120,8 +120,7 @@ static void ddc_service_construct( ddc_service->link = init_data->link; ddc_service->ctx = init_data->ctx; - if (ddc_service->link && ddc_service->ctx->dc->config.dp_connector_no_native_i2c && - ddc_service->link->no_ddc_pin) { + if (ddc_service->link && ddc_service->link->force_to_use_aux) { // Obtain aux instance info from i2c_info without GPIO DDC pin info if (dcb->funcs->get_connector_aux_info(dcb, init_data->id, &i2c_info) == BP_RESULT_OK) ddc_service->link->aux_hw_inst = (uint8_t)i2c_info.i2c_line; @@ -252,6 +251,21 @@ static uint32_t defer_delay_converter_wa( #define DP_TRANSLATOR_DELAY 5 +/** + * link_get_ddc_aux_inst - Return the AUX/DDC hardware instance for a link. + * @link: the link to query + * + * Return: aux_hw_inst when I2C is forced over AUX, otherwise the DDC pin + * channel index. + */ +uint8_t link_get_ddc_aux_inst(const struct dc_link *link) +{ + if (link->force_to_use_aux) + return link->aux_hw_inst; + ASSERT(link->ddc->ddc_pin->hw_info.ddc_channel <= 0xFF); + return (uint8_t)link->ddc->ddc_pin->hw_info.ddc_channel; +} + uint32_t link_get_aux_defer_delay(struct ddc_service *ddc) { uint32_t defer_delay = 0; @@ -526,7 +540,7 @@ bool try_to_configure_aux_timeout(struct ddc_service *ddc, if (ddc->link->ep_type != DISPLAY_ENDPOINT_PHY) return true; - if (ddc->ctx->dc->config.dp_connector_no_native_i2c && ddc->link->no_ddc_pin) { + if (ddc->link->force_to_use_aux) { if (ddc->ctx->dc->res_pool->engines[ddc->link->aux_hw_inst]->funcs->configure_timeout) { ddc->ctx->dc->res_pool->engines[ddc->link->aux_hw_inst]->funcs->configure_timeout(ddc, timeout); result = true; diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h index f2a80e12494b..fdd8a3dce97f 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h @@ -46,6 +46,8 @@ void set_ddc_transaction_type( struct ddc_service *ddc, enum ddc_transaction_type type); +uint8_t link_get_ddc_aux_inst(const struct dc_link *link); + uint32_t link_get_aux_defer_delay(struct ddc_service *ddc); bool link_is_in_aux_transaction_mode(struct ddc_service *ddc); diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index 3f185ba2846f..d2329714408a 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -2583,7 +2583,7 @@ bool dp_is_sink_present(struct dc_link *link) /* We can't perform the step below for ASICs with no Native * I2C signaling support on DP connectors, so skip it. */ - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) + if (link->force_to_use_aux) return present; ddc = get_ddc_pin(link->ddc); diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c index 465b9e53d311..0d4f88ff844d 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_panel_replay.c @@ -25,6 +25,7 @@ #include "link_dp_panel_replay.h" #include "link_edp_panel_control.h" +#include "link_ddc.h" #include "link_dpcd.h" #include "dm_helpers.h" #include "dc/dc_dmub_srv.h" @@ -119,11 +120,7 @@ static bool dp_setup_panel_replay(struct dc_link *link, const struct dc_stream_s if (!dp_pr_get_panel_inst(dc, link, &panel_inst)) return false; - if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { - replay_context.aux_inst = (enum channel_id) link->aux_hw_inst; - } else { - replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; - } + replay_context.aux_inst = (enum channel_id) link_get_ddc_aux_inst(link); replay_context.digbe_inst = link->link_enc->transmitter; replay_context.digfe_inst = link->link_enc->preferred_engine; diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c index 1fda6e226e23..baf57692bbb5 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c @@ -29,6 +29,7 @@ */ #include "link_edp_panel_control.h" +#include "link_ddc.h" #include "link_dpcd.h" #include "link_dp_capability.h" #include "dm_helpers.h" @@ -788,11 +789,7 @@ bool edp_setup_psr(struct dc_link *link, } } - if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { - psr_context->channel = (enum channel_id)link->aux_hw_inst; - } else { - psr_context->channel = link->ddc->ddc_pin->hw_info.ddc_channel; - } + psr_context->channel = link_get_ddc_aux_inst(link); psr_context->transmitterId = link->link_enc->transmitter; psr_context->engineId = link->link_enc->preferred_engine; @@ -1025,11 +1022,7 @@ bool edp_setup_freesync_replay(struct dc_link *link, const struct dc_stream_stat if (!dp_pr_get_panel_inst(dc, link, &panel_inst)) return false; - if (dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { - replay_context.aux_inst = (enum channel_id) link->aux_hw_inst; - } else { - replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; - } + replay_context.aux_inst = link_get_ddc_aux_inst(link); replay_context.digbe_inst = link->link_enc->transmitter; replay_context.digfe_inst = link->link_enc->preferred_engine; diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index af6b162a337d..db101fdb11f0 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -483,12 +483,7 @@ bool mod_power_notify_mode_change(struct mod_power *mod_power, link = dc_stream_get_link(stream); if (link != NULL && dc_get_edp_link_panel_inst(dc, link, &panel_inst)) { - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { - aux_inst = (uint8_t)link->aux_hw_inst; - } else { - ASSERT(link->ddc->ddc_pin->hw_info.ddc_channel <= 0xFF); - aux_inst = (uint8_t)link->ddc->ddc_pin->hw_info.ddc_channel; - } + aux_inst = link->dc->link_srv->get_ddc_aux_inst(link); mod_power_update_backlight_on_mode_change(core_power, link, panel_inst, aux_inst, is_hdr); diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index a1a0563598b5..b9447cb7485b 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -849,12 +849,7 @@ bool mod_power_set_backlight_nits(struct mod_power *mod_power, core_power = MOD_POWER_TO_CORE(mod_power); link = dc_stream_get_link(stream); - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { - aux_inst = (uint8_t)link->aux_hw_inst; - } else { - ASSERT(link->ddc->ddc_pin->hw_info.ddc_channel <= 0xFF); - aux_inst = (uint8_t)link->ddc->ddc_pin->hw_info.ddc_channel; - } + aux_inst = link->dc->link_srv->get_ddc_aux_inst(link); if (!dc_get_edp_link_panel_inst(core_power->dc, stream->link, &panel_inst)) return false; @@ -941,12 +936,7 @@ bool mod_power_set_backlight_percent(struct mod_power *mod_power, core_power = MOD_POWER_TO_CORE(mod_power); link = dc_stream_get_link(stream); - if (link->ctx->dc->config.dp_connector_no_native_i2c && link->no_ddc_pin) { - aux_inst = (uint8_t)link->aux_hw_inst; - } else { - ASSERT(link->ddc->ddc_pin->hw_info.ddc_channel <= 0xFF); - aux_inst = (uint8_t)link->ddc->ddc_pin->hw_info.ddc_channel; - } + aux_inst = link->dc->link_srv->get_ddc_aux_inst(link); if (!dc_get_edp_link_panel_inst(core_power->dc, stream->link, &panel_inst)) return false; -- cgit v1.2.3 From 703e3ae7565d0b7eeaa91d679b4ef3e38f257735 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 12 Jun 2026 17:20:27 -0600 Subject: drm/amd/display: Extract backlight helpers for KUnit tests [WHAT] Extract shared backlight device index lookup and property setup into testable helpers. The duplicated bd-to-index scan in update_status/get_brightness is replaced by amdgpu_dm_backlight_get_device_index(), and the inline backlight_properties calculation is replaced by amdgpu_dm_backlight_fill_props(). Add KUnit coverage for both new helpers. Keep the runtime power_supply_is_system_supplied() call at the caller so the helpers remain pure and deterministic under test. Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 84 ++++++++------ .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 8 ++ .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 123 +++++++++++++++++++++ 3 files changed, 184 insertions(+), 31 deletions(-) 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 f19092a3237e..33f4be403a65 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 @@ -236,6 +236,21 @@ static struct dc_stream_state *dm_find_stream_with_link( return NULL; } +STATIC_IFN_KUNIT +int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, + struct backlight_device *bd) +{ + int i; + + for (i = 0; i < dm->num_of_edps; i++) { + if (bd == dm->backlight_dev[i]) + return i; + } + + return 0; +} +EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_device_index); + void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, int bl_idx, u32 user_brightness) @@ -335,14 +350,8 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; + int i = amdgpu_dm_backlight_get_device_index(dm, bd); - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); return 0; @@ -377,14 +386,8 @@ static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; + int i = amdgpu_dm_backlight_get_device_index(dm, bd); - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; return amdgpu_dm_backlight_get_level(dm, i); } @@ -394,6 +397,35 @@ static const struct backlight_ops amdgpu_dm_backlight_ops = { .update_status = amdgpu_dm_backlight_update_status, }; +STATIC_IFN_KUNIT +void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, + bool is_system_supplied, + bool custom_curve_enabled, + struct backlight_properties *props) +{ + unsigned int min, max; + + if (get_brightness_range(caps, &min, &max)) { + if (is_system_supplied) + props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, + 100); + else + props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, + 100); + props->max_brightness = max - min; + } else { + props->brightness = MAX_BACKLIGHT_LEVEL; + props->max_brightness = MAX_BACKLIGHT_LEVEL; + } + + if (caps && caps->data_points && custom_curve_enabled) + props->scale = BACKLIGHT_SCALE_NON_LINEAR; + else + props->scale = BACKLIGHT_SCALE_LINEAR; + props->type = BACKLIGHT_RAW; +} +EXPORT_IF_KUNIT(amdgpu_dm_backlight_fill_props); + void amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) { @@ -402,7 +434,6 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) struct backlight_properties props = { 0 }; struct amdgpu_dm_backlight_caps *caps; char bl_name[16]; - int min, max; int real_brightness; int init_brightness; @@ -417,26 +448,17 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) } caps = &dm->backlight_caps[aconnector->bl_idx]; - if (get_brightness_range(caps, &min, &max)) { - if (power_supply_is_system_supplied() > 0) - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); - else - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); - /* min is zero, so max needs to be adjusted */ - props.max_brightness = max - min; - drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, - caps->ac_level, caps->dc_level); - } else - props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; + amdgpu_dm_backlight_fill_props(caps, power_supply_is_system_supplied() > 0, + !(amdgpu_dc_debug_mask & + DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE), + &props); + drm_dbg(drm, "Backlight caps: max_brightness: %d, ac %d, dc %d\n", + props.max_brightness, caps->ac_level, caps->dc_level); init_brightness = props.brightness; - if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { + if (props.scale == BACKLIGHT_SCALE_NON_LINEAR) drm_info(drm, "Using custom brightness curve\n"); - props.scale = BACKLIGHT_SCALE_NON_LINEAR; - } else - props.scale = BACKLIGHT_SCALE_LINEAR; - props.type = BACKLIGHT_RAW; snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", drm->primary->index + aconnector->bl_idx); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index a6c01b7ccab3..98d612c60ae9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -26,6 +26,8 @@ struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct backlight_device; +struct backlight_properties; struct drm_connector; struct attribute_group; @@ -56,6 +58,12 @@ u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, uint32_t brightness); u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, uint32_t brightness); +int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, + struct backlight_device *bd); +void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, + bool is_system_supplied, + bool custom_curve_enabled, + struct backlight_properties *props); uint amdgpu_dm_get_dc_debug_mask(void); void amdgpu_dm_set_dc_debug_mask(uint val); int amdgpu_dm_get_abm_level_param(void); 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 8763cd635ae1..0e9de940e5a8 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 @@ -6,6 +6,7 @@ */ #include +#include #include "dc.h" #include "amdgpu.h" @@ -13,6 +14,7 @@ #include "amdgpu_dm.h" #include "amdgpu_dm_backlight.h" #include "amd_shared.h" +#include "dc/inc/hw/panel_cntl.h" struct dm_backlight_connector_fixture { struct amdgpu_device *adev; @@ -47,6 +49,51 @@ static void setup_test_connector(struct kunit *test, fixture->link->connector_signal = signal; } +/* Tests for amdgpu_dm_backlight_get_device_index() */ + +/** + * dm_test_backlight_device_index_matches_second - Test matching second backlight device + * @test: The KUnit test context + */ +static void dm_test_backlight_device_index_matches_second(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct backlight_device *bd0; + struct backlight_device *bd1; + + bd0 = kunit_kzalloc(test, sizeof(*bd0), GFP_KERNEL); + bd1 = kunit_kzalloc(test, sizeof(*bd1), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bd0); + KUNIT_ASSERT_NOT_NULL(test, bd1); + + dm->num_of_edps = 2; + dm->backlight_dev[0] = bd0; + dm->backlight_dev[1] = bd1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_device_index(dm, bd1), 1); +} + +/** + * dm_test_backlight_device_index_missing_fallback - Test missing backlight device fallback + * @test: The KUnit test context + */ +static void dm_test_backlight_device_index_missing_fallback(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct backlight_device *known_bd; + struct backlight_device *unknown_bd; + + known_bd = kunit_kzalloc(test, sizeof(*known_bd), GFP_KERNEL); + unknown_bd = kunit_kzalloc(test, sizeof(*unknown_bd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, known_bd); + KUNIT_ASSERT_NOT_NULL(test, unknown_bd); + + dm->num_of_edps = 1; + dm->backlight_dev[0] = known_bd; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_device_index(dm, unknown_bd), 0); +} + /* Tests for amdgpu_dm_update_backlight_caps() */ /** @@ -740,6 +787,75 @@ static void dm_test_brightness_range_zero_signals(struct kunit *test) KUNIT_EXPECT_EQ(test, max, 0U); } +/* Tests for amdgpu_dm_backlight_fill_props() */ + +/** + * dm_test_backlight_fill_props_ac_linear - Test AC brightness and linear scale + * @test: The KUnit test context + */ +static void dm_test_backlight_fill_props_ac_linear(struct kunit *test) +{ + struct backlight_properties props = {}; + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.ac_level = 40; + caps.dc_level = 20; + + get_brightness_range(&caps, &min, &max); + amdgpu_dm_backlight_fill_props(&caps, true, false, &props); + + KUNIT_EXPECT_EQ(test, props.brightness, + DIV_ROUND_CLOSEST((max - min) * caps.ac_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR); + KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); +} + +/** + * dm_test_backlight_fill_props_dc_nonlinear - Test DC brightness and non-linear scale + * @test: The KUnit test context + */ +static void dm_test_backlight_fill_props_dc_nonlinear(struct kunit *test) +{ + struct backlight_properties props = {}; + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.ac_level = 40; + caps.dc_level = 20; + caps.data_points = 2; + + get_brightness_range(&caps, &min, &max); + amdgpu_dm_backlight_fill_props(&caps, false, true, &props); + + KUNIT_EXPECT_EQ(test, props.brightness, + DIV_ROUND_CLOSEST((max - min) * caps.dc_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_NON_LINEAR); + KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); +} + +/** + * dm_test_backlight_fill_props_default_range - Test default properties without caps + * @test: The KUnit test context + */ +static void dm_test_backlight_fill_props_default_range(struct kunit *test) +{ + struct backlight_properties props = {}; + + amdgpu_dm_backlight_fill_props(NULL, false, true, &props); + + KUNIT_EXPECT_EQ(test, props.brightness, MAX_BACKLIGHT_LEVEL); + KUNIT_EXPECT_EQ(test, props.max_brightness, MAX_BACKLIGHT_LEVEL); + KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR); + KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); +} + /* Tests for amdgpu_dm_update_connector_ext_caps() */ /** @@ -1062,6 +1178,9 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) } static struct kunit_case dm_backlight_test_cases[] = { + /* amdgpu_dm_backlight_get_device_index */ + KUNIT_CASE(dm_test_backlight_device_index_matches_second), + KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), KUNIT_CASE(dm_test_backlight_caps_valid_short_circuit), #if !defined(CONFIG_ACPI) KUNIT_CASE(dm_test_backlight_caps_aux_support_noop), @@ -1095,6 +1214,10 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_brightness_from_user_midrange), KUNIT_CASE(dm_test_brightness_from_user_with_curve), KUNIT_CASE(dm_test_brightness_range_zero_signals), + /* amdgpu_dm_backlight_fill_props */ + KUNIT_CASE(dm_test_backlight_fill_props_ac_linear), + KUNIT_CASE(dm_test_backlight_fill_props_dc_nonlinear), + KUNIT_CASE(dm_test_backlight_fill_props_default_range), /* amdgpu_dm_update_connector_ext_caps */ KUNIT_CASE(dm_test_update_connector_ext_caps_negative_bl_idx), KUNIT_CASE(dm_test_update_connector_ext_caps_non_edp), -- cgit v1.2.3 From 88ae862060f05cd8279e764832f04eafafa505d8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 12 Jun 2026 20:25:05 -0600 Subject: drm/amd/display: Add more KUnit tests for amdgpu_dm_colorop [WHAT] Add KUnit coverage for amdgpu_dm_initialize_default_pipeline() using an amdgpu_device-backed DRM mock so drm_to_adev() and the DC color capability checks are exercised. Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 1 + .../amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 147 ++++++++++++++++++--- 2 files changed, 133 insertions(+), 15 deletions(-) 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 48f5c431eaf9..056a76b88f43 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 @@ -235,3 +235,4 @@ int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_pr return amdgpu_dm_build_default_pipeline(dev, plane, hw_3d_lut, list); } +EXPORT_IF_KUNIT(amdgpu_dm_initialize_default_pipeline); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c index fa270ff28c6a..b28a165b213e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -9,6 +9,8 @@ #include #include +#include "dc.h" +#include "amdgpu.h" #include "amdgpu_dm_colorop.h" /* Tests for amdgpu_dm_supported_degam_tfs */ @@ -133,6 +135,30 @@ static void kunit_colorop_pipeline_destroy(void *drm) drm_colorop_pipeline_destroy((struct drm_device *)drm); } +static void dm_expect_colorop_pipeline(struct kunit *test, struct drm_device *drm, + const struct drm_prop_enum_list *list, + const enum drm_colorop_type *expected, + int expected_count) +{ + struct drm_colorop *op, *first = NULL; + int i = 0; + + drm_for_each_colorop(op, drm) { + if (op->base.id == (uint32_t)list->type) { + first = op; + break; + } + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, first); + + for (op = first; op; op = op->next, i++) { + KUNIT_ASSERT_LT(test, i, expected_count); + KUNIT_EXPECT_EQ(test, op->type, expected[i]); + KUNIT_EXPECT_NOT_NULL(test, op->bypass_property); + } + KUNIT_EXPECT_EQ(test, i, expected_count); +} + /** * dm_test_initialize_default_pipeline() - Verify amdgpu_dm_build_default_pipeline() * produces the expected colorop chain with all ops bypassable. @@ -154,8 +180,6 @@ static void dm_test_initialize_default_pipeline(struct kunit *test) struct drm_device *drm; struct drm_plane *plane; struct drm_prop_enum_list list = {}; - struct drm_colorop *op, *first = NULL; - int i = 0; int ret; dev = drm_kunit_helper_alloc_device(test); @@ -185,20 +209,110 @@ static void dm_test_initialize_default_pipeline(struct kunit *test) KUNIT_ASSERT_EQ(test, ret, 0); kfree(list.name); - drm_for_each_colorop(op, drm) { - if (op->base.id == (uint32_t)list.type) { - first = op; - break; - } - } - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, first); + dm_expect_colorop_pipeline(test, drm, &list, expected, ARRAY_SIZE(expected)); +} - for (op = first; op; op = op->next, i++) { - KUNIT_ASSERT_LT(test, i, (int)ARRAY_SIZE(expected)); - KUNIT_EXPECT_EQ(test, op->type, expected[i]); - KUNIT_EXPECT_NOT_NULL(test, op->bypass_property); - } - KUNIT_EXPECT_EQ(test, i, (int)ARRAY_SIZE(expected)); +static void dm_test_initialize_default_pipeline_caps(struct kunit *test, + bool dpp_hw_3d_lut, + bool mpc_preblend, + const enum drm_colorop_type *expected, + int expected_count) +{ + struct drm_prop_enum_list list = {}; + struct amdgpu_device *adev; + struct drm_device *drm; + struct drm_plane *plane; + struct device *dev; + struct dc *dc; + int ret; + + 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); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.dc->caps.color.dpp.hw_3d_lut = dpp_hw_3d_lut; + adev->dm.dc->caps.color.mpc.preblend = mpc_preblend; + + plane = drm_kunit_helper_create_primary_plane(test, drm, + NULL, NULL, NULL, 0, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); + + kunit_add_action(test, kunit_colorop_pipeline_destroy, drm); + + ret = amdgpu_dm_initialize_default_pipeline(plane, &list); + KUNIT_ASSERT_EQ(test, ret, 0); + kfree(list.name); + + dm_expect_colorop_pipeline(test, drm, &list, expected, expected_count); +} + +/** + * dm_test_initialize_default_pipeline_dpp_3d_lut() - Test DPP 3D LUT cap. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline_dpp_3d_lut(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + 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, + }; + + dm_test_initialize_default_pipeline_caps(test, true, false, + expected, ARRAY_SIZE(expected)); +} + +/** + * dm_test_initialize_default_pipeline_mpc_preblend() - Test MPC preblend cap. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline_mpc_preblend(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + 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, + }; + + dm_test_initialize_default_pipeline_caps(test, false, true, + expected, ARRAY_SIZE(expected)); +} + +/** + * dm_test_initialize_default_pipeline_no_3d_lut() - Test no 3D LUT caps. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline_no_3d_lut(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + + dm_test_initialize_default_pipeline_caps(test, false, false, + expected, ARRAY_SIZE(expected)); } static struct kunit_case dm_colorop_test_cases[] = { @@ -224,6 +338,9 @@ static struct kunit_case dm_colorop_test_cases[] = { KUNIT_CASE(dm_test_degam_and_blnd_tfs_match), /* amdgpu_dm_initialize_default_pipeline */ KUNIT_CASE(dm_test_initialize_default_pipeline), + KUNIT_CASE(dm_test_initialize_default_pipeline_dpp_3d_lut), + KUNIT_CASE(dm_test_initialize_default_pipeline_mpc_preblend), + KUNIT_CASE(dm_test_initialize_default_pipeline_no_3d_lut), {} }; -- cgit v1.2.3 From 2b147895be109e0860269a7a72c697cdf049a885 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 12 Jun 2026 16:12:21 -0400 Subject: drm/amd/display: Add kunit tests for amdgpu_dm_plane Add kunit tests for some functions in amdgpu_dm_plane. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 115 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h | 51 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 1204 ++++++++++++++++++++ 4 files changed, 1325 insertions(+), 47 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c 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 c7f8e08feaf4..62f1ad1ff7b5 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,6 +37,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_plane.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_colorop.h" #include "gc/gc_11_0_0_offset.h" #include "gc/gc_11_0_0_sh_mask.h" @@ -97,6 +98,7 @@ const struct drm_format_info *amdgpu_dm_plane_get_format_info(u32 pixel_format, { return amdgpu_lookup_format_info(pixel_format, modifier); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_format_info); void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state *plane_state, bool *per_pixel_alpha, bool *pre_multiplied_alpha, @@ -139,8 +141,10 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state *global_alpha_value = plane_state->alpha >> 8; } } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_blending_from_plane_state); -static void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, uint64_t *cap, uint64_t mod) +STATIC_IFN_KUNIT void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod) { if (!*mods) return; @@ -164,27 +168,29 @@ static void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, uint64 (*mods)[*size] = mod; *size += 1; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_add_modifier); -static bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier) +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); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_modifier_has_dcc); -static unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_t modifier) +STATIC_IFN_KUNIT unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_t modifier) { if (modifier == DRM_FORMAT_MOD_LINEAR) return 0; return AMD_FMT_MOD_GET(TILE, modifier); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_modifier_gfx9_swizzle_mode); -static void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, - uint64_t tiling_flags) +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); @@ -210,9 +216,10 @@ static void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_in 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 void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, - struct dc_tiling_info *tiling_info) +STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, + struct dc_tiling_info *tiling_info) { /* Fill GFX9 params */ tiling_info->gfx9.num_pipes = @@ -231,10 +238,11 @@ static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgp if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(10, 3, 0)) tiling_info->gfx9.num_pkrs = adev->gfx.config.gb_addr_config_fields.num_pkrs; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_tiling_info_from_device); -static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, - struct dc_tiling_info *tiling_info, - uint64_t modifier) +STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, + struct dc_tiling_info *tiling_info, + uint64_t modifier) { unsigned int mod_bank_xor_bits = AMD_FMT_MOD_GET(BANK_XOR_BITS, modifier); unsigned int mod_pipe_xor_bits = AMD_FMT_MOD_GET(PIPE_XOR_BITS, modifier); @@ -259,14 +267,15 @@ static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amd /* for DCC we know it isn't rb aligned, so rb_per_se doesn't matter. */ } } - -static int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, - const enum surface_pixel_format format, - const enum dc_rotation_angle rotation, - const struct dc_tiling_info *tiling_info, - const struct dc_plane_dcc_param *dcc, - const struct dc_plane_address *address, - const struct plane_size *plane_size) +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier); + +STATIC_IFN_KUNIT int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct dc_tiling_info *tiling_info, + const struct dc_plane_dcc_param *dcc, + const struct dc_plane_address *address, + const struct plane_size *plane_size) { struct dc *dc = adev->dm.dc; struct dc_dcc_surface_param input; @@ -307,15 +316,16 @@ static int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, return 0; } - -static int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, - const struct amdgpu_framebuffer *afb, - const enum surface_pixel_format format, - const enum dc_rotation_angle rotation, - const struct plane_size *plane_size, - struct dc_tiling_info *tiling_info, - struct dc_plane_dcc_param *dcc, - struct dc_plane_address *address) +EXPORT_IF_KUNIT(amdgpu_dm_plane_validate_dcc); + +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) { const uint64_t modifier = afb->base.modifier; int ret = 0; @@ -358,15 +368,16 @@ static int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdg return ret; } - -static int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, - const struct amdgpu_framebuffer *afb, - const enum surface_pixel_format format, - const enum dc_rotation_angle rotation, - const struct plane_size *plane_size, - struct dc_tiling_info *tiling_info, - struct dc_plane_dcc_param *dcc, - struct dc_plane_address *address) +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers); + +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) { const uint64_t modifier = afb->base.modifier; int ret = 0; @@ -398,6 +409,7 @@ static int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amd return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers); static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *adev, uint64_t **mods, @@ -724,7 +736,7 @@ static void amdgpu_dm_plane_add_gfx12_modifiers(struct amdgpu_device *adev, } -static int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsigned int plane_type, uint64_t **mods) +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; @@ -777,10 +789,11 @@ static int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsig return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_plane_modifiers); -static int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, - const struct dc_plane_cap *plane_cap, - uint32_t *formats, int max_formats) +STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, + const struct dc_plane_cap *plane_cap, + uint32_t *formats, int max_formats) { int i, num_formats = 0; @@ -836,6 +849,7 @@ static int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, return num_formats; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_plane_formats); int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, @@ -922,6 +936,7 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_plane_buffer_attributes); static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *new_state) @@ -1042,9 +1057,9 @@ static void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane, amdgpu_bo_unref(&rbo); } -static void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, - struct drm_framebuffer *fb, - int *min_downscale, int *max_upscale) +STATIC_IFN_KUNIT void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, + struct drm_framebuffer *fb, + int *min_downscale, int *max_upscale) { struct amdgpu_device *adev = drm_to_adev(dev); struct dc *dc = adev->dm.dc; @@ -1088,6 +1103,7 @@ static void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, if (*min_downscale == 1) *min_downscale = 1000; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_min_max_dc_plane_scaling); int amdgpu_dm_plane_helper_check_state(struct drm_plane_state *state, struct drm_crtc_state *new_crtc_state) @@ -1142,6 +1158,7 @@ int amdgpu_dm_plane_helper_check_state(struct drm_plane_state *state, return drm_atomic_helper_check_plane_state( state, new_crtc_state, min_scale, max_scale, true, true); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_helper_check_state); int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev, const struct drm_plane_state *state, @@ -1225,6 +1242,7 @@ int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_dc_scaling_info); static int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state) @@ -1343,6 +1361,7 @@ int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_cursor_position); void amdgpu_dm_plane_handle_cursor_update(struct drm_plane *plane, struct drm_plane_state *old_plane_state) @@ -1546,9 +1565,9 @@ static struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct return &dm_plane_state->base; } -static bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, - uint32_t format, - uint64_t modifier) +STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier) { struct amdgpu_device *adev = drm_to_adev(plane->dev); const struct drm_format_info *info = drm_format_info(format); @@ -1607,6 +1626,7 @@ static bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, return true; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_format_mod_supported); static void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, struct drm_plane_state *state) @@ -1982,4 +2002,5 @@ bool amdgpu_dm_plane_is_video_format(uint32_t format) return false; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_is_video_format); 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 ea2619b507db..911fb2d73e22 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 @@ -28,6 +28,8 @@ #define __AMDGPU_DM_PLANE_H__ #include "dc.h" +#include +#include "amdgpu.h" int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc *crtc, struct dc_cursor_position *position); @@ -65,4 +67,53 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state bool *global_alpha, int *global_alpha_value); 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, + struct dc_tiling_info *tiling_info, + uint64_t modifier); +int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct dc_tiling_info *tiling_info, + const struct dc_plane_dcc_param *dcc, + const struct dc_plane_address *address, + const struct plane_size *plane_size); +bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier); +unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_t modifier); +int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, + unsigned int plane_type, uint64_t **mods); +int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, + const struct dc_plane_cap *plane_cap, + uint32_t *formats, int max_formats); +int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address); +int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address); +bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier); +void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, + struct drm_framebuffer *fb, + int *min_downscale, + int *max_upscale); +#endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 168ad064e7cb..4d89ad8a6df6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_irq_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o +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 @@ -31,3 +32,4 @@ 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 obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_quirks_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_plane_test.o 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 new file mode 100644 index 000000000000..deec75857c0e --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -0,0 +1,1204 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_plane.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + + #include + #include + #include "link_enc_cfg.h" + #include "amdgpu_dm_plane.h" + #include + #include + + +struct dm_test_dcc_cap_ctx { + bool callback_ret; + bool capable; + bool output_independent_64b_blks; + bool called; + struct dc_dcc_surface_param captured_input; +}; + +static struct dm_test_dcc_cap_ctx *dm_test_dcc_ctx; + +static bool dm_test_get_dcc_compression_cap(const struct dc *dc, + const struct dc_dcc_surface_param *input, + struct dc_surface_dcc_cap *output) +{ + if (!dm_test_dcc_ctx) + return false; + + dm_test_dcc_ctx->called = true; + dm_test_dcc_ctx->captured_input = *input; + output->capable = dm_test_dcc_ctx->capable; + output->grph.rgb.independent_64b_blks = dm_test_dcc_ctx->output_independent_64b_blks; + + return dm_test_dcc_ctx->callback_ret; +} + +static void dm_test_init_validate_dcc_inputs(struct amdgpu_device **adev, + struct dc **dc, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address, + struct plane_size *plane_size, + struct kunit *test) +{ + *adev = kunit_kzalloc(test, sizeof(**adev), GFP_KERNEL); + *dc = kunit_kzalloc(test, sizeof(**dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, *adev); + KUNIT_ASSERT_NOT_NULL(test, *dc); + + (*adev)->dm.dc = *dc; + (*adev)->family = AMDGPU_FAMILY_NV; + + tiling_info->gfx9.swizzle = 9; + dcc->enable = 1; + dcc->independent_64b_blks = 1; + plane_size->surface_size.width = 1920; + plane_size->surface_size.height = 1080; + + (void)address; +} + + +/** + * dm_test_plane_is_video_format_known_video() - Verify known video formats. + * @test: KUnit test context. + * + * Verify if NV12, NV21, and P010 are treated as video formats. + */ +static void dm_test_plane_is_video_format_known_video(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_is_video_format(DRM_FORMAT_NV12)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_is_video_format(DRM_FORMAT_NV21)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_is_video_format(DRM_FORMAT_P010)); +} + +/** + * dm_test_fill_blending_defaults() - Verify default blending output values. + * @test: KUnit test context. + * + * Verify if default blending output values are used for opaque alpha and no + * per-pixel blending. + */ +static void dm_test_fill_blending_defaults(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state.alpha = 0xffff; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_FALSE(test, per_pixel_alpha); + KUNIT_EXPECT_TRUE(test, pre_multiplied_alpha); + KUNIT_EXPECT_FALSE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0xff); +} + +/** + * dm_test_fill_blending_premulti_alpha_format() - Verify premultiplied alpha path. + * @test: KUnit test context. + * + * Verify if premultiplied mode enables per-pixel alpha for ARGB8888. + */ +static void dm_test_fill_blending_premulti_alpha_format(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + struct drm_framebuffer fb = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + fb.format = drm_format_info(DRM_FORMAT_ARGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb.format); + + state.fb = &fb; + state.pixel_blend_mode = DRM_MODE_BLEND_PREMULTI; + state.alpha = 0xffff; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_TRUE(test, per_pixel_alpha); + KUNIT_EXPECT_TRUE(test, pre_multiplied_alpha); + KUNIT_EXPECT_FALSE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0xff); +} + +/** + * dm_test_fill_blending_coverage_alpha_format() - Verify coverage mode behavior. + * @test: KUnit test context. + * + * Verify if coverage mode sets per-pixel alpha and disables + * pre_multiplied_alpha for ARGB8888. + */ +static void dm_test_fill_blending_coverage_alpha_format(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + struct drm_framebuffer fb = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + fb.format = drm_format_info(DRM_FORMAT_ARGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb.format); + + state.fb = &fb; + state.pixel_blend_mode = DRM_MODE_BLEND_COVERAGE; + state.alpha = 0xffff; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_TRUE(test, per_pixel_alpha); + KUNIT_EXPECT_FALSE(test, pre_multiplied_alpha); + KUNIT_EXPECT_FALSE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0xff); +} + +/** + * dm_test_fill_blending_global_alpha() - Verify global alpha conversion to 8 bits. + * @test: KUnit test context. + * + * Verify if global alpha is enabled and converted from 16-bit to 8-bit. + */ +static void dm_test_fill_blending_global_alpha(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state.alpha = 0x8000; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_FALSE(test, per_pixel_alpha); + KUNIT_EXPECT_TRUE(test, pre_multiplied_alpha); + KUNIT_EXPECT_TRUE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0x80); +} + +/** + * dm_test_modifier_has_dcc() - Verify helper detects AMD DCC modifiers. + * @test: KUnit test context. + * + * Verify if DCC detection works for linear and AMD DCC modifiers. + */ +static void dm_test_modifier_has_dcc(struct kunit *test) +{ + uint64_t dcc_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(DCC, 1); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_modifier_has_dcc(DRM_FORMAT_MOD_LINEAR)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_modifier_has_dcc(dcc_mod)); +} + +/** + * dm_test_modifier_gfx9_swizzle_mode() - Verify swizzle helper for linear and AMD modifiers. + * @test: KUnit test context. + * + * Verify if swizzle mode decoding works for linear and AMD tiled modifiers. + */ +static void dm_test_modifier_gfx9_swizzle_mode(struct kunit *test) +{ + uint64_t mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_modifier_gfx9_swizzle_mode(DRM_FORMAT_MOD_LINEAR), 0U); + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_modifier_gfx9_swizzle_mode(mod), + (unsigned int)AMD_FMT_MOD_TILE_GFX9_64K_S_X); +} + +/** + * dm_test_get_plane_formats() - Verify plane format counts for key plane types. + * @test: KUnit test context. + * + * Verify if returned format counts match primary, overlay, and cursor planes. + */ +static void dm_test_get_plane_formats(struct kunit *test) +{ + struct drm_plane plane = {0}; + struct dc_plane_cap cap = {0}; + uint32_t formats[32] = {0}; + + plane.type = DRM_PLANE_TYPE_PRIMARY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 14); + + cap.pixel_format_support.nv12 = true; + cap.pixel_format_support.p010 = true; + cap.pixel_format_support.fp16 = true; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, &cap, formats, 32), 20); + + plane.type = DRM_PLANE_TYPE_OVERLAY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 9); + + plane.type = DRM_PLANE_TYPE_CURSOR; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 1); +} + +/** + * dm_test_get_plane_modifiers() - Verify early-return and cursor modifier list. + * @test: KUnit test context. + * + * Verify if modifier list handling works for unsupported families and cursor planes. + */ +static void dm_test_get_plane_modifiers(struct kunit *test) +{ + struct amdgpu_device *adev; + uint64_t *mods = NULL; + + 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), + 0); + KUNIT_ASSERT_NOT_NULL(test, mods); + KUNIT_EXPECT_EQ(test, mods[0], DRM_FORMAT_MOD_LINEAR); + KUNIT_EXPECT_EQ(test, mods[1], DRM_FORMAT_MOD_INVALID); + kfree(mods); +} + +/** + * dm_test_fill_dc_scaling_info() - Verify basic error and success paths. + * @test: KUnit test context. + * + * Verify if scaling info rejects invalid sizes and accepts valid sizes. + */ +static void dm_test_fill_dc_scaling_info(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane_state state = {0}; + struct dc_scaling_info info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + state.src_w = 0; + state.src_h = 100 << 16; + state.crtc_w = 100; + state.crtc_h = 100; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), -EINVAL); + + state.src_w = 100 << 16; + state.src_h = 100 << 16; + state.crtc_w = 100; + state.crtc_h = 100; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), 0); +} + +/** + * dm_test_get_min_max_dc_plane_scaling() - Verify format-specific cap selection and 1->1000 conversion. + * @test: KUnit test context. + * + * Verify if min/max scaling values are correct for NV12 and XRGB8888 formats. + */ +static void dm_test_get_min_max_dc_plane_scaling(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_framebuffer *fb; + int min_downscale = 0; + int max_upscale = 0; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.nv12 = 1; + dc->caps.planes[0].max_downscale_factor.nv12 = 1; + dc->caps.planes[0].max_upscale_factor.argb8888 = 1600; + dc->caps.planes[0].max_downscale_factor.argb8888 = 250; + + fb->format = drm_format_info(DRM_FORMAT_NV12); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + amdgpu_dm_plane_get_min_max_dc_plane_scaling(&adev->ddev, fb, &min_downscale, &max_upscale); + KUNIT_EXPECT_EQ(test, min_downscale, 1000); + KUNIT_EXPECT_EQ(test, max_upscale, 1000); + + fb->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + amdgpu_dm_plane_get_min_max_dc_plane_scaling(&adev->ddev, fb, &min_downscale, &max_upscale); + KUNIT_EXPECT_EQ(test, min_downscale, 250); + 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. + * + * Verify if cursor clipping, hotspot adjustment, and off-screen disable behavior work. + */ +static void dm_test_get_cursor_position(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *amdgpu_crtc; + struct drm_plane plane = {0}; + struct drm_plane_state state = {0}; + struct drm_framebuffer fb = {0}; + struct dc_cursor_position position = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + amdgpu_crtc = kunit_kzalloc(test, sizeof(*amdgpu_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, amdgpu_crtc); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 0); + amdgpu_crtc->max_cursor_width = 64; + amdgpu_crtc->max_cursor_height = 64; + + plane.dev = &adev->ddev; + plane.state = &state; + state.fb = &fb; + state.crtc_x = -5; + state.crtc_y = -7; + state.crtc_w = 32; + state.crtc_h = 32; + + KUNIT_ASSERT_EQ(test, + amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + 0); + KUNIT_EXPECT_TRUE(test, position.enable); + KUNIT_EXPECT_EQ(test, position.x, 0); + KUNIT_EXPECT_EQ(test, position.y, 0); + KUNIT_EXPECT_EQ(test, position.x_hotspot, 5); + KUNIT_EXPECT_EQ(test, position.y_hotspot, 7); + KUNIT_EXPECT_TRUE(test, position.translate_by_source); + + memset(&position, 0, sizeof(position)); + state.crtc_x = -64; + state.crtc_y = 0; + KUNIT_ASSERT_EQ(test, + amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + 0); + KUNIT_EXPECT_FALSE(test, position.enable); +} + +/** + * dm_test_format_mod_supported() - Verify key format/modifier acceptance and rejection paths. + * @test: KUnit test context. + * + * Verify if format-modifier support checks match accepted and rejected cases. + */ +static void dm_test_format_mod_supported(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane plane = {0}; + uint64_t listed_mod; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + plane.dev = &adev->ddev; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR)); + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_INVALID)); + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_VENDOR_AMD)); + + listed_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(DCC, 1); + plane.modifiers = &listed_mod; + plane.modifier_count = 1; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_NV12, listed_mod)); +} + +/** + * dm_test_fill_gfx12_plane_attributes_from_modifiers() - Verify GFX12 DCC mapping path. + * @test: KUnit test context. + * + * Verify if GFX12 modifier parsing enables DCC and sets expected DCC block mode. + */ +static void dm_test_fill_gfx12_plane_attributes_from_modifiers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = false, + }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 1); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( + adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), + 0); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxAddr3); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_128b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_from_modifiers() - Verify basic GFX9 linear modifier path. + * @test: KUnit test context. + * + * Verify if GFX9 linear modifier handling keeps DCC disabled. + */ +static void dm_test_fill_gfx9_plane_attributes_from_modifiers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + afb->base.modifier = DRM_FORMAT_MOD_LINEAR; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers( + adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), + 0); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion9); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.swizzle, 0U); + KUNIT_EXPECT_FALSE(test, dcc.enable); +} + +/** + * dm_test_helper_check_state_viewport_reject() - Verify viewport outside screen rejects state. + * @test: KUnit test context. + * + * Verify if plane state is rejected when the viewport is outside display bounds. + */ +static void dm_test_helper_check_state_viewport_reject(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = 200; + state->crtc_y = 0; + state->crtc_w = 100; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 100; + new_crtc_state->mode.crtc_vdisplay = 100; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_helper_check_state(state, new_crtc_state), -EINVAL); +} + +/** + * dm_test_validate_dcc_disabled_returns_success() - Verify disabled DCC is accepted. + * @test: KUnit test context. + * + * Verify if DCC validation succeeds when DCC is disabled. + */ +static void dm_test_validate_dcc_disabled_returns_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dcc.enable = 0; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + 0); +} + +/** + * dm_test_validate_dcc_video_non_gfx12_fails() - Verify video format restriction on pre-GFX12. + * @test: KUnit test context. + * + * Verify if video format DCC validation fails on non-GFX12 devices. + */ +static void dm_test_validate_dcc_video_non_gfx12_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + adev->family = AMDGPU_FAMILY_NV; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + -EINVAL); +} + +/** + * dm_test_validate_dcc_missing_cap_func_fails() - Verify missing capability callback fails. + * @test: KUnit test context. + * + * Verify if validation fails when DCC capability callback is not provided. + */ +static void dm_test_validate_dcc_missing_cap_func_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = NULL; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + -EINVAL); +} + +/** + * dm_test_validate_dcc_success_and_scan_mapping() - Verify success path and rotation-to-scan mapping. + * @test: KUnit test context. + * + * Verify if DCC validation succeeds and rotation-to-scan mapping is correct. + */ +static void dm_test_validate_dcc_success_and_scan_mapping(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = true, + }; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_90, &tiling_info, &dcc, + &address, &plane_size), + 0); + KUNIT_EXPECT_TRUE(test, ctx.called); + KUNIT_EXPECT_EQ(test, (int)ctx.captured_input.scan, (int)SCAN_DIRECTION_VERTICAL); + KUNIT_EXPECT_EQ(test, (int)ctx.captured_input.format, + (int)SURFACE_PIXEL_FORMAT_GRPH_ARGB8888); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_validate_dcc_independent_64b_mismatch_fails() - Verify 64B compatibility check. + * @test: KUnit test context. + * + * Verify if validation fails when independent_64b_blks values do not match. + */ +static void dm_test_validate_dcc_independent_64b_mismatch_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = true, + }; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dcc.independent_64b_blks = 0; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + -EINVAL); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_add_modifier_appends_value() - Verify one modifier append. + * @test: KUnit test context. + * + * Verify if a modifier is appended and size is updated. + */ +static void dm_test_add_modifier_appends_value(struct kunit *test) +{ + uint64_t size = 0; + uint64_t cap = 2; + uint64_t *mods = kmalloc_array(cap, sizeof(*mods), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + + amdgpu_dm_plane_add_modifier(&mods, &size, &cap, 0x1234ULL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + KUNIT_EXPECT_EQ(test, size, 1ULL); + KUNIT_EXPECT_EQ(test, cap, 2ULL); + KUNIT_EXPECT_EQ(test, mods[0], 0x1234ULL); + + kfree(mods); +} + +/** + * dm_test_add_modifier_grows_capacity() - Verify add triggers growth and preserves old data. + * @test: KUnit test context. + * + * Verify if modifier array growth keeps old data and appends new data. + */ +static void dm_test_add_modifier_grows_capacity(struct kunit *test) +{ + uint64_t size = 1; + uint64_t cap = 1; + uint64_t *mods = kmalloc_array(cap, sizeof(*mods), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + mods[0] = 0xAAULL; + + amdgpu_dm_plane_add_modifier(&mods, &size, &cap, 0xBBULL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + KUNIT_EXPECT_EQ(test, cap, 2ULL); + KUNIT_EXPECT_EQ(test, size, 2ULL); + KUNIT_EXPECT_EQ(test, mods[0], 0xAAULL); + KUNIT_EXPECT_EQ(test, mods[1], 0xBBULL); + + kfree(mods); +} + +/** + * dm_test_add_modifier_noop_when_mods_null() - Verify helper is a no-op on NULL mods list. + * @test: KUnit test context. + * + * Verify if add_modifier does nothing when the modifier list is NULL. + */ +static void dm_test_add_modifier_noop_when_mods_null(struct kunit *test) +{ + uint64_t size = 3; + uint64_t cap = 7; + uint64_t *mods = NULL; + + amdgpu_dm_plane_add_modifier(&mods, &size, &cap, 0x55ULL); + + KUNIT_EXPECT_PTR_EQ(test, mods, NULL); + KUNIT_EXPECT_EQ(test, size, 3ULL); + 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. + * + * Verify if pre-10.3 device fields are copied and existing num_pkrs is kept. + */ +static void dm_test_fill_gfx9_tiling_info_from_device_pre_10_3(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 8; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 1; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 3; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 2, 9); + + tiling_info.gfx9.num_pkrs = 0x5a; + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(adev, &tiling_info); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.pipe_interleave, 256U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.max_compressed_frags, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_rb_per_se, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 0x5aU); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_device_10_3_plus() - Verify num_pkrs update on 10.3+. + * @test: KUnit test context. + * + * Verify if 10.3+ device fields are copied and num_pkrs is updated. + */ +static void dm_test_fill_gfx9_tiling_info_from_device_10_3_plus(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 128; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 6; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(adev, &tiling_info); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.pipe_interleave, 128U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.max_compressed_frags, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_rb_per_se, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 6U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_modifier_linear() - Verify non-AMD modifier keeps device values. + * @test: KUnit test context. + * + * Verify if linear modifier path keeps values from device configuration. + */ +static void dm_test_fill_gfx9_tiling_info_from_modifier_linear(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 8; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 1; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 3; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(adev, &tiling_info, + DRM_FORMAT_MOD_LINEAR); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.pipe_interleave, 256U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.max_compressed_frags, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_rb_per_se, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 3U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_modifier_pre_nv() - Verify AMD modifier updates banks on pre-NV. + * @test: KUnit test context. + * + * Verify if AMD modifier updates pre-NV pipe, engine, and bank fields. + */ +static void dm_test_fill_gfx9_tiling_info_from_modifier_pre_nv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + uint64_t modifier; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_RV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 16; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 1; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 7; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 2, 9); + + tiling_info.gfx9.num_pkrs = 0x5a; + + modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, 7) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, 3) | + AMD_FMT_MOD_SET(PACKERS, 2); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(adev, &tiling_info, modifier); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 32U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 0x5aU); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_modifier_nv() - Verify AMD modifier updates packers on NV+. + * @test: KUnit test context. + * + * Verify if AMD modifier updates NV+ pipe, engine, and packer fields. + */ +static void dm_test_fill_gfx9_tiling_info_from_modifier_nv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + uint64_t modifier; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 9; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 128; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, 6) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, 2) | + AMD_FMT_MOD_SET(PACKERS, 3); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(adev, &tiling_info, modifier); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 32U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 9U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); +} + +static struct kunit_case amdgpu_dm_plane_test_cases[] = { + /* amdgpu_dm_plane_is_video_format() */ + KUNIT_CASE(dm_test_plane_is_video_format_known_video), + /* amdgpu_dm_plane_fill_blending_from_plane_state() */ + KUNIT_CASE(dm_test_fill_blending_defaults), + KUNIT_CASE(dm_test_fill_blending_premulti_alpha_format), + KUNIT_CASE(dm_test_fill_blending_coverage_alpha_format), + KUNIT_CASE(dm_test_fill_blending_global_alpha), + /* amdgpu_dm_plane_modifier_* helpers() */ + KUNIT_CASE(dm_test_modifier_has_dcc), + KUNIT_CASE(dm_test_modifier_gfx9_swizzle_mode), + /* amdgpu_dm_plane_get_plane_formats() */ + KUNIT_CASE(dm_test_get_plane_formats), + /* amdgpu_dm_plane_get_plane_modifiers() */ + KUNIT_CASE(dm_test_get_plane_modifiers), + /* amdgpu_dm_plane_fill_dc_scaling_info() */ + KUNIT_CASE(dm_test_fill_dc_scaling_info), + /* amdgpu_dm_plane_get_min_max_dc_plane_scaling() */ + KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling), + /* amdgpu_dm_plane_fill_plane_buffer_attributes() */ + KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx8), + /* amdgpu_dm_plane_get_cursor_position() */ + KUNIT_CASE(dm_test_get_cursor_position), + /* amdgpu_dm_plane_format_mod_supported() */ + KUNIT_CASE(dm_test_format_mod_supported), + /* amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() */ + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), + /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_from_modifiers), + /* amdgpu_dm_plane_helper_check_state() */ + KUNIT_CASE(dm_test_helper_check_state_viewport_reject), + /* amdgpu_dm_plane_add_modifier() */ + 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), + /* amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier() */ + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_modifier_linear), + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_modifier_pre_nv), + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_modifier_nv), + /* amdgpu_dm_plane_validate_dcc() */ + KUNIT_CASE(dm_test_validate_dcc_disabled_returns_success), + KUNIT_CASE(dm_test_validate_dcc_video_non_gfx12_fails), + KUNIT_CASE(dm_test_validate_dcc_missing_cap_func_fails), + KUNIT_CASE(dm_test_validate_dcc_success_and_scan_mapping), + KUNIT_CASE(dm_test_validate_dcc_independent_64b_mismatch_fails), + {} +}; + +static struct kunit_suite amdgpu_dm_plane_test_suite = { + .name = "amdgpu_dm_plane", + .test_cases = amdgpu_dm_plane_test_cases, +}; + +kunit_test_suite(amdgpu_dm_plane_test_suite); + +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_plane"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 7a561c2b1b63abcffb55f625c0d0adb68ab2961a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 15:42:59 -0600 Subject: drm/amd/display: Simplify boolean checks [WHAT] Use direct boolean in connector and IRQ code paths. This removes redundant comparisons around MST state, IRQ validation, handler removal, and DMUB notification offload without changing behavior. Assisted-by: Copilot:GPT-5 Reviewed-by: Chen-Yu Chen Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 8 ++++---- 2 files changed, 5 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 959c843fb77c..d4720c5576ce 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 @@ -466,7 +466,7 @@ void amdgpu_dm_update_connector_after_detect( struct drm_device *dev = connector->dev; /* MST handled by drm_mst framework */ - if (aconnector->mst_mgr.mst_state == true) + if (aconnector->mst_mgr.mst_state) return; sink = aconnector->dc_link->local_sink; 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 57dd176e4cc1..ffaf2b7bc35d 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 @@ -188,7 +188,7 @@ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); - if (handler_removed == false) { + if (!handler_removed) { /* Not necessarily an error - caller may not * know the context. */ @@ -326,7 +326,7 @@ void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev, unsigned long irq_table_flags; enum dc_irq_source irq_source; - if (false == validate_irq_registration_params(int_params, ih)) + if (!validate_irq_registration_params(int_params, ih)) return DAL_INVALID_IRQ_HANDLER_IDX; handler_data = kzalloc_obj(*handler_data); @@ -392,7 +392,7 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, struct dc_interrupt_params int_params; int i; - if (false == validate_irq_unregistration_params(irq_source, ih)) + if (!validate_irq_unregistration_params(irq_source, ih)) return; memset(&int_params, 0, sizeof(int_params)); @@ -2188,7 +2188,7 @@ static void dm_dmub_outbox1_low_irq(void *interrupt_params) dmub_notification_type_str(notify.type)); continue; } - if (dm->dmub_thread_offload[notify.type] == true) { + if (dm->dmub_thread_offload[notify.type]) { dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, GFP_ATOMIC); if (!dmub_hpd_wrk) { -- cgit v1.2.3 From 418755e47af3d280750592cde008d91f5e110126 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 15:51:20 -0600 Subject: drm/amd/display: Simplify DMUB notify registration [WHAT] Use an early guard for invalid DMUB notify callback registration inputs. This keeps the same accepted and rejected cases while removing the redundant else block. Assisted-by: Copilot:GPT-5 Reviewed-by: Chen-Yu Chen Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 7519219db0f8..2f14614c196c 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 @@ -124,12 +124,12 @@ bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, dmub_notify_interrupt_callback_t callback, bool dmub_int_thread_offload) { - if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) { - adev->dm.dmub_callback[type] = callback; - adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; - } else + if (!callback || type >= ARRAY_SIZE(adev->dm.dmub_thread_offload)) return false; + adev->dm.dmub_callback[type] = callback; + adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; + return true; } EXPORT_IF_KUNIT(dm_register_dmub_notify_callback); -- cgit v1.2.3 From 829769f1cfe88f35125e0fd1186fce5c2aa19a3d Mon Sep 17 00:00:00 2001 From: James Lin Date: Tue, 16 Jun 2026 15:33:20 +0800 Subject: drm/amd/display: scale plane global alpha to 12 bits on DCN 4.2 [why] On DCN 4.2 the global alpha is reported using 12 bits (MPCC_GLOBAL_ALPHA spans bits [0:11]), whereas other ASICs such as DCN 3.1.4 use an 8-bit field (MPCC_GLOBAL_ALPHA spans bits [16:23]). The DRM plane alpha property is 16-bit and amdgpu_dm unconditionally scaled it down by >> 8, which only matches the 8-bit hardware field. On DCN 4.2 this fed a value that was 4 bits too small into the 12-bit field, so the hardware applied the wrong global alpha and the resulting blended output did not match the expected hw * alpha value. [how] Detect DCN 4.2 via amdgpu_ip_version(adev, DCE_HWIP, 0) and scale the 16-bit plane alpha by >> 4 to fill the 12-bit MPCC_GLOBAL_ALPHA field. All other ASICs keep the existing >> 8 behavior for their 8-bit field. Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 62f1ad1ff7b5..35813a39ebcb 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 @@ -137,8 +137,18 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state } if (plane_state->alpha < 0xffff) { + struct amdgpu_device *adev = drm_to_adev(plane_state->plane->dev); *global_alpha = true; - *global_alpha_value = plane_state->alpha >> 8; + /* + * DCN 4.2 uses a 12-bit MPCC_GLOBAL_ALPHA field, while + * other ASICs use an 8-bit field. The DRM plane alpha is + * 16-bit, so scale it down to the width the hardware expects. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0)) + *global_alpha_value = plane_state->alpha >> 4; + else + *global_alpha_value = plane_state->alpha >> 8; + } } EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_blending_from_plane_state); -- cgit v1.2.3 From dcce6246e6c63762d8d0f892f6626d30583b27e9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 16 Jun 2026 09:47:28 -0600 Subject: drm/amd/display: Fix KUnit test crash after global alpha change [WHY] amdgpu_dm_plane_fill_blending_from_plane_state added drm_to_adev() but dm_test_fill_blending_global_alpha did not initialize plane_state->plane, causing a NULL pointer dereference. [HOW] Add an amdgpu_device and drm_plane so the plane->dev dereference is valid in the test. Fixes: 829769f1cfe8 ("drm/amd/display: scale plane global alpha to 12 bits on DCN 4.2") Cc: PingLei.Lin@amd.com Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 7 +++++++ 1 file changed, 7 insertions(+) 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 deec75857c0e..071c28abaa8a 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 @@ -184,12 +184,19 @@ static void dm_test_fill_blending_coverage_alpha_format(struct kunit *test) */ static void dm_test_fill_blending_global_alpha(struct kunit *test) { + struct amdgpu_device *adev; + struct drm_plane plane = {0}; struct drm_plane_state state = { 0 }; bool per_pixel_alpha; bool pre_multiplied_alpha; bool global_alpha; int global_alpha_value; + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + plane.dev = &adev->ddev; + state.plane = &plane; state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; state.alpha = 0x8000; -- cgit v1.2.3 From e14fcf9e5d2b521bec0ea3051058e64add87cb21 Mon Sep 17 00:00:00 2001 From: Peichen Huang Date: Tue, 16 Jun 2026 10:37:02 +0800 Subject: drm/amd/display: correct encoder minimal creation [WHY] shift and mask are not correctly initialized in create_minimal functions. [HOW] Correct initialize necessary variables. Reviewed-by: Cruise Hung Signed-off-by: Peichen Huang Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.c | 4 ++++ drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.h | 2 ++ drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 4 +++- drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 4 +++- 11 files changed, 33 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.c b/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.c index bcb791d74189..f57f3ba68a02 100644 --- a/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.c +++ b/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.c @@ -516,6 +516,8 @@ void dcn31_link_encoder_construct_minimal( struct dc_context *ctx, const struct encoder_feature_support *enc_features, const struct dcn10_link_enc_registers *link_regs, + const struct dcn10_link_enc_shift *link_shift, + const struct dcn10_link_enc_mask *link_mask, enum engine_id eng_id) { struct dcn10_link_encoder *enc10 = &enc20->enc10; @@ -529,6 +531,8 @@ void dcn31_link_encoder_construct_minimal( enc10->base.features = *enc_features; enc10->base.transmitter = TRANSMITTER_UNKNOWN; enc10->link_regs = link_regs; + enc10->link_shift = link_shift; + enc10->link_mask = link_mask; enc10->base.output_signals = SIGNAL_TYPE_DISPLAY_PORT | diff --git a/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.h b/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.h index 3cf587527991..88ab9684e207 100644 --- a/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.h +++ b/drivers/gpu/drm/amd/display/dc/dio/dcn31/dcn31_dio_link_encoder.h @@ -246,6 +246,8 @@ void dcn31_link_encoder_construct_minimal( struct dc_context *ctx, const struct encoder_feature_support *enc_features, const struct dcn10_link_enc_registers *link_regs, + const struct dcn10_link_enc_shift *link_shift, + const struct dcn10_link_enc_mask *link_mask, enum engine_id eng_id); void dcn31_link_encoder_set_dio_phy_mux( 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 02bf6f1f3100..e29efa452c87 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 @@ -1189,7 +1189,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1201,6 +1201,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 d8096f11fb77..f50b3250dcba 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 @@ -1246,7 +1246,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1258,6 +1258,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 ca458f30e45c..8297f2f04c16 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 @@ -1188,7 +1188,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1200,6 +1200,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 560a53de22fc..046566ad1afe 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 @@ -1181,7 +1181,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1193,6 +1193,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 efed9317f3ff..5541b89b1350 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 @@ -1188,7 +1188,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1200,6 +1200,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 079b4f735ab3..053b4380f57e 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 @@ -1168,7 +1168,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1180,6 +1180,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 a293e05f8085..592000cf9250 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 @@ -1175,7 +1175,7 @@ static struct link_encoder *dcn31_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if (((unsigned int)eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if (((unsigned int)eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc_obj(struct dcn20_link_encoder); @@ -1187,6 +1187,8 @@ static struct link_encoder *dcn31_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 44728894dceb..c999db12d0a5 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 @@ -1882,7 +1882,7 @@ static struct link_encoder *dcn42_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if ((unsigned int)(eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if ((unsigned int)(eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); @@ -1894,6 +1894,8 @@ static struct link_encoder *dcn42_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; 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 669bd5eb4c8f..60cbaf4f6fdf 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 @@ -1824,7 +1824,7 @@ static struct link_encoder *dcn42b_link_enc_create_minimal( { struct dcn20_link_encoder *enc20; - if ((unsigned int)(eng_id - ENGINE_ID_DIGA) > ctx->dc->res_pool->res_cap->num_dig_link_enc) + if ((unsigned int)(eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; enc20 = kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); @@ -1836,6 +1836,8 @@ static struct link_encoder *dcn42b_link_enc_create_minimal( ctx, &link_enc_feature, &link_enc_regs[eng_id - ENGINE_ID_DIGA], + &le_shift, + &le_mask, eng_id); return &enc20->enc10.base; -- cgit v1.2.3 From b34e4c1d05e772b7eb574ef2f383516bcdb12105 Mon Sep 17 00:00:00 2001 From: Bhuvanachandra Pinninti Date: Thu, 4 Jun 2026 12:24:00 +0530 Subject: drm/amd/display: Cleaned up headers [why & how] The register spec headers are duplicated in the external asic_reg path and maintaining a local copy is unnecessary. Reviewed-by: Aric Cyr Signed-off-by: Bhuvanachandra Pinninti Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dce/dce_aux.c | 1 + drivers/gpu/drm/amd/display/dc/dce110/dce110_compressor.c | 2 -- drivers/gpu/drm/amd/display/dc/dce110/dce110_mem_input_v.c | 2 -- drivers/gpu/drm/amd/display/dc/dce112/dce112_compressor.c | 8 ++++++-- drivers/gpu/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c | 5 ++++- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c b/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c index fa0d63de1aa4..6cb5e8152cf1 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c @@ -26,6 +26,7 @@ #include "dm_services.h" #include "core_types.h" #include "dce_aux.h" +#include "dce/dce_11_0_d.h" #include "dce/dce_11_0_sh_mask.h" #include "dm_event_log.h" #include "dm_helpers.h" diff --git a/drivers/gpu/drm/amd/display/dc/dce110/dce110_compressor.c b/drivers/gpu/drm/amd/display/dc/dce110/dce110_compressor.c index 9be578ff8c88..140c66081492 100644 --- a/drivers/gpu/drm/amd/display/dc/dce110/dce110_compressor.c +++ b/drivers/gpu/drm/amd/display/dc/dce110/dce110_compressor.c @@ -27,8 +27,6 @@ #include "dce/dce_11_0_d.h" #include "dce/dce_11_0_sh_mask.h" -#include "gmc/gmc_8_2_sh_mask.h" -#include "gmc/gmc_8_2_d.h" #include "include/logger_interface.h" diff --git a/drivers/gpu/drm/amd/display/dc/dce110/dce110_mem_input_v.c b/drivers/gpu/drm/amd/display/dc/dce110/dce110_mem_input_v.c index b265a72eeb70..095869912c09 100644 --- a/drivers/gpu/drm/amd/display/dc/dce110/dce110_mem_input_v.c +++ b/drivers/gpu/drm/amd/display/dc/dce110/dce110_mem_input_v.c @@ -27,8 +27,6 @@ #include "dce/dce_11_0_d.h" #include "dce/dce_11_0_sh_mask.h" /* TODO: this needs to be looked at, used by Stella's workaround*/ -#include "gmc/gmc_8_2_d.h" -#include "gmc/gmc_8_2_sh_mask.h" #include "include/logger_interface.h" #include "inc/dce_calcs.h" diff --git a/drivers/gpu/drm/amd/display/dc/dce112/dce112_compressor.c b/drivers/gpu/drm/amd/display/dc/dce112/dce112_compressor.c index fe97d3946cab..4b273762f07a 100644 --- a/drivers/gpu/drm/amd/display/dc/dce112/dce112_compressor.c +++ b/drivers/gpu/drm/amd/display/dc/dce112/dce112_compressor.c @@ -27,8 +27,12 @@ #include "dce/dce_11_2_d.h" #include "dce/dce_11_2_sh_mask.h" -#include "gmc/gmc_8_1_sh_mask.h" -#include "gmc/gmc_8_1_d.h" + +#ifndef mmGMCON_LPT_TARGET +#define mmGMCON_LPT_TARGET 0x0D53 +#define GMCON_LPT_TARGET__STCTRL_LPT_TARGET__SHIFT 0x00000000 +#define GMCON_LPT_TARGET__STCTRL_LPT_TARGET_MASK 0xffffffffL +#endif #include "include/logger_interface.h" diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c b/drivers/gpu/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c index fabb9da504be..19d148a85f12 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c @@ -35,7 +35,10 @@ #include "dce/dce_8_0_d.h" #include "dce/dce_8_0_sh_mask.h" -#include "smu/smu_7_0_1_d.h" + +#ifndef mmGPIOPAD_A +#define mmGPIOPAD_A 0x0183 +#endif /* * @brief -- cgit v1.2.3 From f3403ab74a29f244cecc7d64b2076ba0021fa737 Mon Sep 17 00:00:00 2001 From: Bhuvanachandra Pinninti Date: Fri, 24 Apr 2026 19:52:25 +0530 Subject: drm/amd/display: Add block sequence support for bandwidth programming operations [why] Bandwidth clock programming build and execution phases were coupled, preventing the HWSS from orchestrating them through block sequencing. [how] Separate clock programming into build and execute phases across latest versions. Build phase populates the clk_mgr internal block sequence array, then registers a single CLK_MGR_UPDATE_CLOCKS HWSS step. Execute phase dispatches the pre-built sequence. Add HWSS operations for clk_mgr_set_max_memclk, hubbub_program_watermarks, hubbub_program_arbiter, and hubbub_program_compbuf_segments. Reviewed-by: Alvin Lee Signed-off-by: Bhuvanachandra Pinninti Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.c | 57 +++++++- .../amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.h | 9 ++ drivers/gpu/drm/amd/display/dc/core/dc.c | 4 +- .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 154 +++++++++++++++++++++ .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 95 +++++++++++++ .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 10 ++ .../drm/amd/display/dc/hwss/dcn401/dcn401_init.c | 2 + drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h | 82 +++++++++++ drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 8 ++ 9 files changed, 416 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.c index 4a60c5f54a04..6ee3da89d058 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.c @@ -10,6 +10,7 @@ #include "dcn31/dcn31_clk_mgr.h" #include "dcn32/dcn32_clk_mgr.h" #include "dcn401/dcn401_clk_mgr.h" +#include "hw_sequencer.h" #include "reg_helper.h" #include "core_types.h" #include "dm_helpers.h" @@ -1085,7 +1086,8 @@ static unsigned int dcn401_build_update_display_clocks_sequence( struct clk_mgr *clk_mgr_base, struct dc_state *context, struct dc_clocks *new_clocks, - bool safe_to_lower) + bool safe_to_lower, + unsigned int num_steps_start) { struct clk_mgr_internal *clk_mgr_internal = TO_CLK_MGR_INTERNAL(clk_mgr_base); struct dcn401_clk_mgr *clk_mgr401 = TO_DCN401_CLK_MGR(clk_mgr_internal); @@ -1100,7 +1102,7 @@ static unsigned int dcn401_build_update_display_clocks_sequence( bool frl_present = false; unsigned int i; - unsigned int num_steps = 0; + unsigned int num_steps = num_steps_start; /* CLK_MGR401_READ_CLOCKS_FROM_DENTIST */ if (clk_mgr_base->clks.dispclk_khz == 0 || @@ -1239,6 +1241,44 @@ static unsigned int dcn401_build_update_display_clocks_sequence( return num_steps; } +/* + * Build-for-BLS functions. + * These build both bandwidth and display clock sequences into the clk_mgr's + * internal block sequence array, then add a single CLK_MGR_UPDATE_CLOCKS step + * to the HWSS block sequence whose executor will call + * execute_clk_mgr_block_sequence to dispatch all accumulated steps. + */ +void dcn401_build_clock_update_for_bls( + struct clk_mgr *clk_mgr_base, + struct dc_state *context, + bool safe_to_lower, + struct block_sequence_state *seq_state) +{ + struct clk_mgr_internal *clk_mgr_internal = TO_CLK_MGR_INTERNAL(clk_mgr_base); + struct dcn401_clk_mgr *clk_mgr401 = TO_DCN401_CLK_MGR(clk_mgr_internal); + unsigned int num_bw_steps; + unsigned int total_steps; + + /* Build bandwidth clocks sequence starting at index 0 */ + num_bw_steps = dcn401_build_update_bandwidth_clocks_sequence(clk_mgr_base, + context, + &context->bw_ctx.bw.dcn.clk, + safe_to_lower); + + /* Build display clocks sequence appended after bandwidth steps */ + total_steps = dcn401_build_update_display_clocks_sequence(clk_mgr_base, + context, + &context->bw_ctx.bw.dcn.clk, + safe_to_lower, + num_bw_steps); + + /* Store total step count for the executor */ + clk_mgr401->num_block_sequence_steps = total_steps; + + /* Add single HWSS step that will execute all clk_mgr block sequence steps */ + hwss_add_clk_mgr_update_clocks(seq_state, clk_mgr_base); +} + static void dcn401_update_clocks(struct clk_mgr *clk_mgr_base, struct dc_state *context, bool safe_to_lower) @@ -1260,7 +1300,8 @@ static void dcn401_update_clocks(struct clk_mgr *clk_mgr_base, num_steps = dcn401_build_update_display_clocks_sequence(clk_mgr_base, context, &context->bw_ctx.bw.dcn.clk, - safe_to_lower); + safe_to_lower, + 0); /* execute sequence */ dcn401_execute_block_sequence(clk_mgr_base, num_steps); @@ -1549,6 +1590,14 @@ unsigned int dcn401_get_max_clock_khz(struct clk_mgr *clk_mgr_base, enum clk_typ return 0; } +static void dcn401_execute_clk_mgr_block_sequence_bls(struct clk_mgr *clk_mgr_base) +{ + struct clk_mgr_internal *clk_mgr_internal = TO_CLK_MGR_INTERNAL(clk_mgr_base); + struct dcn401_clk_mgr *clk_mgr401 = TO_DCN401_CLK_MGR(clk_mgr_internal); + + dcn401_execute_block_sequence(clk_mgr_base, clk_mgr401->num_block_sequence_steps); +} + static struct clk_mgr_funcs dcn401_funcs = { .get_dp_ref_clk_frequency = dce12_get_dp_ref_freq_khz, .get_dtb_ref_clk_frequency = dcn401_get_dtb_ref_freq_khz, @@ -1566,6 +1615,8 @@ static struct clk_mgr_funcs dcn401_funcs = { .get_hard_min_fclk = dcn401_get_hard_min_fclk, .is_dc_mode_present = dcn401_is_dc_mode_present, .get_max_clock_khz = dcn401_get_max_clock_khz, + .build_clock_update_for_bls = dcn401_build_clock_update_for_bls, + .execute_clk_mgr_block_sequence = dcn401_execute_clk_mgr_block_sequence_bls, }; struct clk_mgr_internal *dcn401_clk_mgr_construct( diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.h index 370d2ddd6064..d4cd69a5a8dd 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.h +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.h @@ -102,6 +102,7 @@ struct dcn401_clk_mgr { struct clk_mgr_internal base; struct dcn401_clk_mgr_block_sequence block_sequence[DCN401_CLK_MGR_MAX_SEQUENCE_SIZE]; + unsigned int num_block_sequence_steps; }; void dcn401_init_clocks(struct clk_mgr *clk_mgr_base); @@ -114,4 +115,12 @@ void dcn401_clk_mgr_destroy(struct clk_mgr_internal *clk_mgr); unsigned int dcn401_get_max_clock_khz(struct clk_mgr *clk_mgr_base, enum clk_type clk_type); +struct block_sequence_state; + +void dcn401_build_clock_update_for_bls( + struct clk_mgr *clk_mgr_base, + struct dc_state *context, + bool safe_to_lower, + struct block_sequence_state *seq_state); + #endif /* __DCN401_CLK_MGR_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 3aa95410006a..28339e4b6d67 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -4256,8 +4256,8 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, hwss_add_dc_set_optimized_required(&seq_state, dc, true); } else { - if (get_seamless_boot_stream_count(context) == 0) - hwss_add_prepare_bandwidth(&seq_state, dc, dc->current_state); + if (get_seamless_boot_stream_count(context) == 0 && dc->hwss.prepare_bandwidth_sequence) + dc->hwss.prepare_bandwidth_sequence(dc, dc->current_state, &seq_state); hwss_add_link_set_dpms_on(&seq_state, dc->current_state, dpms_pipe_ctx); } } else if (pipe_ctx->stream->link->wa_flags.blank_stream_on_ocs_change && stream_update->output_color_space 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 c7c32c0a6b50..e47c8cf5d036 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 @@ -37,6 +37,7 @@ #include "dchubbub.h" #include "dccg.h" #include "abm.h" +#include "clk_mgr.h" #include "dcn10/dcn10_hubbub.h" #include "dce/dmub_hw_lock_mgr.h" #include "link_service.h" @@ -1668,6 +1669,21 @@ void hwss_execute_sequence(struct dc *dc, case LINK_SET_DPMS_ON: hwss_link_set_dpms_on(params); break; + case CLK_MGR_SET_MAX_MEMCLK: + hwss_clk_mgr_set_max_memclk(params); + break; + case CLK_MGR_UPDATE_CLOCKS: + hwss_clk_mgr_update_clocks(params); + break; + case HUBBUB_PROGRAM_WATERMARKS: + hwss_hubbub_program_watermarks(params); + break; + case HUBBUB_PROGRAM_ARBITER: + hwss_hubbub_program_arbiter(params); + break; + case HUBBUB_PROGRAM_COMPBUF_SEGMENTS: + hwss_hubbub_program_compbuf_segments(params); + break; default: ASSERT(false); break; @@ -3849,6 +3865,70 @@ void hwss_dsc_set_config_simple(union block_sequence_params *params) dsc->funcs->dsc_set_config(dsc, dsc_cfg, dsc_optc_cfg); } +/* + * Clock manager executor functions + */ +void hwss_clk_mgr_set_max_memclk(union block_sequence_params *params) +{ + struct clk_mgr *clk_mgr = params->clk_mgr_set_max_memclk_params.clk_mgr; + unsigned int memclk_mhz = params->clk_mgr_set_max_memclk_params.memclk_mhz; + + if (clk_mgr && clk_mgr->funcs && clk_mgr->funcs->set_max_memclk) + clk_mgr->funcs->set_max_memclk(clk_mgr, memclk_mhz); +} + +void hwss_clk_mgr_update_clocks(union block_sequence_params *params) +{ + struct clk_mgr *clk_mgr = params->clk_mgr_update_clocks_params.clk_mgr; + + if (clk_mgr && clk_mgr->funcs && clk_mgr->funcs->execute_clk_mgr_block_sequence) + clk_mgr->funcs->execute_clk_mgr_block_sequence(clk_mgr); +} + +/* + * Hubbub executor functions + */ +void hwss_hubbub_program_watermarks(union block_sequence_params *params) +{ + struct dc *dc = params->hubbub_program_watermarks_params.dc; + struct hubbub *hubbub = params->hubbub_program_watermarks_params.hubbub; + union dcn_watermark_set *watermarks = params->hubbub_program_watermarks_params.watermarks; + unsigned int refclk_mhz = params->hubbub_program_watermarks_params.refclk_mhz; + bool safe_to_lower = params->hubbub_program_watermarks_params.safe_to_lower; + + if (hubbub && hubbub->funcs && hubbub->funcs->program_watermarks) { + bool wm_changed = hubbub->funcs->program_watermarks(hubbub, watermarks, refclk_mhz, safe_to_lower); + + if (dc && !safe_to_lower) + dc->optimized_required |= wm_changed; + } +} + +void hwss_hubbub_program_arbiter(union block_sequence_params *params) +{ + struct dc *dc = params->hubbub_program_arbiter_params.dc; + struct hubbub *hubbub = params->hubbub_program_arbiter_params.hubbub; + struct dml2_display_arb_regs *arb_regs = params->hubbub_program_arbiter_params.arb_regs; + bool safe_to_lower = params->hubbub_program_arbiter_params.safe_to_lower; + + if (hubbub && hubbub->funcs && hubbub->funcs->program_arbiter) { + bool arb_changed = hubbub->funcs->program_arbiter(hubbub, arb_regs, safe_to_lower); + + if (dc && !safe_to_lower) + dc->optimized_required |= arb_changed; + } +} + +void hwss_hubbub_program_compbuf_segments(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_program_compbuf_segments_params.hubbub; + unsigned int compbuf_size = params->hubbub_program_compbuf_segments_params.compbuf_size; + bool safe_to_lower = params->hubbub_program_compbuf_segments_params.safe_to_lower; + + if (hubbub && hubbub->funcs && hubbub->funcs->program_compbuf_segments) + hubbub->funcs->program_compbuf_segments(hubbub, compbuf_size, safe_to_lower); +} + void hwss_add_dccg_set_dto_dscclk(struct block_sequence_state *seq_state, struct dccg *dccg, int inst, int num_slices_h) { @@ -4909,6 +4989,9 @@ void hwss_add_hpo_dp_stream_enc_update_dp_info_packets_sdp_line_num(struct block } } +/* + * Clock manager helper functions + */ void hwss_add_hpo_dp_stream_enc_update_dp_info_packets(struct block_sequence_state *seq_state, struct pipe_ctx *pipe_ctx) { @@ -4919,6 +5002,28 @@ void hwss_add_hpo_dp_stream_enc_update_dp_info_packets(struct block_sequence_sta } } +void hwss_add_clk_mgr_set_max_memclk(struct block_sequence_state *seq_state, + struct clk_mgr *clk_mgr, + unsigned int memclk_mhz) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = CLK_MGR_SET_MAX_MEMCLK; + seq_state->steps[*seq_state->num_steps].params.clk_mgr_set_max_memclk_params.clk_mgr = clk_mgr; + seq_state->steps[*seq_state->num_steps].params.clk_mgr_set_max_memclk_params.memclk_mhz = memclk_mhz; + (*seq_state->num_steps)++; + } +} + +void hwss_add_clk_mgr_update_clocks(struct block_sequence_state *seq_state, + struct clk_mgr *clk_mgr) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = CLK_MGR_UPDATE_CLOCKS; + seq_state->steps[*seq_state->num_steps].params.clk_mgr_update_clocks_params.clk_mgr = clk_mgr; + (*seq_state->num_steps)++; + } +} + void hwss_add_stream_enc_update_dp_info_packets_sdp_line_num(struct block_sequence_state *seq_state, struct pipe_ctx *pipe_ctx) { @@ -5022,6 +5127,26 @@ void hwss_add_setup_periodic_interrupt(struct block_sequence_state *seq_state, (*seq_state->num_steps)++; } } +/* + * Hubbub helper functions + */ +void hwss_add_hubbub_program_watermarks(struct block_sequence_state *seq_state, + struct dc *dc, + struct hubbub *hubbub, + union dcn_watermark_set *watermarks, + unsigned int refclk_mhz, + bool safe_to_lower) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PROGRAM_WATERMARKS; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_watermarks_params.dc = dc; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_watermarks_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_watermarks_params.watermarks = watermarks; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_watermarks_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_watermarks_params.safe_to_lower = safe_to_lower; + (*seq_state->num_steps)++; + } +} void hwss_add_dp_trace_source_sequence(struct block_sequence_state *seq_state, struct dc_link *link, @@ -5035,6 +5160,22 @@ void hwss_add_dp_trace_source_sequence(struct block_sequence_state *seq_state, } } +void hwss_add_hubbub_program_arbiter(struct block_sequence_state *seq_state, + struct dc *dc, + struct hubbub *hubbub, + struct dml2_display_arb_regs *arb_regs, + bool safe_to_lower) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PROGRAM_ARBITER; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_arbiter_params.dc = dc; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_arbiter_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_arbiter_params.arb_regs = arb_regs; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_arbiter_params.safe_to_lower = safe_to_lower; + (*seq_state->num_steps)++; + } +} + void hwss_add_set_dmdata_attributes(struct block_sequence_state *seq_state, struct pipe_ctx *pipe_ctx) { @@ -5119,6 +5260,19 @@ void hwss_add_disable_audio_stream(struct block_sequence_state *seq_state, (*seq_state->num_steps)++; } } +void hwss_add_hubbub_program_compbuf_segments(struct block_sequence_state *seq_state, + struct hubbub *hubbub, + unsigned int compbuf_size, + bool safe_to_lower) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PROGRAM_COMPBUF_SEGMENTS; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_compbuf_segments_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_compbuf_segments_params.compbuf_size = compbuf_size; + seq_state->steps[*seq_state->num_steps].params.hubbub_program_compbuf_segments_params.safe_to_lower = safe_to_lower; + (*seq_state->num_steps)++; + } +} void hwss_add_prepare_bandwidth(struct block_sequence_state *seq_state, 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 af83286c6114..0336d118e77e 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 @@ -1496,6 +1496,57 @@ void dcn401_prepare_bandwidth(struct dc *dc, } } +void dcn401_prepare_bandwidth_sequence(struct dc *dc, + struct dc_state *context, + struct block_sequence_state *seq_state) +{ + struct hubbub *hubbub = dc->res_pool->hubbub; + bool p_state_change_support = context->bw_ctx.bw.dcn.clk.p_state_change_support; + unsigned int compbuf_size = 0; + + /* Any transition into P-State support should disable MCLK switching first to avoid hangs */ + if (p_state_change_support) { + dc->optimized_required = true; + context->bw_ctx.bw.dcn.clk.p_state_change_support = false; + } + + if (dc->clk_mgr->dc_mode_softmax_enabled) + if (dc->clk_mgr->clks.dramclk_khz <= (int)dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000 && + context->bw_ctx.bw.dcn.clk.dramclk_khz > (int)dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000) + hwss_add_clk_mgr_set_max_memclk(seq_state, dc->clk_mgr, + dc->clk_mgr->bw_params->clk_table.entries[dc->clk_mgr->bw_params->clk_table.num_entries - 1].memclk_mhz); + + /* Build bandwidth and display clocks back-to-back (SW calc + append BLS steps) */ + if (dc->clk_mgr->funcs->build_clock_update_for_bls) + dc->clk_mgr->funcs->build_clock_update_for_bls( + dc->clk_mgr, context, false, seq_state); + + hwss_add_hubbub_program_watermarks(seq_state, dc, hubbub, + &context->bw_ctx.bw.dcn.watermarks, + dc->res_pool->ref_clocks.dchub_ref_clock_inKhz / 1000, + false); + + if (hubbub->funcs->program_arbiter) + hwss_add_hubbub_program_arbiter(seq_state, dc, hubbub, + &context->bw_ctx.bw.dcn.arb_regs, false); + + if (hubbub->funcs->program_compbuf_segments) { + compbuf_size = context->bw_ctx.bw.dcn.arb_regs.compbuf_size; + dc->optimized_required |= (compbuf_size != dc->current_state->bw_ctx.bw.dcn.arb_regs.compbuf_size); + + hwss_add_hubbub_program_compbuf_segments(seq_state, hubbub, compbuf_size, false); + } + + if (dc->debug.fams2_config.bits.enable) { + dcn401_dmub_hw_control_lock(dc, context, true); + dcn401_fams2_update_config(dc, context, false); + dcn401_dmub_hw_control_lock(dc, context, false); + } + + if (p_state_change_support != context->bw_ctx.bw.dcn.clk.p_state_change_support) + context->bw_ctx.bw.dcn.clk.p_state_change_support = p_state_change_support; +} + void dcn401_optimize_bandwidth( struct dc *dc, struct dc_state *context) @@ -1549,6 +1600,50 @@ void dcn401_optimize_bandwidth( } } +/* + * optimize_bandwidth_sequence is unused for now. It will be used when + * dc_commit_state_no_check is moved into block sequence pattern, similar + * to how commit_planes_do_stream_update_sequence replaces + * commit_planes_do_stream_update. + */ +void dcn401_optimize_bandwidth_sequence(struct dc *dc, + struct dc_state *context, + struct block_sequence_state *seq_state) +{ + struct hubbub *hubbub = dc->res_pool->hubbub; + + /* enable fams2 if needed */ + if (dc->debug.fams2_config.bits.enable) { + dcn401_dmub_hw_control_lock(dc, context, true); + dcn401_fams2_update_config(dc, context, true); + dcn401_dmub_hw_control_lock(dc, context, false); + } + + hwss_add_hubbub_program_watermarks(seq_state, dc, hubbub, + &context->bw_ctx.bw.dcn.watermarks, + dc->res_pool->ref_clocks.dchub_ref_clock_inKhz / 1000, + true); + + if (hubbub->funcs->program_arbiter) + hwss_add_hubbub_program_arbiter(seq_state, dc, hubbub, + &context->bw_ctx.bw.dcn.arb_regs, true); + + if (dc->clk_mgr->dc_mode_softmax_enabled) + if (dc->clk_mgr->clks.dramclk_khz > (int)dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000 && + context->bw_ctx.bw.dcn.clk.dramclk_khz <= (int)dc->clk_mgr->bw_params->dc_mode_softmax_memclk * 1000) + hwss_add_clk_mgr_set_max_memclk(seq_state, dc->clk_mgr, + dc->clk_mgr->bw_params->dc_mode_softmax_memclk); + + if (hubbub->funcs->program_compbuf_segments) + hwss_add_hubbub_program_compbuf_segments(seq_state, hubbub, + context->bw_ctx.bw.dcn.arb_regs.compbuf_size, true); + + /* Build bandwidth and display clocks (SW calc + append BLS steps) */ + if (dc->clk_mgr->funcs->build_clock_update_for_bls) + dc->clk_mgr->funcs->build_clock_update_for_bls( + dc->clk_mgr, context, true, seq_state); +} + void dcn401_dmub_hw_control_lock(struct dc *dc, struct dc_state *context, bool lock) 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 2afeafc902c7..a760050eea8c 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 @@ -70,10 +70,20 @@ void dcn401_wait_for_dcc_meta_propagation(const struct dc *dc, void dcn401_prepare_bandwidth(struct dc *dc, struct dc_state *context); +struct block_sequence_state; + +void dcn401_prepare_bandwidth_sequence(struct dc *dc, + struct dc_state *context, + struct block_sequence_state *seq_state); + void dcn401_optimize_bandwidth( struct dc *dc, struct dc_state *context); +void dcn401_optimize_bandwidth_sequence(struct dc *dc, + struct dc_state *context, + struct block_sequence_state *seq_state); + void dcn401_dmub_hw_control_lock(struct dc *dc, struct dc_state *context, bool lock); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c index 33b2cf344f1e..f206e221f926 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c @@ -44,7 +44,9 @@ static const struct hw_sequencer_funcs dcn401_funcs = { .interdependent_update_lock = dcn401_interdependent_update_lock, .cursor_lock = dcn10_cursor_lock, .prepare_bandwidth = dcn401_prepare_bandwidth, + .prepare_bandwidth_sequence = dcn401_prepare_bandwidth_sequence, .optimize_bandwidth = dcn401_optimize_bandwidth, + .optimize_bandwidth_sequence = dcn401_optimize_bandwidth_sequence, .update_bandwidth = dcn401_update_bandwidth, .set_drr = dcn10_set_drr, .get_position = dcn10_get_position, 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 dfb278a9fc3e..65df8002d3d7 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h @@ -894,6 +894,36 @@ struct disable_audio_stream_params { struct pipe_ctx *pipe_ctx; }; +struct clk_mgr_set_max_memclk_params { + struct clk_mgr *clk_mgr; + unsigned int memclk_mhz; +}; + +struct clk_mgr_update_clocks_params { + struct clk_mgr *clk_mgr; +}; + +struct hubbub_program_watermarks_params { + struct dc *dc; + struct hubbub *hubbub; + union dcn_watermark_set *watermarks; + unsigned int refclk_mhz; + bool safe_to_lower; +}; + +struct hubbub_program_arbiter_params { + struct dc *dc; + struct hubbub *hubbub; + struct dml2_display_arb_regs *arb_regs; + bool safe_to_lower; +}; + +struct hubbub_program_compbuf_segments_params { + struct hubbub *hubbub; + unsigned int compbuf_size; + bool safe_to_lower; +}; + struct prepare_bandwidth_params { struct dc *dc; struct dc_state *context; @@ -1057,6 +1087,11 @@ union block_sequence_params { struct disable_audio_stream_params disable_audio_stream_params; struct prepare_bandwidth_params prepare_bandwidth_params; struct link_set_dpms_on_params link_set_dpms_on_params; + struct clk_mgr_set_max_memclk_params clk_mgr_set_max_memclk_params; + struct clk_mgr_update_clocks_params clk_mgr_update_clocks_params; + struct hubbub_program_watermarks_params hubbub_program_watermarks_params; + struct hubbub_program_arbiter_params hubbub_program_arbiter_params; + struct hubbub_program_compbuf_segments_params hubbub_program_compbuf_segments_params; }; enum block_sequence_func { @@ -1209,6 +1244,11 @@ enum block_sequence_func { DISABLE_AUDIO_STREAM, PREPARE_BANDWIDTH, LINK_SET_DPMS_ON, + CLK_MGR_SET_MAX_MEMCLK, + CLK_MGR_UPDATE_CLOCKS, + HUBBUB_PROGRAM_WATERMARKS, + HUBBUB_PROGRAM_ARBITER, + HUBBUB_PROGRAM_COMPBUF_SEGMENTS, /* This must be the last value in this enum, add new ones above */ HWSS_BLOCK_SEQUENCE_FUNC_COUNT }; @@ -1316,8 +1356,14 @@ struct hw_sequencer_funcs { /* Bandwidth Related */ void (*prepare_bandwidth)(struct dc *dc, struct dc_state *context); + void (*prepare_bandwidth_sequence)(struct dc *dc, + struct dc_state *context, + struct block_sequence_state *seq_state); bool (*update_bandwidth)(struct dc *dc, struct dc_state *context); void (*optimize_bandwidth)(struct dc *dc, struct dc_state *context); + void (*optimize_bandwidth_sequence)(struct dc *dc, + struct dc_state *context, + struct block_sequence_state *seq_state); /* Infopacket Related */ void (*set_avmute)(struct pipe_ctx *pipe_ctx, bool enable); @@ -2475,4 +2521,40 @@ void hwss_add_link_set_dpms_on(struct block_sequence_state *seq_state, struct dc_state *state, struct pipe_ctx *pipe_ctx); +/* Clock manager BLS executor functions */ +void hwss_clk_mgr_set_max_memclk(union block_sequence_params *params); +void hwss_clk_mgr_update_clocks(union block_sequence_params *params); + +void hwss_hubbub_program_watermarks(union block_sequence_params *params); + +void hwss_hubbub_program_arbiter(union block_sequence_params *params); + +void hwss_hubbub_program_compbuf_segments(union block_sequence_params *params); + +/* Clock manager BLS add-helper functions */ +void hwss_add_clk_mgr_set_max_memclk(struct block_sequence_state *seq_state, + struct clk_mgr *clk_mgr, + unsigned int memclk_mhz); + +void hwss_add_clk_mgr_update_clocks(struct block_sequence_state *seq_state, + struct clk_mgr *clk_mgr); + +void hwss_add_hubbub_program_watermarks(struct block_sequence_state *seq_state, + struct dc *dc, + struct hubbub *hubbub, + union dcn_watermark_set *watermarks, + unsigned int refclk_mhz, + bool safe_to_lower); + +void hwss_add_hubbub_program_arbiter(struct block_sequence_state *seq_state, + struct dc *dc, + struct hubbub *hubbub, + struct dml2_display_arb_regs *arb_regs, + bool safe_to_lower); + +void hwss_add_hubbub_program_compbuf_segments(struct block_sequence_state *seq_state, + struct hubbub *hubbub, + unsigned int compbuf_size, + bool safe_to_lower); + #endif /* __DC_HW_SEQUENCER_H__ */ 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 69c4a49a40fc..68dc2d4ba7ca 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 @@ -320,6 +320,8 @@ struct clk_states { uint32_t dprefclk_khz; }; +struct block_sequence_state; + struct clk_mgr_funcs { /* * This function should set new clocks based on the input "safe_to_lower". @@ -409,6 +411,12 @@ struct clk_mgr_funcs { void (*get_requested_memory_qos)( struct clk_mgr *clk_mgr, struct dc_requested_memory_qos *qos); + + 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); + + void (*execute_clk_mgr_block_sequence)(struct clk_mgr *clk_mgr); }; struct clk_mgr { -- cgit v1.2.3 From 8cbe3648aa868c2c2d557073cf61526d1177d756 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 16 Jun 2026 11:29:03 -0400 Subject: drm/amd/display: clamp DMUB AUX reply length to payload buffer [Why] amdgpu_dm_process_dmub_aux_transfer_sync() copies p_notify->aux_reply.length bytes into payload->data without clamping. payload->data is typically a 16-byte DPCD scratch buffer, while aux_reply.length is echoed from the sink via the DMUB ring. While this is clamped by DMUB it's prudent to ensure we validate this in the driver as well. [How] Clamp the copy to sizeof(aux_reply.data), the scratch buffer the reply was read into, and use that for both the memcpy and the return value. For regular transfers additionally clamp to payload->length to cover callers whose destination buffer is smaller than 16 bytes. The write-status-update retry path (dce_aux_transfer_with_retries) deliberately zeroes payload->length while still expecting the partial-write status byte, so that bound is skipped in that case to avoid dropping the reply. Also guard against a NULL payload->data. Fixes: 81927e2808be ("drm/amd/display: Support for DMUB AUX") Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) 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 2f14614c196c..0aa99d1a542f 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 @@ -797,12 +797,26 @@ int amdgpu_dm_process_dmub_aux_transfer_sync( payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF; /*write req may receive a byte indicating partially written number as well*/ - if (p_notify->aux_reply.length) - memcpy(payload->data, p_notify->aux_reply.data, - p_notify->aux_reply.length); + if (p_notify->aux_reply.length && payload->data) { + /* Bound the reply to the scratch buffer it was read into. */ + ret = min((uint32_t)p_notify->aux_reply.length, + (uint32_t)sizeof(p_notify->aux_reply.data)); + + /* + * During a write-status-update retry the caller zeroes + * payload->length while still expecting the partial-write + * status byte in payload->data (see dce_aux_transfer_with_retries), + * so only clamp to payload->length for regular transfers. + */ + if (!payload->write_status_update) + ret = min(ret, payload->length); + + memcpy(payload->data, p_notify->aux_reply.data, ret); + } else { + /* success */ + ret = p_notify->aux_reply.length; + } - /* success */ - ret = p_notify->aux_reply.length; *operation_result = p_notify->result; out: reinit_completion(&adev->dm.dmub_aux_transfer_done); -- cgit v1.2.3 From d0a775e5d70b376696245a14c09e3aa6dde0023a Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 16 Jun 2026 12:17:45 -0400 Subject: drm/amd/display: guard against overflow in HDCP message dump [Why] mod_hdcp_dump_binary_message() computed target_size (a uint32_t) as roughly byte_size * msg_size and gated the whole write on buf_size >= target_size. A large msg_size can overflow target_size, wrapping it to a small value that passes the check while the loop still writes byte_size * msg_size bytes into buf. All current callers pass small constants so this is not reachable today, but the unchecked arithmetic should be hardened. [How] Drop the overflow-prone target_size precomputation and instead bounds-check the output position on every iteration, stopping once the next entry would not leave room for the trailing terminator. This cannot overflow and, for oversized messages, dumps as much as fits rather than printing nothing. Fixes: 4c283fdac08a ("drm/amd/display: Add HDCP module") Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/modules/hdcp/hdcp_log.c | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c index 1164fd96b714..f0f8e280ed30 100644 --- a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp_log.c @@ -33,22 +33,28 @@ void mod_hdcp_dump_binary_message(uint8_t *msg, uint32_t msg_size, byte_size = 3, newline_size = 1, terminator_size = 1; - uint32_t line_count = msg_size / bytes_per_line, - trailing_bytes = msg_size % bytes_per_line; - uint32_t target_size = (byte_size * bytes_per_line + newline_size) * line_count + - byte_size * trailing_bytes + newline_size + terminator_size; uint32_t buf_pos = 0; uint32_t i = 0; - if (buf_size >= target_size) { - for (i = 0; i < msg_size; i++) { - if (i % bytes_per_line == 0) - buf[buf_pos++] = '\n'; - sprintf((char *)&buf[buf_pos], "%02X ", msg[i]); - buf_pos += byte_size; - } - buf[buf_pos++] = '\0'; + /* Need room for at least the terminator. */ + if (buf_size < terminator_size) + return; + + for (i = 0; i < msg_size; i++) { + uint32_t needed = byte_size + terminator_size; + + if (i % bytes_per_line == 0) + needed += newline_size; + + if (buf_pos + needed > buf_size) + break; + + if (i % bytes_per_line == 0) + buf[buf_pos++] = '\n'; + sprintf((char *)&buf[buf_pos], "%02X ", msg[i]); + buf_pos += byte_size; } + buf[buf_pos++] = '\0'; } void mod_hdcp_log_ddc_trace(struct mod_hdcp *hdcp) -- cgit v1.2.3 From e5316b76d31c93d9204e987a85bdbf1dce08a9cf Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Wed, 17 Jun 2026 09:55:24 -0400 Subject: drm/amd/display: Revert "Add Debug Option To Enable Per-DPM De-rate Usage" Revert due to regression. This reverts commit e82936e8dad0ccbe067323fe7c4e1ae4593104f3. Reviewed-by: Martin Leung Signed-off-by: Austin Zheng Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 -- .../dcn401/dcn401_soc_and_ip_translator.c | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index b323f7826451..92f84277c522 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1289,8 +1289,6 @@ struct dc_debug_options { bool enable_replay_esd_recovery; uint8_t iommu_mismatch_temp_wka; bool disable_dynamic_expansion_for_test_pattern; - uint32_t dml21_custom_derate_num_dpms; - uint32_t dml21_custom_derate_at_dpm[DML2_MAX_NUM_DPM_LVL]; }; diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c index 0c8e652c3532..89f7ccd7f81f 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c @@ -269,22 +269,6 @@ void dcn401_update_soc_bb_with_values_from_software_policy(struct dml2_soc_bb *s if (dc->bb_overrides.sr_enter_plus_exit_z8_time_ns) soc_bb->power_management_parameters.z8_stutter_enter_plus_exit_latency_us = dc->bb_overrides.sr_enter_plus_exit_z8_time_ns / 1000.0; - - /* Override per-dpm derates based on a custom derate table. - * Global derate value will be used for derates that aren't populated - * 3 derates for a single DPM level: - * bits 0-7: dram_derate_percent_pixel - * bits 8-15: fclk_derate_percent - * bits 16-23: dcfclk_derate_percent - */ - for (unsigned int i = 0; i < dc->debug.dml21_custom_derate_num_dpms; i++) { - soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[i] - = dc->debug.dml21_custom_derate_at_dpm[i] & 0xFF; - soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[i] - = (dc->debug.dml21_custom_derate_at_dpm[i] >> 8) & 0xFF; - soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[i] - = (dc->debug.dml21_custom_derate_at_dpm[i] >> 16) & 0xFF; - } } static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) -- cgit v1.2.3 From 682710244fa3176f642eecd4c1a27e69be2e3a7f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 17:10:40 -0600 Subject: drm/amd/display: Add KUnit test for amdgpu_dm_wb [WHAT] Add KUnit test with DRM mock for amdgpu_dm_wb_connector_init(). Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 70 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) 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 058d478a073d..0bf82e46f773 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 @@ -216,3 +216,4 @@ int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_wb_connector_init); 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 b8ad4b87163a..f9a839c10bf4 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 @@ -16,6 +16,9 @@ #include #include +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" @@ -68,6 +71,23 @@ static struct drm_connector_state *alloc_test_conn_state(struct kunit *test, return conn_state; } +static struct amdgpu_device *alloc_test_adev(struct kunit *test) +{ + 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(struct amdgpu_device), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + return drm_to_adev(drm); +} + /* Tests for amdgpu_dm_wb_encoder_atomic_check */ /** @@ -310,6 +330,54 @@ static void dm_test_wb_get_modes_bounded_by_max(struct kunit *test) } } +/* Tests for amdgpu_dm_wb_connector_init using DRM mock */ + +/** + * dm_test_wb_connector_init_success - Verify writeback connector initialization + * @test: KUnit test context + * + * Uses a DRM mock device embedded in struct amdgpu_device to verify that + * amdgpu_dm_wb_connector_init() initializes the writeback connector, stores + * the DC link, installs connector state through reset, and wires the expected + * DRM callbacks. + */ +static void dm_test_wb_connector_init_success(struct kunit *test) +{ + struct amdgpu_dm_wb_connector *wbcon; + struct amdgpu_display_manager *dm; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + int ret; + + adev = alloc_test_adev(test); + adev->mode_info.num_crtc = 1; + dm = &adev->dm; + dm->adev = adev; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + dc->links[0] = link; + dm->dc = dc; + + wbcon = kunit_kzalloc(test, sizeof(*wbcon), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wbcon); + + ret = amdgpu_dm_wb_connector_init(dm, wbcon, 0); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_PTR_EQ(test, wbcon->link, link); + KUNIT_EXPECT_TRUE(test, wbcon->base.base.funcs != NULL); + KUNIT_EXPECT_TRUE(test, wbcon->base.base.helper_private != NULL); + KUNIT_EXPECT_TRUE(test, wbcon->base.base.state != NULL); + KUNIT_EXPECT_TRUE(test, wbcon->base.encoder.funcs != NULL); + KUNIT_EXPECT_EQ(test, wbcon->base.encoder.possible_crtcs, 0x1); +} + static struct kunit_case dm_wb_test_cases[] = { /* amdgpu_dm_wb_encoder_atomic_check */ KUNIT_CASE(dm_test_wb_atomic_check_no_job), @@ -322,6 +390,8 @@ static struct kunit_case dm_wb_test_cases[] = { /* amdgpu_dm_wb_connector_get_modes */ KUNIT_CASE(dm_test_wb_get_modes_returns_modes), KUNIT_CASE(dm_test_wb_get_modes_bounded_by_max), + /* amdgpu_dm_wb_connector_init */ + KUNIT_CASE(dm_test_wb_connector_init_success), {} }; -- cgit v1.2.3 From d99024d7d243aecec42b98ac000e720499cb3e92 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 18:54:53 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_replay [WHAT] Add KUnit coverage for amdgpu_dm_set_replay_caps(), amdgpu_dm_link_setup_replay(), and amdgpu_dm_replay_set_event() including happy-path tests that exercise the configuration logic, coasting vtotal calculations, and early-return when replay events are already in the desired state. Assisted-by: Copilot:Claude-Opus-4.6 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c | 3 + .../amdgpu_dm/tests/amdgpu_dm_replay_test.c | 437 ++++++++++++++++++++- 2 files changed, 436 insertions(+), 4 deletions(-) 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 f3cea2aba901..42e17119461d 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 @@ -128,6 +128,7 @@ bool amdgpu_dm_set_replay_caps(struct dc_link *link, struct amdgpu_dm_connector return true; } +EXPORT_IF_KUNIT(amdgpu_dm_set_replay_caps); /* * amdgpu_dm_link_setup_replay() - config replay settings @@ -166,6 +167,7 @@ bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream, static_coasting_vtotal); return true; } +EXPORT_IF_KUNIT(amdgpu_dm_link_setup_replay); /* * amdgpu_dm_replay_set_event() - set or clear replay event for a stream @@ -205,3 +207,4 @@ bool amdgpu_dm_replay_set_event(struct amdgpu_display_manager *dm, return mod_power_set_replay_event(dm->power_module, stream, set_event, event, wait_for_disable); } +EXPORT_IF_KUNIT(amdgpu_dm_replay_set_event); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c index 28ff8bbcc0f7..68f2f4d70407 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c @@ -8,12 +8,12 @@ #include #include "dc.h" +#include "dc_dmub_srv.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" - -/* Extern declaration for the function under test */ -extern bool amdgpu_dm_link_supports_replay(struct dc_link *link, - struct amdgpu_dm_connector *aconnector); +#include "amdgpu_dm_replay.h" +#include "modules/power/power_helpers.h" +#include "dmub/dmub_srv.h" /* * Helper: allocate a dc_link, amdgpu_dm_connector, and dm_connector_state @@ -23,6 +23,9 @@ struct replay_test_ctx { struct dc_link *link; struct amdgpu_dm_connector *aconnector; struct dm_connector_state *dm_state; + struct dc *dc; + struct dc_context *dc_ctx; + struct dc_stream_state *stream; }; static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) @@ -41,8 +44,21 @@ static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc); + + ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); + + ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->stream); + /* Wire connector state so to_dm_connector_state() works */ ctx->aconnector->base.state = &ctx->dm_state->base; + ctx->link->ctx = ctx->dc_ctx; + ctx->dc_ctx->dc = ctx->dc; + ctx->dc->ctx = ctx->dc_ctx; + ctx->stream->link = ctx->link; return ctx; } @@ -55,6 +71,7 @@ static void set_all_replay_caps(struct replay_test_ctx *ctx) { ctx->dm_state->freesync_capable = true; ctx->aconnector->vsdb_info.replay_mode = true; + ctx->link->connector_signal = SIGNAL_TYPE_EDP; ctx->link->dpcd_caps.edp_rev = EDP_REVISION_13; ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; @@ -181,7 +198,398 @@ static void dm_test_replay_both_deviations_zero(struct kunit *test) /* End of tests for amdgpu_dm_link_supports_replay() */ +/* Tests for amdgpu_dm_set_replay_caps() */ + +/** + * dm_test_replay_set_caps_already_supported - Verify cached Replay support + * @test: KUnit test context + * + * When replay_supported is already set, amdgpu_dm_set_replay_caps() should + * return true without revalidating the link capabilities. + */ +static void dm_test_replay_set_caps_already_supported(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + ctx->link->replay_settings.config.replay_supported = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_non_embedded_signal - Verify non-eDP rejection + * @test: KUnit test context + * + * When the link signal is not embedded, amdgpu_dm_set_replay_caps() should + * reject Replay even if the sink capability fields are otherwise valid. + */ +static void dm_test_replay_set_caps_non_embedded_signal(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_disallowed_by_panel - Verify panel policy rejection + * @test: KUnit test context + * + * When the panel configuration disallows Replay, amdgpu_dm_set_replay_caps() + * should return false before accepting the capability set. + */ +static void dm_test_replay_set_caps_disallowed_by_panel(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->panel_config.psr.disallow_replay = true; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_link_not_supported - Verify capability rejection + * @test: KUnit test context + * + * When amdgpu_dm_link_supports_replay() rejects the link, the higher-level + * Replay setup helper should also return false. + */ +static void dm_test_replay_set_caps_link_not_supported(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->dm_state->freesync_capable = false; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_missing_dmub_srv - Verify missing DMUB rejection + * @test: KUnit test context + * + * When the link and connector support Replay but no DMUB service is available, + * amdgpu_dm_set_replay_caps() should return false. + */ +static void dm_test_replay_set_caps_missing_dmub_srv(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_success - Verify successful Replay configuration + * @test: KUnit test context + * + * When all prerequisites are met (embedded signal, panel allows replay, link + * supports replay, DMUB present with replay support), amdgpu_dm_set_replay_caps() + * should configure the link replay settings and return true. + */ +static void dm_test_replay_set_caps_success(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct dc_dmub_srv *dmub_srv; + struct dmub_srv *dmub; + + set_all_replay_caps(ctx); + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dmub_srv); + + dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dmub); + + dmub->feature_caps.replay_supported = 1; + dmub_srv->dmub = dmub; + ctx->dc_ctx->dmub_srv = dmub_srv; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); + KUNIT_EXPECT_TRUE(test, ctx->link->replay_settings.config.replay_supported); +} + +/* Tests for amdgpu_dm_link_setup_replay() */ + +/** + * dm_test_replay_link_setup_null_stream - Verify NULL stream rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when no stream is provided. + */ +static void dm_test_replay_link_setup_null_stream(struct kunit *test) +{ + struct mod_vrr_params vrr_params = { 0 }; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(NULL, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_null_link - Verify NULL stream link rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when the stream has no + * associated link. + */ +static void dm_test_replay_link_setup_null_link(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + ctx->stream->link = NULL; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_null_vrr_params - Verify NULL VRR params rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when VRR parameters are + * not supplied. + */ +static void dm_test_replay_link_setup_null_vrr_params(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(ctx->stream, NULL)); +} + +/** + * dm_test_replay_link_setup_not_supported - Verify unsupported Replay rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when Replay is not marked + * supported on the link configuration. + */ +static void dm_test_replay_link_setup_not_supported(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_already_enabled - Verify enabled Replay success + * @test: KUnit test context + * + * When Replay is already enabled, amdgpu_dm_link_setup_replay() should return + * true without recalculating coasting vtotal state. + */ +static void dm_test_replay_link_setup_already_enabled(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + ctx->link->replay_settings.config.replay_supported = true; + ctx->link->replay_settings.replay_feature_enabled = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_success - Verify coasting vtotal configuration + * @test: KUnit test context + * + * When Replay is supported but not yet enabled, amdgpu_dm_link_setup_replay() + * should calculate the link-off frame count and set the coasting vtotal values, + * then return true. + */ +static void dm_test_replay_link_setup_success(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + ctx->link->replay_settings.config.replay_supported = true; + ctx->link->replay_settings.config.replay_version = DC_FREESYNC_REPLAY; + + /* Set timing so calculate_replay_link_off_frame_count computes */ + ctx->stream->timing.v_total = 1125; + ctx->stream->timing.h_total = 2200; + ctx->stream->timing.pix_clk_100hz = 1485000; + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 4; + ctx->link->dpcd_caps.pr_info.max_deviation_line = 10; + + /* min_refresh_in_uhz = 0 makes calc return v_total directly */ + vrr_params.min_refresh_in_uhz = 0; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); + + /* Verify coasting vtotal was set */ + KUNIT_EXPECT_EQ(test, + ctx->link->replay_settings.coasting_vtotal_table[PR_COASTING_TYPE_NOM], + (uint32_t)1125); + KUNIT_EXPECT_EQ(test, + ctx->link->replay_settings.coasting_vtotal_table[PR_COASTING_TYPE_STATIC], + (uint32_t)1125); + + /* Verify link_off_frame_count was calculated: 2200*10/(4*1125) = 4 */ + KUNIT_EXPECT_EQ(test, + ctx->link->replay_settings.link_off_frame_count, + (uint32_t)4); +} + +/* Tests for amdgpu_dm_replay_set_event() */ + +/** + * dm_test_replay_set_event_null_stream - Verify NULL stream rejection + * @test: KUnit test context + * + * amdgpu_dm_replay_set_event() should return false when no stream is provided. + */ +static void dm_test_replay_set_event_null_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, NULL, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_null_link - Verify NULL stream link rejection + * @test: KUnit test context + * + * amdgpu_dm_replay_set_event() should return false when the stream has no + * associated link. + */ +static void dm_test_replay_set_event_null_link(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + ctx->stream->link = NULL; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_feature_disabled - Verify disabled Replay rejection + * @test: KUnit test context + * + * amdgpu_dm_replay_set_event() should return false when Replay is not enabled + * on the stream link. + */ +static void dm_test_replay_set_event_feature_disabled(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_missing_power_module - Verify missing power rejection + * @test: KUnit test context + * + * When Replay is enabled but no power module is available, the event helper + * should return false after failing to read the current Replay events. + */ +static void dm_test_replay_set_event_missing_power_module(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + ctx->link->replay_settings.replay_feature_enabled = true; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_already_set - Verify no-op when event already active + * @test: KUnit test context + * + * When the requested event is already in the desired state, the function should + * return true without calling mod_power_set_replay_event(). + */ +static void dm_test_replay_set_event_already_set(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct core_power *core_power; + struct power_entity *map; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power); + + map = kunit_kzalloc(test, sizeof(*map), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, map); + + /* Wire the power module so mod_power_get_replay_event() succeeds */ + map->stream = ctx->stream; + map->replay_events = replay_event_vsync; + core_power->map = map; + core_power->num_entities = 1; + dm->power_module = &core_power->mod_public; + + ctx->link->replay_settings.replay_feature_enabled = true; + + /* Event already set — should return true without calling set */ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_already_clear - Verify no-op when event already cleared + * @test: KUnit test context + * + * When clearing an event that is not currently active, the function should + * return true without calling mod_power_set_replay_event(). + */ +static void dm_test_replay_set_event_already_clear(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct core_power *core_power; + struct power_entity *map; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power); + + map = kunit_kzalloc(test, sizeof(*map), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, map); + + /* Wire the power module — replay_events has NO vsync bit */ + map->stream = ctx->stream; + map->replay_events = 0; + core_power->map = map; + core_power->num_entities = 1; + dm->power_module = &core_power->mod_public; + + ctx->link->replay_settings.replay_feature_enabled = true; + + /* Clearing an event that's already clear — should return true */ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, false, + replay_event_vsync, false)); +} + static struct kunit_case dm_replay_test_cases[] = { + /* amdgpu_dm_link_supports_replay */ KUNIT_CASE(dm_test_replay_supports_all_caps), KUNIT_CASE(dm_test_replay_no_freesync), KUNIT_CASE(dm_test_replay_no_vsdb_replay_mode), @@ -191,6 +599,27 @@ static struct kunit_case dm_replay_test_cases[] = { KUNIT_CASE(dm_test_replay_zero_pixel_deviation), KUNIT_CASE(dm_test_replay_zero_max_deviation_line), KUNIT_CASE(dm_test_replay_both_deviations_zero), + /* amdgpu_dm_set_replay_caps */ + KUNIT_CASE(dm_test_replay_set_caps_already_supported), + KUNIT_CASE(dm_test_replay_set_caps_non_embedded_signal), + KUNIT_CASE(dm_test_replay_set_caps_disallowed_by_panel), + KUNIT_CASE(dm_test_replay_set_caps_link_not_supported), + KUNIT_CASE(dm_test_replay_set_caps_missing_dmub_srv), + KUNIT_CASE(dm_test_replay_set_caps_success), + /* amdgpu_dm_link_setup_replay */ + KUNIT_CASE(dm_test_replay_link_setup_null_stream), + KUNIT_CASE(dm_test_replay_link_setup_null_link), + KUNIT_CASE(dm_test_replay_link_setup_null_vrr_params), + KUNIT_CASE(dm_test_replay_link_setup_not_supported), + KUNIT_CASE(dm_test_replay_link_setup_already_enabled), + KUNIT_CASE(dm_test_replay_link_setup_success), + /* amdgpu_dm_replay_set_event */ + KUNIT_CASE(dm_test_replay_set_event_null_stream), + KUNIT_CASE(dm_test_replay_set_event_null_link), + KUNIT_CASE(dm_test_replay_set_event_feature_disabled), + KUNIT_CASE(dm_test_replay_set_event_missing_power_module), + KUNIT_CASE(dm_test_replay_set_event_already_set), + KUNIT_CASE(dm_test_replay_set_event_already_clear), {} }; -- cgit v1.2.3 From f1fa90c7a70966117c40ae01d5ae45f07eb73366 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 16 Jun 2026 19:42:06 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_psr [WHAT] Add Kunit tests for functions: - link_supports_psrsu() - amdgpu_dm_psr_fill_caps() - amdgpu_dm_set_psr_caps() - amdgpu_dm_psr_is_active_allowed() - amdgpu_dm_psr_set_event() Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c | 51 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h | 5 + .../display/amdgpu_dm/tests/amdgpu_dm_psr_test.c | 538 +++++++++++++++++++++ 3 files changed, 592 insertions(+), 2 deletions(-) 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 0dadc0bb214f..f87de3d18ac0 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 @@ -32,8 +32,8 @@ #include "modules/power/power_helpers.h" #include "amdgpu_dm_kunit_helpers.h" - -static bool link_supports_psrsu(struct dc_link *link) +STATIC_IFN_KUNIT +bool link_supports_psrsu(struct dc_link *link) { struct dc *dc = link->ctx->dc; @@ -60,6 +60,7 @@ static bool link_supports_psrsu(struct dc_link *link) /* Temporarily disable PSR-SU to avoid glitches */ return false; } +EXPORT_IF_KUNIT(link_supports_psrsu); STATIC_IFN_KUNIT void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) @@ -134,6 +135,7 @@ bool amdgpu_dm_set_psr_caps(struct dc_link *link, struct amdgpu_dm_connector *ac amdgpu_dm_psr_fill_caps(link, &aconnector->psr_caps); return true; } +EXPORT_IF_KUNIT(amdgpu_dm_set_psr_caps); /* * amdgpu_dm_psr_is_active_allowed() - check if psr is allowed on any stream @@ -157,6 +159,7 @@ bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm) } return false; } +EXPORT_IF_KUNIT(amdgpu_dm_psr_is_active_allowed); /* * amdgpu_dm_psr_set_event() - set or clear PSR event for stream @@ -190,3 +193,47 @@ bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream set_event, event, wait_for_disable); } EXPORT_IF_KUNIT(amdgpu_dm_psr_set_event); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +/** + * amdgpu_dm_psr_get_dc_feature_mask() - Get DC feature mask for KUnit tests. + * + * Return: Current value of amdgpu_dc_feature_mask. + */ +unsigned int amdgpu_dm_psr_get_dc_feature_mask(void) +{ + return amdgpu_dc_feature_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_get_dc_feature_mask); + +/** + * amdgpu_dm_psr_set_dc_feature_mask() - Set DC feature mask for KUnit tests. + * @feature_mask: DC feature mask to set while testing amdgpu_dm_psr_fill_caps(). + */ +void amdgpu_dm_psr_set_dc_feature_mask(unsigned int feature_mask) +{ + amdgpu_dc_feature_mask = feature_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_set_dc_feature_mask); + +/** + * amdgpu_dm_psr_get_dc_debug_mask() - Get DC debug mask for KUnit tests. + * + * Return: Current value of amdgpu_dc_debug_mask. + */ +unsigned int amdgpu_dm_psr_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_get_dc_debug_mask); + +/** + * amdgpu_dm_psr_set_dc_debug_mask() - Set DC debug mask for KUnit tests. + * @debug_mask: DC debug mask to set while testing link_supports_psrsu(). + */ +void amdgpu_dm_psr_set_dc_debug_mask(unsigned int debug_mask) +{ + amdgpu_dc_debug_mask = debug_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_set_dc_debug_mask); +#endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h index 40a09b5dc606..e442e7ed82ec 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h @@ -43,7 +43,12 @@ bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, bool wait_for_disable); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +bool link_supports_psrsu(struct dc_link *link); void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps); +unsigned int amdgpu_dm_psr_get_dc_feature_mask(void); +void amdgpu_dm_psr_set_dc_feature_mask(unsigned int feature_mask); +unsigned int amdgpu_dm_psr_get_dc_debug_mask(void); +void amdgpu_dm_psr_set_dc_debug_mask(unsigned int debug_mask); #endif #endif /* AMDGPU_DM_AMDGPU_DM_PSR_H_ */ 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 09084f70a405..2dd870f650db 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 @@ -7,7 +7,12 @@ #include +#include "dc.h" +#include "core_types.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_psr.h" +#include "power_helpers.h" /* * Helper: allocate and zero-initialise a dc_link sufficient for @@ -25,6 +30,365 @@ static struct dc_link *alloc_test_link(struct kunit *test) return link; } +/* + * Helper: allocate and wire the minimal DM/DC state needed for + * amdgpu_dm_psr_is_active_allowed() testing. + */ +static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct dc *dc; + struct dc_state *state; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + dm->dc = dc; + dc->current_state = state; + + return dm; +} + +static void add_test_stream(struct kunit *test, struct dc_state *state, + unsigned int index, struct dc_link *link) +{ + struct dc_stream_state *stream; + + KUNIT_ASSERT_LT(test, index, (unsigned int)MAX_PIPES); + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + state->streams[index] = stream; + if (state->stream_count <= index) + state->stream_count = index + 1; +} + +static struct dc_stream_state *alloc_test_psr_stream(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_link *link; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + link = alloc_test_link(test); + link->psr_settings.psr_feature_enabled = true; + stream->link = link; + kref_init(&stream->refcount); + + return stream; +} + +static struct core_power *create_test_power_module(struct kunit *test, + struct dc_stream_state *stream, struct psr_caps *caps) +{ + struct core_power *core_power; + + core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power); + + core_power->map = kunit_kzalloc(test, sizeof(*core_power->map), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power->map); + + core_power->map[0].stream = stream; + core_power->map[0].caps = caps; + core_power->map[0].psr_events = psr_event_vsync; + core_power->num_entities = 1; + + return core_power; +} + +static struct dc_link *alloc_test_psrsu_link(struct kunit *test) +{ + struct dc_link *link = alloc_test_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); + + 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; + link->dpcd_caps.psr_info.psr_version = DP_PSR2_WITH_Y_COORD_ET_SUPPORTED; + link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 1; + + return link; +} + +static struct dc_link *alloc_test_psr_caps_link(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->ctx->dc->caps.dmub_caps.psr = true; + link->connector_signal = SIGNAL_TYPE_EDP; + link->type = dc_connection_single; + + return link; +} + +static struct amdgpu_dm_connector *alloc_test_aconnector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + return aconnector; +} + +/* Tests for link_supports_psrsu() */ + +/** + * dm_test_link_supports_psrsu_no_dmcub() - DMCUB support is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_no_dmcub(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->ctx->dc->caps.dmcub_support = false; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_old_dcn() - DCN version 3.1 or newer is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_old_dcn(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->ctx->dce_version = DCN_VERSION_3_0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_panel_unsupported() - Panel PSR-SU caps are required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_panel_unsupported(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.psr_info.psr_version = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_missing_alpm() - AUX wake ALPM is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_missing_alpm(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_missing_y_coordinate() - Y coordinate support is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_missing_y_coordinate(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_missing_granularity() - Required granularity must + * be reported by the panel. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_missing_granularity(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED = 1; + link->dpcd_caps.psr_info.psr2_su_y_granularity_cap = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_debug_mask_disabled() - Debug mask disables PSR-SU. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_debug_mask_disabled(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + unsigned int old_debug_mask; + + old_debug_mask = amdgpu_dm_psr_get_dc_debug_mask(); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask | DC_DISABLE_PSR_SU); + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask); +} + +/** + * dm_test_link_supports_psrsu_temporarily_disabled() - Supported panels still + * return false while PSR-SU is temporarily disabled. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_temporarily_disabled(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + unsigned int old_debug_mask; + + old_debug_mask = amdgpu_dm_psr_get_dc_debug_mask(); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask & ~DC_DISABLE_PSR_SU); + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask); +} + +/* End of tests for link_supports_psrsu() */ + +/* Tests for amdgpu_dm_set_psr_caps() */ + +/** + * dm_test_set_psr_caps_null_link() - NULL link is rejected. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_null_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(NULL, aconnector)); +} + +/** + * dm_test_set_psr_caps_null_connector() - NULL connector is rejected. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_null_connector(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, NULL)); +} + +/** + * dm_test_set_psr_caps_no_dmub_psr() - DMUB PSR capability is required. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_no_dmub_psr(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->psr_settings.psr_version = DC_PSR_VERSION_1; + link->ctx->dc->caps.dmub_caps.psr = false; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); + KUNIT_EXPECT_EQ(test, link->psr_settings.psr_version, + DC_PSR_VERSION_UNSUPPORTED); +} + +/** + * dm_test_set_psr_caps_non_edp() - Only eDP links can enable PSR. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_non_edp(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_disconnected() - Disconnected links cannot enable PSR. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_disconnected(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->type = dc_connection_none; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_no_dpcd_psr() - DPCD PSR version is required. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_no_dpcd_psr(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->dpcd_caps.psr_info.psr_version = 0; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_edp1_disabled() - eDP panel instance 1 is blocked. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_edp1_disabled(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct dc_link *edp0 = alloc_test_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + struct dc *dc = link->ctx->dc; + + edp0->connector_signal = SIGNAL_TYPE_EDP; + dc->links[0] = edp0; + dc->links[1] = link; + dc->link_count = 2; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_success_psr1() - Valid eDP link enables PSR1 caps. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_success_psr1(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_set_psr_caps(link, aconnector)); + KUNIT_EXPECT_EQ(test, link->psr_settings.psr_version, DC_PSR_VERSION_1); + KUNIT_EXPECT_EQ(test, (int)aconnector->psr_caps.psr_version, 1); + KUNIT_EXPECT_EQ(test, (int)aconnector->psr_caps.support_ver, + DP_PSR2_WITH_Y_COORD_ET_SUPPORTED); +} + +/* End of tests for amdgpu_dm_set_psr_caps() */ + /* Tests for amdgpu_dm_psr_fill_caps() — PSR version mapping */ static void dm_test_psr_fill_caps_version_1(struct kunit *test) @@ -221,6 +585,24 @@ static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) (caps.psr_power_opt_flag & psr_power_opt_z10_static_screen) != 0); } + +static void dm_test_psr_fill_caps_power_opts_smu_opt_set(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + unsigned int old_feature_mask; + + memset(&caps, 0, sizeof(caps)); + old_feature_mask = amdgpu_dm_psr_get_dc_feature_mask(); + amdgpu_dm_psr_set_dc_feature_mask(old_feature_mask | DC_PSR_ALLOW_SMU_OPT); + + amdgpu_dm_psr_fill_caps(link, &caps); + amdgpu_dm_psr_set_dc_feature_mask(old_feature_mask); + + KUNIT_EXPECT_TRUE(test, + (caps.psr_power_opt_flag & + psr_power_opt_smu_opt_static_screen) != 0); +} /* End of tests for amdgpu_dm_psr_fill_caps() */ /* Tests for amdgpu_dm_psr_set_event() — early-exit validation guards */ @@ -258,9 +640,155 @@ static void dm_test_psr_set_event_psr_not_enabled(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, stream, true, psr_event_vsync, false)); } + +/** + * dm_test_psr_set_event_get_event_fails() - Failed power event read returns false. + * @test: KUnit test context. + */ +static void dm_test_psr_set_event_get_event_fails(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_stream_state *stream = alloc_test_psr_stream(test); + + dm->power_module = NULL; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(dm, stream, true, psr_event_vsync, false)); +} + +/** + * dm_test_psr_set_event_already_set() - Already set event returns true. + * @test: KUnit test context. + */ +static void dm_test_psr_set_event_already_set(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_stream_state *stream = alloc_test_psr_stream(test); + struct psr_caps caps = {0}; + struct core_power *core_power; + + caps.psr_version = 1; + core_power = create_test_power_module(test, stream, &caps); + dm->power_module = &core_power->mod_public; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_psr_set_event(dm, stream, true, psr_event_vsync, false)); + KUNIT_EXPECT_EQ(test, core_power->map[0].psr_events, + (unsigned int)psr_event_vsync); +} + +/** + * dm_test_psr_set_event_updates_event() - Changed event delegates to mod_power. + * @test: KUnit test context. + */ +static void dm_test_psr_set_event_updates_event(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_stream_state *stream = alloc_test_psr_stream(test); + struct psr_caps caps = {0}; + struct core_power *core_power; + + caps.psr_version = 1; + core_power = create_test_power_module(test, stream, &caps); + dm->power_module = &core_power->mod_public; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_psr_set_event(dm, stream, true, psr_event_full_screen, false)); + KUNIT_EXPECT_EQ(test, core_power->map[0].psr_events, + (unsigned int)(psr_event_vsync | psr_event_full_screen)); +} /* End of tests for amdgpu_dm_psr_set_event() */ +/* Tests for amdgpu_dm_psr_is_active_allowed() */ + +/** + * dm_test_psr_is_active_allowed_no_streams() - Empty DC state disallows PSR. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_no_streams(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/** + * dm_test_psr_is_active_allowed_null_link() - Streams without links are skipped. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_null_link(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_state *state = dm->dc->current_state; + + add_test_stream(test, state, 0, NULL); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/** + * dm_test_psr_is_active_allowed_requires_enabled_and_allowed() - Both link flags + * must be set before PSR active is allowed. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_requires_enabled_and_allowed(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_state *state = dm->dc->current_state; + struct dc_link *link = alloc_test_link(test); + + add_test_stream(test, state, 0, link); + link->psr_settings.psr_allow_active = true; + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); + + link->psr_settings.psr_allow_active = false; + link->psr_settings.psr_feature_enabled = true; + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/** + * dm_test_psr_is_active_allowed_any_stream() - Any enabled and allowed stream + * permits active PSR. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_any_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_state *state = dm->dc->current_state; + struct dc_link *disabled_link = alloc_test_link(test); + struct dc_link *allowed_link = alloc_test_link(test); + + disabled_link->psr_settings.psr_allow_active = true; + allowed_link->psr_settings.psr_feature_enabled = true; + allowed_link->psr_settings.psr_allow_active = true; + + add_test_stream(test, state, 0, disabled_link); + add_test_stream(test, state, 1, allowed_link); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/* End of tests for amdgpu_dm_psr_is_active_allowed() */ + static struct kunit_case dm_psr_test_cases[] = { + /* link_supports_psrsu */ + KUNIT_CASE(dm_test_link_supports_psrsu_no_dmcub), + KUNIT_CASE(dm_test_link_supports_psrsu_old_dcn), + KUNIT_CASE(dm_test_link_supports_psrsu_panel_unsupported), + KUNIT_CASE(dm_test_link_supports_psrsu_missing_alpm), + KUNIT_CASE(dm_test_link_supports_psrsu_missing_y_coordinate), + KUNIT_CASE(dm_test_link_supports_psrsu_missing_granularity), + KUNIT_CASE(dm_test_link_supports_psrsu_debug_mask_disabled), + KUNIT_CASE(dm_test_link_supports_psrsu_temporarily_disabled), + /* amdgpu_dm_set_psr_caps */ + KUNIT_CASE(dm_test_set_psr_caps_null_link), + KUNIT_CASE(dm_test_set_psr_caps_null_connector), + KUNIT_CASE(dm_test_set_psr_caps_no_dmub_psr), + KUNIT_CASE(dm_test_set_psr_caps_non_edp), + KUNIT_CASE(dm_test_set_psr_caps_disconnected), + KUNIT_CASE(dm_test_set_psr_caps_no_dpcd_psr), + KUNIT_CASE(dm_test_set_psr_caps_edp1_disabled), + KUNIT_CASE(dm_test_set_psr_caps_success_psr1), + /* amdgpu_dm_psr_fill_caps */ KUNIT_CASE(dm_test_psr_fill_caps_version_1), KUNIT_CASE(dm_test_psr_fill_caps_version_su1), KUNIT_CASE(dm_test_psr_fill_caps_version_unsupported), @@ -273,9 +801,19 @@ static struct kunit_case dm_psr_test_cases[] = { KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields_unset), KUNIT_CASE(dm_test_psr_fill_caps_rate_control_always_zero), KUNIT_CASE(dm_test_psr_fill_caps_power_opts_z10_always_set), + KUNIT_CASE(dm_test_psr_fill_caps_power_opts_smu_opt_set), + /* amdgpu_dm_psr_set_event */ KUNIT_CASE(dm_test_psr_set_event_null_stream), KUNIT_CASE(dm_test_psr_set_event_null_link), KUNIT_CASE(dm_test_psr_set_event_psr_not_enabled), + KUNIT_CASE(dm_test_psr_set_event_get_event_fails), + KUNIT_CASE(dm_test_psr_set_event_already_set), + KUNIT_CASE(dm_test_psr_set_event_updates_event), + /* amdgpu_dm_psr_is_active_allowed */ + KUNIT_CASE(dm_test_psr_is_active_allowed_no_streams), + KUNIT_CASE(dm_test_psr_is_active_allowed_null_link), + KUNIT_CASE(dm_test_psr_is_active_allowed_requires_enabled_and_allowed), + KUNIT_CASE(dm_test_psr_is_active_allowed_any_stream), {} }; -- cgit v1.2.3 From b292f97d300f373e6de2acfa3a9fa8bd82e84c46 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 17 Jun 2026 14:04:48 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_pp_smu Add comprehensive KUnit test coverage for amdgpu_dm_pp_smu.c including: - Utility functions: dc_to_pp_clock_type, pp_to_dc_clock_levels, build_pm_display_cfg, get_default_clock_levels, build_wm_clock_ranges_soc15, cap_clock_levels_to_validation - DPM-backed functions: dm_pp_get_clock_levels_by_type, dm_pp_notify_wm_clock_changes, dm_pp_apply_clock_for_voltage_request, dm_pp_get_static_clocks - Raven pass-throughs: pp_rv_set_wm_ranges, pp_rv_set_pme_wa_enable, pp_rv_set_active_display_count, pp_rv_set_min_deep_sleep_dcfclk, pp_rv_set_hard_min_dcefclk_by_freq, pp_rv_set_hard_min_fclk_by_freq - Navi functions: pp_nv_set_wm_ranges, pp_nv_get_maximum_sustainable_clocks, pp_nv_get_uclk_dpm_states, pp_nv_get_dpm_clock_table - Renoir: pp_rn_get_dpm_clock_table - dm_pp_get_funcs ASIC family selection v2: squash in build fix for removed functions Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 52 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 23 + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 1481 +++++++++++++++++++- 3 files changed, 1538 insertions(+), 18 deletions(-) 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 e0fe4cb97f31..0d2e5294d062 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 @@ -337,6 +337,7 @@ bool dm_pp_get_clock_levels_by_type( return true; } +EXPORT_IF_KUNIT(dm_pp_get_clock_levels_by_type); bool dm_pp_get_clock_levels_by_type_with_latency( const struct dc_context *ctx, @@ -357,6 +358,7 @@ bool dm_pp_get_clock_levels_by_type_with_latency( return true; } +EXPORT_IF_KUNIT(dm_pp_get_clock_levels_by_type_with_latency); bool dm_pp_get_clock_levels_by_type_with_voltage( const struct dc_context *ctx, @@ -377,6 +379,7 @@ bool dm_pp_get_clock_levels_by_type_with_voltage( return true; } +EXPORT_IF_KUNIT(dm_pp_get_clock_levels_by_type_with_voltage); bool dm_pp_notify_wm_clock_changes( const struct dc_context *ctx, @@ -396,6 +399,7 @@ bool dm_pp_notify_wm_clock_changes( return false; } +EXPORT_IF_KUNIT(dm_pp_notify_wm_clock_changes); bool dm_pp_apply_clock_for_voltage_request( const struct dc_context *ctx, @@ -464,7 +468,7 @@ STATIC_IFN_KUNIT void build_wm_clock_ranges_soc15( } EXPORT_IF_KUNIT(build_wm_clock_ranges_soc15); -static void pp_rv_set_wm_ranges(struct pp_smu *pp, +STATIC_IFN_KUNIT void pp_rv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges) { const struct dc_context *ctx = pp->dm; @@ -476,48 +480,54 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp, amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, &wm_with_clock_ranges); } +EXPORT_IF_KUNIT(pp_rv_set_wm_ranges); -static void pp_rv_set_pme_wa_enable(struct pp_smu *pp) +STATIC_IFN_KUNIT void pp_rv_set_pme_wa_enable(struct pp_smu *pp) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_notify_smu_enable_pwe(adev); } +EXPORT_IF_KUNIT(pp_rv_set_pme_wa_enable); -static void pp_rv_set_active_display_count(struct pp_smu *pp, int count) +STATIC_IFN_KUNIT void pp_rv_set_active_display_count(struct pp_smu *pp, int count) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_active_display_count(adev, count); } +EXPORT_IF_KUNIT(pp_rv_set_active_display_count); -static void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock) +STATIC_IFN_KUNIT void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_min_deep_sleep_dcefclk(adev, clock); } +EXPORT_IF_KUNIT(pp_rv_set_min_deep_sleep_dcfclk); -static void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock) +STATIC_IFN_KUNIT void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_hard_min_dcefclk_by_freq(adev, clock); } +EXPORT_IF_KUNIT(pp_rv_set_hard_min_dcefclk_by_freq); -static void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz) +STATIC_IFN_KUNIT void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_hard_min_fclk_by_freq(adev, mhz); } +EXPORT_IF_KUNIT(pp_rv_set_hard_min_fclk_by_freq); -static enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges) { const struct dc_context *ctx = pp->dm; @@ -527,8 +537,9 @@ static enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_wm_ranges); -static enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count) +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; @@ -543,8 +554,9 @@ static enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count) return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_display_count); -static enum pp_smu_status +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; @@ -560,8 +572,9 @@ pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz) return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_min_deep_sleep_dcfclk); -static enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq( +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq( struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; @@ -583,8 +596,9 @@ static enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_hard_min_dcefclk_by_freq); -static enum pp_smu_status +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; @@ -606,8 +620,9 @@ pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz) return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_hard_min_uclk_by_freq); -static enum pp_smu_status pp_nv_set_pstate_handshake_support( +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_pstate_handshake_support( struct pp_smu *pp, bool pstate_handshake_supported) { const struct dc_context *ctx = pp->dm; @@ -619,6 +634,7 @@ static enum pp_smu_status pp_nv_set_pstate_handshake_support( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_pstate_handshake_support); STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, enum amd_pp_clock_type *clock_type) @@ -641,7 +657,7 @@ STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, } EXPORT_IF_KUNIT(pp_smu_nv_clock_id_to_pp); -static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, enum pp_smu_nv_clock_id clock_id, int mhz) { const struct dc_context *ctx = pp->dm; @@ -665,8 +681,9 @@ static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_voltage_by_freq); -static enum pp_smu_status pp_nv_get_maximum_sustainable_clocks( +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_get_maximum_sustainable_clocks( struct pp_smu *pp, struct pp_smu_nv_clock_table *max_clocks) { const struct dc_context *ctx = pp->dm; @@ -682,8 +699,9 @@ static enum pp_smu_status pp_nv_get_maximum_sustainable_clocks( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_get_maximum_sustainable_clocks); -static enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, unsigned int *clock_values_in_khz, unsigned int *num_states) { const struct dc_context *ctx = pp->dm; @@ -700,8 +718,9 @@ static enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_get_uclk_dpm_states); -static enum pp_smu_status pp_rn_get_dpm_clock_table( +STATIC_IFN_KUNIT enum pp_smu_status pp_rn_get_dpm_clock_table( struct pp_smu *pp, struct dpm_clocks *clock_table) { const struct dc_context *ctx = pp->dm; @@ -716,6 +735,7 @@ static enum pp_smu_status pp_rn_get_dpm_clock_table( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_rn_get_dpm_clock_table); void dm_pp_get_funcs( struct dc_context *ctx, 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 e851e3ee5b63..f918eb71f0d1 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 @@ -33,6 +33,29 @@ void cap_clock_levels_to_validation(struct dm_pp_clock_levels *dc_clks, const struct amd_pp_simple_clock_info *validation_clks); bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, enum amd_pp_clock_type *clock_type); +void pp_rv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges); +void pp_rv_set_pme_wa_enable(struct pp_smu *pp); +void pp_rv_set_active_display_count(struct pp_smu *pp, int count); +void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock); +void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock); +void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, + struct pp_smu_wm_range_sets *ranges); +enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count); +enum pp_smu_status pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_pstate_handshake_support(struct pp_smu *pp, + bool pstate_handshake_supported); +enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, + enum pp_smu_nv_clock_id clock_id, int mhz); +enum pp_smu_status pp_nv_get_maximum_sustainable_clocks(struct pp_smu *pp, + struct pp_smu_nv_clock_table *max_clocks); +enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, + unsigned int *clock_values_in_khz, + unsigned int *num_states); +enum pp_smu_status pp_rn_get_dpm_clock_table(struct pp_smu *pp, + struct dpm_clocks *clock_table); #endif #endif /* __AMDGPU_DM_PP_SMU_H__ */ 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 dbb6dfd5c284..8d1d26bfcc16 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 @@ -7,6 +7,7 @@ #include #include +#include #include "dc.h" #include "dm_services.h" @@ -16,6 +17,201 @@ #include "amdgpu_dm.h" #include "amdgpu_dm_pp_smu.h" +/* ---- Stub DPM layer ---- */ + +/** + * struct stub_dpm_context - Tracks stub DPM callback invocations + * @ret_val: Return value for the next DPM callback + * @get_current_clocks_info: Clock info returned by stub get_current_clocks + * @get_clock_by_type_clocks: Clocks returned by stub get_clock_by_type + * @get_validation_clks: Validation clocks returned by stub + * @get_clock_by_type_with_latency_clks: Returned by stub with_latency + * @get_clock_by_type_with_voltage_clks: Returned by stub with_voltage + * @set_watermarks_ret: Return value for set_watermarks + * @display_clock_voltage_ret: Return value for display_clock_voltage_request + * @display_disable_memory_clock_switch_ret: Return for disable_memory_clock + * @get_max_sustainable_ret: Return for get_max_sustainable_clocks_by_dc + * @get_uclk_dpm_ret: Return for get_uclk_dpm_states + * @get_dpm_clock_table_ret: Return for get_dpm_clock_table + * @set_active_display_count_ret: Return for set_active_display_count + * @set_min_deep_sleep_dcefclk_ret: Return for set_min_deep_sleep_dcefclk + * @get_validation_clks_ret: Return for get_display_mode_validation_clocks + */ +struct stub_dpm_context { + int ret_val; + struct amd_pp_clock_info get_current_clocks_info; + struct amd_pp_clocks get_clock_by_type_clocks; + struct amd_pp_simple_clock_info get_validation_clks; + int get_validation_clks_ret; + struct pp_clock_levels_with_latency get_clock_by_type_with_latency_clks; + struct pp_clock_levels_with_voltage get_clock_by_type_with_voltage_clks; + int set_watermarks_ret; + int display_clock_voltage_ret; + int display_disable_memory_clock_switch_ret; + int get_max_sustainable_ret; + int get_uclk_dpm_ret; + int get_dpm_clock_table_ret; + int set_active_display_count_ret; + int set_min_deep_sleep_dcefclk_ret; +}; + +static struct stub_dpm_context *stub_dpm_ctx; + +static int stub_get_current_clocks(void *handle, struct amd_pp_clock_info *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_current_clocks_info; + return 0; +} + +static int stub_get_clock_by_type(void *handle, enum amd_pp_clock_type type, + struct amd_pp_clocks *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_clock_by_type_clocks; + return 0; +} + +static int stub_get_display_mode_validation_clocks(void *handle, + struct amd_pp_simple_clock_info *clocks) +{ + if (stub_dpm_ctx->get_validation_clks_ret) + return stub_dpm_ctx->get_validation_clks_ret; + *clocks = stub_dpm_ctx->get_validation_clks; + return 0; +} + +static int stub_get_clock_by_type_with_latency(void *handle, + enum amd_pp_clock_type type, + struct pp_clock_levels_with_latency *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_clock_by_type_with_latency_clks; + return 0; +} + +static int stub_get_clock_by_type_with_voltage(void *handle, + enum amd_pp_clock_type type, + struct pp_clock_levels_with_voltage *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_clock_by_type_with_voltage_clks; + return 0; +} + +static void stub_display_configuration_change(void *handle) +{ + /* No-op: satisfies display_configuration_changed callback */ +} + +static void stub_pm_compute_clocks(void *handle) +{ + /* No-op: satisfies pm_compute_clocks callback */ +} + +static int stub_set_watermarks_for_clocks_ranges(void *handle, void *clock_ranges) +{ + return stub_dpm_ctx->set_watermarks_ret; +} + +static int stub_display_clock_voltage_request(void *handle, + struct pp_display_clock_request *clock) +{ + return stub_dpm_ctx->display_clock_voltage_ret; +} + +static int stub_set_active_display_count(void *handle, uint32_t count) +{ + return stub_dpm_ctx->set_active_display_count_ret; +} + +static int stub_set_min_deep_sleep_dcefclk(void *handle, uint32_t clock) +{ + return stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret; +} + +static int stub_set_hard_min_dcefclk_by_freq(void *handle, uint32_t clock) +{ + return 0; +} + +static int stub_set_hard_min_fclk_by_freq(void *handle, uint32_t clock) +{ + return 0; +} + +static int stub_notify_smu_enable_pwe(void *handle) +{ + return 0; +} + +static int stub_display_disable_memory_clock_switch(void *handle, + bool disable_memory_clock_switch) +{ + return stub_dpm_ctx->display_disable_memory_clock_switch_ret; +} + +static int stub_get_max_sustainable_clocks_by_dc(void *handle, + struct pp_smu_nv_clock_table *max_clocks) +{ + return stub_dpm_ctx->get_max_sustainable_ret; +} + +static int stub_get_uclk_dpm_states(void *handle, + unsigned int *clock_values_in_khz, + unsigned int *num_states) +{ + return stub_dpm_ctx->get_uclk_dpm_ret; +} + +static int stub_get_dpm_clock_table(void *handle, struct dpm_clocks *clock_table) +{ + return stub_dpm_ctx->get_dpm_clock_table_ret; +} + +static const struct amd_pm_funcs stub_pp_funcs = { + .get_current_clocks = stub_get_current_clocks, + .get_clock_by_type = stub_get_clock_by_type, + .get_display_mode_validation_clocks = stub_get_display_mode_validation_clocks, + .get_clock_by_type_with_latency = stub_get_clock_by_type_with_latency, + .get_clock_by_type_with_voltage = stub_get_clock_by_type_with_voltage, + .display_configuration_changed = stub_display_configuration_change, + .pm_compute_clocks = stub_pm_compute_clocks, + .set_watermarks_for_clocks_ranges = stub_set_watermarks_for_clocks_ranges, + .display_clock_voltage_request = stub_display_clock_voltage_request, + .set_active_display_count = stub_set_active_display_count, + .set_min_deep_sleep_dcefclk = stub_set_min_deep_sleep_dcefclk, + .set_hard_min_dcefclk_by_freq = stub_set_hard_min_dcefclk_by_freq, + .set_hard_min_fclk_by_freq = stub_set_hard_min_fclk_by_freq, + .notify_smu_enable_pwe = stub_notify_smu_enable_pwe, + .display_disable_memory_clock_switch = stub_display_disable_memory_clock_switch, + .get_max_sustainable_clocks_by_dc = stub_get_max_sustainable_clocks_by_dc, + .get_uclk_dpm_states = stub_get_uclk_dpm_states, + .get_dpm_clock_table = stub_get_dpm_clock_table, +}; + +/** + * setup_stub_dpm - Initialize a stub DPM environment for testing + * @test: KUnit test context + * @adev: Pointer to amdgpu_device to configure + * + * Sets up adev->powerplay.pp_funcs and initializes adev->pm.mutex so that + * amdgpu_dpm_* functions can be safely called with stub callbacks. + */ +static void setup_stub_dpm(struct kunit *test, struct amdgpu_device *adev) +{ + stub_dpm_ctx = kunit_kzalloc(test, sizeof(*stub_dpm_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stub_dpm_ctx); + + adev->powerplay.pp_funcs = &stub_pp_funcs; + adev->powerplay.pp_handle = adev; + mutex_init(&adev->pm.mutex); +} + /* ---- Tests for get_default_clock_levels ---- */ /** @@ -706,23 +902,35 @@ static void dm_test_build_wm_clock_ranges_mcif(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, ranges); KUNIT_ASSERT_NOT_NULL(test, wm); - ranges->num_writer_wm_sets = 1; + ranges->num_writer_wm_sets = 2; ranges->writer_wm_sets[0].wm_inst = 1; ranges->writer_wm_sets[0].max_fill_clk_mhz = 1200; ranges->writer_wm_sets[0].min_fill_clk_mhz = 600; ranges->writer_wm_sets[0].max_drain_clk_mhz = 1000; ranges->writer_wm_sets[0].min_drain_clk_mhz = 500; + /* set 1: wm_inst > 3 -> clamped to WM_SET_A */ + ranges->writer_wm_sets[1].wm_inst = 5; + ranges->writer_wm_sets[1].max_fill_clk_mhz = 1400; + ranges->writer_wm_sets[1].min_fill_clk_mhz = 700; + ranges->writer_wm_sets[1].max_drain_clk_mhz = 1100; + ranges->writer_wm_sets[1].min_drain_clk_mhz = 550; build_wm_clock_ranges_soc15(ranges, wm); KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 0U); - KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 1U); + KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 2U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_set_id, WM_SET_B); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_socclk_clk_in_khz, 1200000U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_socclk_clk_in_khz, 600000U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_mem_clk_in_khz, 1000000U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_mem_clk_in_khz, 500000U); + + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_set_id, WM_SET_A); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_max_socclk_clk_in_khz, 1400000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_min_socclk_clk_in_khz, 700000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_max_mem_clk_in_khz, 1100000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_min_mem_clk_in_khz, 550000U); } /* ---- Tests for cap_clock_levels_to_validation ---- */ @@ -911,6 +1119,1208 @@ static void dm_test_nv_clock_id_invalid(struct kunit *test) KUNIT_EXPECT_EQ(test, clock_type, amd_pp_dcef_clock); } +/* ---- Tests using stub DPM layer ---- */ + +/** + * dm_test_apply_display_requirements_dpm_enabled - Test DPM-enabled path + * @test: KUnit test context + * + * Verify that dm_pp_apply_display_requirements calls build_pm_display_cfg + * and the DPM callbacks when DPM is enabled, and returns true. + */ +static void dm_test_apply_display_requirements_dpm_enabled(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_display_configuration cfg = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + adev->pm.dpm_enabled = true; + + cfg.display_count = 1; + cfg.min_engine_clock_khz = 300000; + cfg.disp_configs[0].v_refresh = 60; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_display_requirements(ctx, &cfg)); + KUNIT_EXPECT_EQ(test, adev->pm.pm_display_cfg.min_core_set_clock, 30000); + KUNIT_EXPECT_EQ(test, adev->pm.pm_display_cfg.vrefresh, 60); +} + +/** + * dm_test_get_clock_levels_by_type_dpm_error - Test DPM error fallback + * @test: KUnit test context + * + * Verify that dm_pp_get_clock_levels_by_type falls back to default clock + * levels when amdgpu_dpm_get_clock_by_type returns an error. + */ +static void dm_test_get_clock_levels_by_type_dpm_error(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels dc_clks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->ret_val = -EINVAL; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type(ctx, + DM_PP_CLOCK_TYPE_DISPLAY_CLK, &dc_clks)); + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 6U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); +} + +/** + * dm_test_get_clock_levels_by_type_success - Test successful clock query + * @test: KUnit test context + * + * Verify that dm_pp_get_clock_levels_by_type returns the queried clocks + * capped by validation clocks. + */ +static void dm_test_get_clock_levels_by_type_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels dc_clks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + stub_dpm_ctx->get_clock_by_type_clocks.count = 3; + stub_dpm_ctx->get_clock_by_type_clocks.clock[0] = 300000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[1] = 500000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[2] = 700000; + + /* validation at 60000 * 10 = 600000 kHz → caps to 2 levels */ + stub_dpm_ctx->get_validation_clks.engine_max_clock = 60000; + stub_dpm_ctx->get_validation_clks.memory_max_clock = 80000; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &dc_clks)); + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U); +} + +/** + * dm_test_get_clock_levels_by_type_validation_fallback - Test validation error + * @test: KUnit test context + * + * Verify that dm_pp_get_clock_levels_by_type uses default validation clocks + * (engine=720000, memory=800000 kHz) when get_display_mode_validation_clocks + * returns an error, capping levels accordingly. + */ +static void dm_test_get_clock_levels_by_type_validation_fallback(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels dc_clks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + /* get_clock_by_type succeeds with 3 engine clock levels */ + stub_dpm_ctx->get_clock_by_type_clocks.count = 3; + stub_dpm_ctx->get_clock_by_type_clocks.clock[0] = 300000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[1] = 500000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[2] = 800000; + + /* Force validation clocks to fail → triggers default path */ + stub_dpm_ctx->get_validation_clks_ret = -EINVAL; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &dc_clks)); + /* + * Default validation: engine_max_clock = 72000 * 10 = 720000 kHz. + * Clocks 300000 and 500000 are within limit, 800000 exceeds it, + * so num_levels is capped to 2. + */ + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U); +} + +/** + * dm_test_get_clock_levels_with_latency_success - Test latency clock query + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_latency returns true and + * copies the clock/latency data from the DPM backend. + */ +static void dm_test_get_clock_levels_with_latency_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_latency info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + stub_dpm_ctx->get_clock_by_type_with_latency_clks.num_levels = 1; + stub_dpm_ctx->get_clock_by_type_with_latency_clks.data[0].clocks_in_khz = 600000; + stub_dpm_ctx->get_clock_by_type_with_latency_clks.data[0].latency_in_us = 15; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type_with_latency(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &info)); + KUNIT_EXPECT_EQ(test, info.num_levels, 1U); + KUNIT_EXPECT_EQ(test, info.data[0].clocks_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, info.data[0].latency_in_us, 15U); +} + +/** + * dm_test_get_clock_levels_with_latency_failure - Test latency query error + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_latency returns false on DPM error. + */ +static void dm_test_get_clock_levels_with_latency_failure(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_latency info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->ret_val = -EINVAL; + + KUNIT_EXPECT_FALSE(test, dm_pp_get_clock_levels_by_type_with_latency(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &info)); +} + +/** + * dm_test_get_clock_levels_with_voltage_success - Test voltage clock query + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_voltage returns true and + * copies the clock/voltage data from the DPM backend. + */ +static void dm_test_get_clock_levels_with_voltage_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_voltage info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + stub_dpm_ctx->get_clock_by_type_with_voltage_clks.num_levels = 1; + stub_dpm_ctx->get_clock_by_type_with_voltage_clks.data[0].clocks_in_khz = 400000; + stub_dpm_ctx->get_clock_by_type_with_voltage_clks.data[0].voltage_in_mv = 900; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type_with_voltage(ctx, + DM_PP_CLOCK_TYPE_MEMORY_CLK, &info)); + KUNIT_EXPECT_EQ(test, info.num_levels, 1U); + KUNIT_EXPECT_EQ(test, info.data[0].clocks_in_khz, 400000U); + KUNIT_EXPECT_EQ(test, info.data[0].voltage_in_mv, 900U); +} + +/** + * dm_test_get_clock_levels_with_voltage_failure - Test voltage query error + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_voltage returns false on DPM error. + */ +static void dm_test_get_clock_levels_with_voltage_failure(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_voltage info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->ret_val = -EINVAL; + + KUNIT_EXPECT_FALSE(test, dm_pp_get_clock_levels_by_type_with_voltage(ctx, + DM_PP_CLOCK_TYPE_MEMORY_CLK, &info)); +} + +/** + * dm_test_notify_wm_clock_changes_polaris - Test Polaris watermark path + * @test: KUnit test context + * + * Verify dm_pp_notify_wm_clock_changes returns true for Polaris ASICs + * when the DPM set_watermarks call succeeds. + */ +static void dm_test_notify_wm_clock_changes_polaris(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges wm = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + adev->asic_type = CHIP_POLARIS10; + stub_dpm_ctx->set_watermarks_ret = 0; + + KUNIT_EXPECT_TRUE(test, dm_pp_notify_wm_clock_changes(ctx, &wm)); +} + +/** + * dm_test_notify_wm_clock_changes_non_polaris - Test non-Polaris path + * @test: KUnit test context + * + * Verify dm_pp_notify_wm_clock_changes returns false for non-Polaris ASICs. + */ +static void dm_test_notify_wm_clock_changes_non_polaris(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges wm = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + adev->asic_type = CHIP_NAVI10; + + KUNIT_EXPECT_FALSE(test, dm_pp_notify_wm_clock_changes(ctx, &wm)); +} + +/** + * dm_test_apply_clock_for_voltage_success - Test successful voltage request + * @test: KUnit test context + * + * Verify dm_pp_apply_clock_for_voltage_request returns true when the DPM + * callback succeeds for a valid clock type. + */ +static void dm_test_apply_clock_for_voltage_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + req.clk_type = DM_PP_CLOCK_TYPE_ENGINE_CLK; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/** + * dm_test_apply_clock_for_voltage_eopnotsupp - Test EOPNOTSUPP treated as success + * @test: KUnit test context + * + * Verify dm_pp_apply_clock_for_voltage_request returns true when the DPM + * callback returns -EOPNOTSUPP (not supported is non-fatal). + */ +static void dm_test_apply_clock_for_voltage_eopnotsupp(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->display_clock_voltage_ret = -EOPNOTSUPP; + + req.clk_type = DM_PP_CLOCK_TYPE_ENGINE_CLK; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/** + * dm_test_apply_clock_for_voltage_fail - Test DPM error returns false + * @test: KUnit test context + * + * Verify dm_pp_apply_clock_for_voltage_request returns false when the DPM + * callback fails with an error other than -EOPNOTSUPP. + */ +static void dm_test_apply_clock_for_voltage_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->display_clock_voltage_ret = -EIO; + + req.clk_type = DM_PP_CLOCK_TYPE_ENGINE_CLK; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_FALSE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/* ---- Tests for pp_nv_set_display_count ---- */ + +/** + * dm_test_nv_set_display_count_ok - Test successful display count set + * @test: KUnit test context + * + * Verify pp_nv_set_display_count returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_nv_set_display_count_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_active_display_count_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_display_count(&pp_smu, 2), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_display_count_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_display_count returns PP_SMU_RESULT_UNSUPPORTED when + * the DPM callback returns -EOPNOTSUPP. + */ +static void dm_test_nv_set_display_count_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_active_display_count_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_display_count(&pp_smu, 2), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_display_count_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_display_count returns PP_SMU_RESULT_FAIL on a generic + * DPM error. + */ +static void dm_test_nv_set_display_count_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_active_display_count_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_display_count(&pp_smu, 2), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_voltage_by_freq ---- */ + +/** + * dm_test_nv_set_voltage_by_freq_ok - Test successful voltage-by-freq + * @test: KUnit test context + * + * Verify pp_nv_set_voltage_by_freq returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_nv_set_voltage_by_freq_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_voltage_by_freq(&pp_smu, PP_SMU_NV_DISPCLK, 600), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_voltage_by_freq_invalid_id - Test invalid clock id + * @test: KUnit test context + * + * Verify pp_nv_set_voltage_by_freq returns PP_SMU_RESULT_FAIL for an + * unrecognized clock id without calling DPM. + */ +static void dm_test_nv_set_voltage_by_freq_invalid_id(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_set_voltage_by_freq(&pp_smu, (enum pp_smu_nv_clock_id)0xff, 600), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_pstate_handshake_support ---- */ + +/** + * dm_test_nv_pstate_handshake_ok - Test successful pstate handshake + * @test: KUnit test context + * + * Verify pp_nv_set_pstate_handshake_support returns PP_SMU_RESULT_OK + * when the DPM callback succeeds. + */ +static void dm_test_nv_pstate_handshake_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_disable_memory_clock_switch_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_pstate_handshake_support(&pp_smu, true), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_pstate_handshake_fail - Test failed pstate handshake + * @test: KUnit test context + * + * Verify pp_nv_set_pstate_handshake_support returns PP_SMU_RESULT_FAIL + * when the DPM callback returns non-zero. + */ +static void dm_test_nv_pstate_handshake_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_disable_memory_clock_switch_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_pstate_handshake_support(&pp_smu, true), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_rn_get_dpm_clock_table ---- */ + +/** + * dm_test_rn_get_dpm_clock_table_ok - Test successful DPM clock table + * @test: KUnit test context + * + * Verify pp_rn_get_dpm_clock_table returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_rn_get_dpm_clock_table_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct dpm_clocks clock_table = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_dpm_clock_table_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_rn_get_dpm_clock_table(&pp_smu, &clock_table), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_rn_get_dpm_clock_table_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_rn_get_dpm_clock_table returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_rn_get_dpm_clock_table_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct dpm_clocks clock_table = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_dpm_clock_table_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_rn_get_dpm_clock_table(&pp_smu, &clock_table), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_rn_get_dpm_clock_table_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_rn_get_dpm_clock_table returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_rn_get_dpm_clock_table_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct dpm_clocks clock_table = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_dpm_clock_table_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_rn_get_dpm_clock_table(&pp_smu, &clock_table), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_rv_set_wm_ranges ---- */ + +/** + * dm_test_rv_set_wm_ranges - Test Raven watermark range forwarding + * @test: KUnit test context + * + * Verify pp_rv_set_wm_ranges converts watermark ranges via + * build_wm_clock_ranges_soc15 and forwards them to DPM without crashing. + */ +static void dm_test_rv_set_wm_ranges(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_wm_range_sets ranges = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + ranges.num_reader_wm_sets = 1; + ranges.reader_wm_sets[0].wm_inst = 0; + ranges.reader_wm_sets[0].max_drain_clk_mhz = 600; + ranges.reader_wm_sets[0].min_drain_clk_mhz = 300; + + pp_rv_set_wm_ranges(&pp_smu, &ranges); + + /* Reaching here without crash confirms coverage */ + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_pme_wa_enable ---- */ + +/** + * dm_test_rv_set_pme_wa_enable - Test Raven PME workaround enable + * @test: KUnit test context + * + * Verify pp_rv_set_pme_wa_enable forwards the call to DPM without crashing. + */ +static void dm_test_rv_set_pme_wa_enable(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_pme_wa_enable(&pp_smu); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_active_display_count ---- */ + +/** + * dm_test_rv_set_active_display_count - Test Raven display count forwarding + * @test: KUnit test context + * + * Verify pp_rv_set_active_display_count forwards the count to DPM without + * crashing. + */ +static void dm_test_rv_set_active_display_count(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_active_display_count(&pp_smu, 2); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_min_deep_sleep_dcfclk ---- */ + +/** + * dm_test_rv_set_min_deep_sleep_dcfclk - Test Raven deep sleep clock + * @test: KUnit test context + * + * Verify pp_rv_set_min_deep_sleep_dcfclk forwards the clock value to DPM + * without crashing. + */ +static void dm_test_rv_set_min_deep_sleep_dcfclk(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_min_deep_sleep_dcfclk(&pp_smu, 300); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_hard_min_dcefclk_by_freq ---- */ + +/** + * dm_test_rv_set_hard_min_dcefclk_by_freq - Test Raven hard min DCEFCLK + * @test: KUnit test context + * + * Verify pp_rv_set_hard_min_dcefclk_by_freq forwards the frequency to DPM + * without crashing. + */ +static void dm_test_rv_set_hard_min_dcefclk_by_freq(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_hard_min_dcefclk_by_freq(&pp_smu, 600); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_hard_min_fclk_by_freq ---- */ + +/** + * dm_test_rv_set_hard_min_fclk_by_freq - Test Raven hard min FCLK + * @test: KUnit test context + * + * Verify pp_rv_set_hard_min_fclk_by_freq forwards the frequency to DPM + * without crashing. + */ +static void dm_test_rv_set_hard_min_fclk_by_freq(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_hard_min_fclk_by_freq(&pp_smu, 800); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_nv_set_wm_ranges ---- */ + +/** + * dm_test_nv_set_wm_ranges - Test Navi watermark range forwarding + * @test: KUnit test context + * + * Verify pp_nv_set_wm_ranges forwards ranges to DPM and unconditionally + * returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_set_wm_ranges(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_wm_range_sets ranges = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + ranges.num_reader_wm_sets = 1; + ranges.reader_wm_sets[0].wm_inst = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_wm_ranges(&pp_smu, &ranges), + (int)PP_SMU_RESULT_OK); +} + +/* ---- Tests for pp_nv_set_min_deep_sleep_dcfclk ---- */ + +/** + * dm_test_nv_set_min_deep_sleep_dcfclk_ok - Test successful deep sleep set + * @test: KUnit test context + * + * Verify pp_nv_set_min_deep_sleep_dcfclk returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_nv_set_min_deep_sleep_dcfclk_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_min_deep_sleep_dcfclk(&pp_smu, 300), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_min_deep_sleep_dcfclk_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_min_deep_sleep_dcfclk returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_set_min_deep_sleep_dcfclk_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_min_deep_sleep_dcfclk(&pp_smu, 300), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_min_deep_sleep_dcfclk_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_min_deep_sleep_dcfclk returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_set_min_deep_sleep_dcfclk_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_min_deep_sleep_dcfclk(&pp_smu, 300), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_hard_min_dcefclk_by_freq ---- */ + +/** + * dm_test_nv_set_hard_min_dcefclk_ok - Test successful hard min DCEFCLK + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_dcefclk_by_freq returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_set_hard_min_dcefclk_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_dcefclk_by_freq(&pp_smu, 600), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_hard_min_dcefclk_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_dcefclk_by_freq returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_set_hard_min_dcefclk_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_dcefclk_by_freq(&pp_smu, 600), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_hard_min_dcefclk_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_dcefclk_by_freq returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_set_hard_min_dcefclk_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_dcefclk_by_freq(&pp_smu, 600), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_hard_min_uclk_by_freq ---- */ + +/** + * dm_test_nv_set_hard_min_uclk_ok - Test successful hard min UCLK + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_uclk_by_freq returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_set_hard_min_uclk_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_uclk_by_freq(&pp_smu, 800), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_hard_min_uclk_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_uclk_by_freq returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_set_hard_min_uclk_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_uclk_by_freq(&pp_smu, 800), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_hard_min_uclk_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_uclk_by_freq returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_set_hard_min_uclk_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_uclk_by_freq(&pp_smu, 800), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_get_maximum_sustainable_clocks ---- */ + +/** + * dm_test_nv_get_max_sustainable_clocks_ok - Test successful query + * @test: KUnit test context + * + * Verify pp_nv_get_maximum_sustainable_clocks returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_get_max_sustainable_clocks_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_nv_clock_table max_clocks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_max_sustainable_ret = 0; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_maximum_sustainable_clocks(&pp_smu, &max_clocks), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_get_max_sustainable_clocks_unsupported - Test EOPNOTSUPP + * @test: KUnit test context + * + * Verify pp_nv_get_maximum_sustainable_clocks returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_get_max_sustainable_clocks_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_nv_clock_table max_clocks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_max_sustainable_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_maximum_sustainable_clocks(&pp_smu, &max_clocks), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_get_max_sustainable_clocks_fail - Test generic error + * @test: KUnit test context + * + * Verify pp_nv_get_maximum_sustainable_clocks returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_get_max_sustainable_clocks_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_nv_clock_table max_clocks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_max_sustainable_ret = -EIO; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_maximum_sustainable_clocks(&pp_smu, &max_clocks), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_get_uclk_dpm_states ---- */ + +/** + * dm_test_nv_get_uclk_dpm_states_ok - Test successful DPM states query + * @test: KUnit test context + * + * Verify pp_nv_get_uclk_dpm_states returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_get_uclk_dpm_states_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + unsigned int clock_values[4] = {}; + unsigned int num_states = 0; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_uclk_dpm_ret = 0; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_uclk_dpm_states(&pp_smu, clock_values, &num_states), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_get_uclk_dpm_states_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_get_uclk_dpm_states returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_get_uclk_dpm_states_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + unsigned int clock_values[4] = {}; + unsigned int num_states = 0; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_uclk_dpm_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_uclk_dpm_states(&pp_smu, clock_values, &num_states), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_get_uclk_dpm_states_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_get_uclk_dpm_states returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_get_uclk_dpm_states_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + unsigned int clock_values[4] = {}; + unsigned int num_states = 0; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_uclk_dpm_ret = -EIO; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_uclk_dpm_states(&pp_smu, clock_values, &num_states), + (int)PP_SMU_RESULT_FAIL); +} + static struct kunit_case dm_pp_smu_test_cases[] = { /* get_default_clock_levels */ KUNIT_CASE(dm_test_default_clock_levels_display), @@ -963,6 +2373,73 @@ static struct kunit_case dm_pp_smu_test_cases[] = { KUNIT_CASE(dm_test_nv_clock_id_phyclk), KUNIT_CASE(dm_test_nv_clock_id_pixelclk), KUNIT_CASE(dm_test_nv_clock_id_invalid), + /* dm_pp_apply_display_requirements (DPM enabled) */ + KUNIT_CASE(dm_test_apply_display_requirements_dpm_enabled), + /* dm_pp_get_clock_levels_by_type */ + KUNIT_CASE(dm_test_get_clock_levels_by_type_dpm_error), + KUNIT_CASE(dm_test_get_clock_levels_by_type_success), + KUNIT_CASE(dm_test_get_clock_levels_by_type_validation_fallback), + /* dm_pp_get_clock_levels_by_type_with_latency */ + KUNIT_CASE(dm_test_get_clock_levels_with_latency_success), + KUNIT_CASE(dm_test_get_clock_levels_with_latency_failure), + /* dm_pp_get_clock_levels_by_type_with_voltage */ + KUNIT_CASE(dm_test_get_clock_levels_with_voltage_success), + KUNIT_CASE(dm_test_get_clock_levels_with_voltage_failure), + /* dm_pp_notify_wm_clock_changes */ + KUNIT_CASE(dm_test_notify_wm_clock_changes_polaris), + KUNIT_CASE(dm_test_notify_wm_clock_changes_non_polaris), + /* dm_pp_apply_clock_for_voltage_request (with DPM) */ + KUNIT_CASE(dm_test_apply_clock_for_voltage_success), + KUNIT_CASE(dm_test_apply_clock_for_voltage_eopnotsupp), + KUNIT_CASE(dm_test_apply_clock_for_voltage_fail), + /* pp_nv_set_display_count */ + KUNIT_CASE(dm_test_nv_set_display_count_ok), + KUNIT_CASE(dm_test_nv_set_display_count_unsupported), + KUNIT_CASE(dm_test_nv_set_display_count_fail), + /* pp_nv_set_voltage_by_freq */ + KUNIT_CASE(dm_test_nv_set_voltage_by_freq_ok), + KUNIT_CASE(dm_test_nv_set_voltage_by_freq_invalid_id), + /* pp_nv_set_pstate_handshake_support */ + KUNIT_CASE(dm_test_nv_pstate_handshake_ok), + KUNIT_CASE(dm_test_nv_pstate_handshake_fail), + /* pp_rn_get_dpm_clock_table */ + KUNIT_CASE(dm_test_rn_get_dpm_clock_table_ok), + KUNIT_CASE(dm_test_rn_get_dpm_clock_table_unsupported), + KUNIT_CASE(dm_test_rn_get_dpm_clock_table_fail), + /* pp_rv_set_wm_ranges */ + KUNIT_CASE(dm_test_rv_set_wm_ranges), + /* pp_rv_set_pme_wa_enable */ + KUNIT_CASE(dm_test_rv_set_pme_wa_enable), + /* pp_rv_set_active_display_count */ + KUNIT_CASE(dm_test_rv_set_active_display_count), + /* pp_rv_set_min_deep_sleep_dcfclk */ + KUNIT_CASE(dm_test_rv_set_min_deep_sleep_dcfclk), + /* pp_rv_set_hard_min_dcefclk_by_freq */ + KUNIT_CASE(dm_test_rv_set_hard_min_dcefclk_by_freq), + /* pp_rv_set_hard_min_fclk_by_freq */ + KUNIT_CASE(dm_test_rv_set_hard_min_fclk_by_freq), + /* pp_nv_set_wm_ranges */ + KUNIT_CASE(dm_test_nv_set_wm_ranges), + /* pp_nv_set_min_deep_sleep_dcfclk */ + KUNIT_CASE(dm_test_nv_set_min_deep_sleep_dcfclk_ok), + KUNIT_CASE(dm_test_nv_set_min_deep_sleep_dcfclk_unsupported), + KUNIT_CASE(dm_test_nv_set_min_deep_sleep_dcfclk_fail), + /* pp_nv_set_hard_min_dcefclk_by_freq */ + KUNIT_CASE(dm_test_nv_set_hard_min_dcefclk_ok), + KUNIT_CASE(dm_test_nv_set_hard_min_dcefclk_unsupported), + KUNIT_CASE(dm_test_nv_set_hard_min_dcefclk_fail), + /* pp_nv_set_hard_min_uclk_by_freq */ + KUNIT_CASE(dm_test_nv_set_hard_min_uclk_ok), + KUNIT_CASE(dm_test_nv_set_hard_min_uclk_unsupported), + KUNIT_CASE(dm_test_nv_set_hard_min_uclk_fail), + /* pp_nv_get_maximum_sustainable_clocks */ + KUNIT_CASE(dm_test_nv_get_max_sustainable_clocks_ok), + KUNIT_CASE(dm_test_nv_get_max_sustainable_clocks_unsupported), + KUNIT_CASE(dm_test_nv_get_max_sustainable_clocks_fail), + /* pp_nv_get_uclk_dpm_states */ + 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), {} }; -- cgit v1.2.3 From 04bed7922fa92ad037ff8c5dbafd5cbc7e4f8db0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 17 Jun 2026 18:11:42 -0600 Subject: drm/amd/display: Add KUnit tests for mst_types Add KUnit coverage for the following MST functions: - dm_dp_aux_transfer(): native read/write, partial write, error result remapping, and HPD disconnect quirk via fake DC link service - dm_dp_aux_transfer_result(): error code translation - dm_dp_aux_fill_payload_flags(): request flag decoding - dm_mst_msg_ready_mask(): ESI mask selection - dm_mst_select_esi_dpcd(): DPCD address/length selection - dm_mst_atomic_best_encoder(): encoder selection by CRTC ID - dm_dp_mst_detect(): unregistered connector early return - dm_dp_mst_atomic_check(): no-old-CRTC early return - dm_dp_create_fake_mst_encoders(): encoder init and CRTC mask - dm_handle_mst_sideband_msg_ready_event(): idle no-ready-bits - retrieve_branch_specific_data(): branch OUI parsing - retrieve_downstream_port_device(): downstream port present - needs_dsc_aux_workaround(): DSC workaround matching - dm_mst_get_pbn_divider(): null link guard - amdgpu_dm_mst_reset_mst_connector_setting(): field reset - dm_dp_mst_is_port_support_mode(): FP-off fallback Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 19 +- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 9 +- .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 572 +++++++++++++++++++++ 3 files changed, 593 insertions(+), 7 deletions(-) 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 b6bfe56eeb68..0546efea5de1 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 @@ -99,8 +99,8 @@ EXPORT_IF_KUNIT(dm_dp_aux_fill_payload_flags); /* * This function handles both native AUX and I2C-Over-AUX transactions. */ -static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, - struct drm_dp_aux_msg *msg) +STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) { ssize_t result = 0; struct aux_payload payload; @@ -167,6 +167,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, return result; } +EXPORT_IF_KUNIT(dm_dp_aux_transfer); static void dm_dp_mst_connector_destroy(struct drm_connector *connector) @@ -518,7 +519,7 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) return ret; } -static struct drm_encoder * +STATIC_IFN_KUNIT struct drm_encoder * dm_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state) { @@ -529,8 +530,9 @@ dm_mst_atomic_best_encoder(struct drm_connector *connector, return &adev->dm.mst_encoders[acrtc->crtc_id].base; } +EXPORT_IF_KUNIT(dm_mst_atomic_best_encoder); -static int +STATIC_IFN_KUNIT int dm_dp_mst_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force) { @@ -600,9 +602,10 @@ dm_dp_mst_detect(struct drm_connector *connector, return connection_status; } +EXPORT_IF_KUNIT(dm_dp_mst_detect); -static int dm_dp_mst_atomic_check(struct drm_connector *connector, - struct drm_atomic_commit *state) +STATIC_IFN_KUNIT int dm_dp_mst_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct drm_dp_mst_topology_mgr *mst_mgr = &aconnector->mst_root->mst_mgr; @@ -610,6 +613,7 @@ static int dm_dp_mst_atomic_check(struct drm_connector *connector, return drm_dp_atomic_release_time_slots(state, mst_mgr, mst_port); } +EXPORT_IF_KUNIT(dm_dp_mst_atomic_check); static const struct drm_connector_helper_funcs dm_dp_mst_connector_helper_funcs = { .get_modes = dm_dp_mst_get_modes, @@ -650,6 +654,7 @@ dm_dp_create_fake_mst_encoders(struct amdgpu_device *adev) drm_encoder_helper_add(encoder, &amdgpu_dm_encoder_helper_funcs); } } +EXPORT_IF_KUNIT(dm_dp_create_fake_mst_encoders); static struct drm_connector * dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, @@ -855,6 +860,7 @@ void dm_handle_mst_sideband_msg_ready_event( if (process_count == max_process_count) DRM_DEBUG_DRIVER("Loop exceeded max iterations\n"); } +EXPORT_IF_KUNIT(dm_handle_mst_sideband_msg_ready_event); static void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr) { @@ -2108,3 +2114,4 @@ enum dc_status dm_dp_mst_is_port_support_mode( #endif return DC_OK; } +EXPORT_IF_KUNIT(dm_dp_mst_is_port_support_mode); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index 2aefab5264d0..fecf108a9216 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -64,7 +64,7 @@ struct aux_payload; struct dc_state; struct dc_stream_state; struct dm_atomic_state; -struct drm_atomic_state; +struct drm_atomic_commit; struct drm_dp_mst_topology_mgr; uint32_t dm_mst_get_pbn_divider(struct dc_link *link); @@ -108,8 +108,15 @@ bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector); ssize_t dm_dp_aux_transfer_result(ssize_t result, enum aux_return_code_type operation_result); void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload); +ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg); u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type); void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read); +struct drm_encoder *dm_mst_atomic_best_encoder(struct drm_connector *connector, + struct drm_atomic_commit *state); +int dm_dp_mst_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state); +int dm_dp_mst_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, bool force); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index e3b171992be1..d40ed83d8685 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -7,6 +7,8 @@ #include +#include +#include #include #include #include @@ -18,12 +20,67 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_mst_types.h" +#include "inc/link_service.h" /* * Minimal mock DPCD backing store and AUX transfer callback used to exercise * the DPCD read paths without real hardware. */ static u8 dm_mst_test_dpcd[0x10]; +static u8 dm_mst_test_desc_dpcd[0x10]; +static struct aux_payload dm_mst_test_last_payload; +static int dm_mst_test_aux_transfer_raw_result; +static enum aux_return_code_type dm_mst_test_aux_transfer_raw_operation_result; + +static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc, + struct aux_payload *payload, + enum aux_return_code_type *operation_result) +{ + size_t i; + + dm_mst_test_last_payload = *payload; + *operation_result = dm_mst_test_aux_transfer_raw_operation_result; + + if (dm_mst_test_aux_transfer_raw_result) + return dm_mst_test_aux_transfer_raw_result; + + if (payload->write) + return 0; + + for (i = 0; i < payload->length; i++) + payload->data[i] = dm_mst_test_dpcd[(payload->address + i) & 0xf]; + + return payload->length; +} + +static void dm_mst_test_setup_dm_aux(struct amdgpu_dm_dp_aux *dm_aux, + struct ddc_service *ddc, + struct dc_link *link, + struct dc *dc, + struct link_service *link_srv, + struct dc_context *ctx, + struct amdgpu_device *adev) +{ + memset(&dm_mst_test_last_payload, 0, sizeof(dm_mst_test_last_payload)); + dm_mst_test_aux_transfer_raw_result = 0; + dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_SUCCESS; + link_srv->aux_transfer_raw = dm_mst_test_aux_transfer_raw; + dc->link_srv = link_srv; + link->dc = dc; + ctx->driver_context = adev; + ddc->link = link; + ddc->ctx = ctx; + dm_aux->ddc_service = ddc; + dm_aux->aux.name = "dm_mst_test_dm_aux"; + dm_aux->aux.transfer = dm_dp_aux_transfer; + drm_dp_aux_init(&dm_aux->aux); + drm_dp_dpcd_set_probe(&dm_aux->aux, false); +} + +static const struct dc_link_status *dm_mst_test_get_status(const struct dc_link *link) +{ + return &link->link_status; +} static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) @@ -45,6 +102,21 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, } } +static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + size_t i; + + if ((msg->request & ~DP_AUX_I2C_MOT) != DP_AUX_NATIVE_READ) + return -EINVAL; + + for (i = 0; i < msg->size; i++) + ((u8 *)msg->buffer)[i] = dm_mst_test_desc_dpcd[msg->address + i - DP_BRANCH_OUI]; + + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + /* Tests for needs_dsc_aux_workaround */ /** @@ -285,6 +357,51 @@ static void dm_mst_test_retrieve_branch_no_parent(struct kunit *test) KUNIT_EXPECT_FALSE(test, retrieve_branch_specific_data(aconnector)); } +/** + * dm_mst_test_retrieve_branch_reads_oui - Test branch OUI parsing + * @test: KUnit test context + * + * Verify that retrieve_branch_specific_data() reads the immediate upstream + * branch descriptor and caches its IEEE OUI value on the connector. + */ +static void dm_mst_test_retrieve_branch_reads_oui(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_branch *branch; + struct drm_dp_mst_port *port; + struct drm_dp_aux *aux; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL); + branch = kunit_kzalloc(test, sizeof(*branch), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, mgr); + KUNIT_ASSERT_NOT_NULL(test, branch); + KUNIT_ASSERT_NOT_NULL(test, port); + KUNIT_ASSERT_NOT_NULL(test, aux); + + memset(dm_mst_test_desc_dpcd, 0, sizeof(dm_mst_test_desc_dpcd)); + dm_mst_test_desc_dpcd[0] = 0x12; + dm_mst_test_desc_dpcd[1] = 0x34; + dm_mst_test_desc_dpcd[2] = 0x56; + + aux->name = "dm_mst_test_desc_aux"; + aux->transfer = dm_mst_test_desc_aux_transfer; + drm_dp_aux_init(aux); + drm_dp_dpcd_set_probe(aux, false); + mgr->aux = aux; + port->parent = branch; + port->mgr = mgr; + port->aux.drm_dev = NULL; + aconnector->mst_output_port = port; + + KUNIT_EXPECT_TRUE(test, retrieve_branch_specific_data(aconnector)); + KUNIT_EXPECT_EQ(test, aconnector->branch_ieee_oui, 0x123456U); +} + /** * dm_mst_test_aux_result_success - AUX_RET_SUCCESS preserves the input result. * @test: KUnit test context. @@ -340,6 +457,246 @@ static void dm_mst_test_aux_result_timeout(struct kunit *test) (ssize_t)-ETIMEDOUT); } +/** + * dm_mst_test_aux_transfer_native_read - native AUX read through DM callback. + * @test: KUnit test context. + * + * The DM AUX transfer callback should build a read payload, call the DC link + * service, and return the number of bytes provided by the fake backend. + */ +static void dm_mst_test_aux_transfer_native_read(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[3] = { 0 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + dm_mst_test_dpcd[4] = 0xaa; + dm_mst_test_dpcd[5] = 0xbb; + dm_mst_test_dpcd[6] = 0xcc; + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + + ret = drm_dp_dpcd_read(&dm_aux->aux, 4, buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_EQ(test, buffer[0], (u8)0xaa); + KUNIT_EXPECT_EQ(test, buffer[1], (u8)0xbb); + KUNIT_EXPECT_EQ(test, buffer[2], (u8)0xcc); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.i2c_over_aux); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U); +} + +/** + * dm_mst_test_aux_transfer_native_write - native AUX write through DM callback. + * @test: KUnit test context. + * + * A successful write with an ACK reply should report the requested write size + * and pass a write payload into the fake DC link service. + */ +static void dm_mst_test_aux_transfer_native_write(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 0x11, 0x22 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + + ret = drm_dp_dpcd_write(&dm_aux->aux, 7, buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.i2c_over_aux); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 7U); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.length, + (u32)sizeof(buffer)); +} + +/** + * dm_mst_test_aux_transfer_partial_write - partial write reports byte count. + * @test: KUnit test context. + * + * A positive write result from the DC link service should be interpreted as a + * partial write and replaced with the first payload byte. + */ +static void dm_mst_test_aux_transfer_partial_write(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 1, 0xaa }; + struct drm_dp_aux_msg msg = { + .address = 7, + .request = DP_AUX_NATIVE_WRITE, + .buffer = buffer, + .size = sizeof(buffer), + }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + dm_mst_test_aux_transfer_raw_result = 1; + + ret = dm_dp_aux_transfer(&dm_aux->aux, &msg); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)buffer[0]); + KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 7U); +} + +/** + * dm_mst_test_aux_transfer_error_result - transfer errors are remapped. + * @test: KUnit test context. + * + * A negative DC link service result should be converted through + * dm_dp_aux_transfer_result() using the returned AUX operation result. + */ +static void dm_mst_test_aux_transfer_error_result(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 0 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + dm_mst_test_aux_transfer_raw_result = -EIO; + dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_ERROR_TIMEOUT; + + ret = drm_dp_dpcd_read(&dm_aux->aux, 4, buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)-ETIMEDOUT); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U); +} + +/** + * dm_mst_test_aux_transfer_hpd_discon_quirk - HPD disconnect quirk succeeds. + * @test: KUnit test context. + * + * AUX_RET_ERROR_HPD_DISCON on the sideband down request address should be + * treated as a successful transfer when the platform quirk is enabled. + */ +static void dm_mst_test_aux_transfer_hpd_discon_quirk(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 2, 0 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + adev->dm.aux_hpd_discon_quirk = true; + dm_mst_test_aux_transfer_raw_result = -EIO; + dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_ERROR_HPD_DISCON; + + ret = drm_dp_dpcd_write(&dm_aux->aux, DP_SIDEBAND_MSG_DOWN_REQ_BASE, + buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, + DP_SIDEBAND_MSG_DOWN_REQ_BASE); +} + /** * dm_mst_test_fill_payload_flags_native_write - native write request decode. * @test: KUnit test context. @@ -463,6 +820,203 @@ static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test) (int)(DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI)); } +/** + * dm_mst_test_sideband_msg_ready_no_ready_bits - Test idle sideband event + * @test: KUnit test context + * + * Verify that dm_handle_mst_sideband_msg_ready_event() returns cleanly when + * the ESI read succeeds but no DOWN_REP/UP_REQ ready bits are set. + */ +static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct link_service *link_srv; + struct dc_link *link; + struct dc *dc; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + + mutex_init(&aconnector->handle_mst_msg_ready); + link_srv->get_status = dm_mst_test_get_status; + dc->link_srv = link_srv; + link->dc = dc; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->link_status.dpcd_caps = &link->dpcd_caps; + aconnector->dc_link = link; + aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux"; + aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false); + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, + DOWN_REP_MSG_RDY_EVENT); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); +} + +/** + * dm_mst_test_atomic_best_encoder - Test MST encoder selection + * @test: KUnit test context + * + * Verify that dm_mst_atomic_best_encoder() selects the MST encoder indexed by + * the CRTC ID in the connector's new atomic state. This uses structural DRM + * mocks only; registering connector/CRTC objects is unnecessary for this helper. + */ +static void dm_mst_test_atomic_best_encoder(struct kunit *test) +{ + struct drm_connector_state connector_state = { 0 }; + struct drm_atomic_commit state = { 0 }; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + unsigned int connector_index = 3; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + aconnector->base.dev = &adev->ddev; + aconnector->base.index = connector_index; + acrtc->crtc_id = 2; + connector_state.connector = &aconnector->base; + connector_state.crtc = &acrtc->base; + state.num_connector = connector_index + 1; + state.connectors = kunit_kzalloc(test, + sizeof(*state.connectors) * state.num_connector, + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state.connectors); + state.connectors[connector_index].ptr = &aconnector->base; + state.connectors[connector_index].new_state = &connector_state; + + KUNIT_EXPECT_PTR_EQ(test, dm_mst_atomic_best_encoder(&aconnector->base, &state), + &adev->dm.mst_encoders[2].base); +} + +/** + * dm_mst_test_create_fake_mst_encoders - Test fake MST encoder setup + * @test: KUnit test context + * + * Verify that dm_dp_create_fake_mst_encoders() initializes the requested MST + * encoders as DPMST encoders with the CRTC mask derived from the device state. + */ +static void dm_mst_test_create_fake_mst_encoders(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + int i; + + 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 | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + adev->dm.display_indexes_num = 3; + adev->mode_info.num_crtc = 3; + + dm_dp_create_fake_mst_encoders(adev); + + for (i = 0; i < adev->dm.display_indexes_num; i++) { + struct drm_encoder *encoder = &adev->dm.mst_encoders[i].base; + + KUNIT_EXPECT_PTR_EQ(test, encoder->dev, drm); + KUNIT_EXPECT_EQ(test, encoder->encoder_type, DRM_MODE_ENCODER_DPMST); + KUNIT_EXPECT_EQ(test, encoder->possible_crtcs, 0x7U); + KUNIT_EXPECT_TRUE(test, encoder->helper_private != NULL); + } +} + +/** + * dm_mst_test_atomic_check_no_old_crtc - Test atomic check no-op path + * @test: KUnit test context + * + * Verify that dm_dp_mst_atomic_check() returns success when the MST port's old + * connector state has no CRTC, before MST topology state is required. + */ +static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test) +{ + struct drm_connector_state old_conn_state = { 0 }; + struct drm_connector_state new_conn_state = { 0 }; + struct drm_atomic_commit state = { 0 }; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_dm_connector *root; + struct drm_dp_mst_port *port; + unsigned int connector_index = 2; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + root = kunit_kzalloc(test, sizeof(*root), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, root); + KUNIT_ASSERT_NOT_NULL(test, port); + + aconnector->base.index = connector_index; + aconnector->mst_root = root; + aconnector->mst_output_port = port; + port->connector = &aconnector->base; + old_conn_state.connector = &aconnector->base; + new_conn_state.connector = &aconnector->base; + state.num_connector = connector_index + 1; + state.connectors = kunit_kzalloc(test, + sizeof(*state.connectors) * state.num_connector, + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state.connectors); + state.connectors[connector_index].ptr = &aconnector->base; + state.connectors[connector_index].old_state = &old_conn_state; + state.connectors[connector_index].new_state = &new_conn_state; + + KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, &state), 0); +} + +/** + * dm_mst_test_detect_unregistered - Test detect skips unregistered connector + * @test: KUnit test context + * + * Verify that dm_dp_mst_detect() returns disconnected for an unregistered + * connector before calling into the MST topology helper. + */ +static void dm_mst_test_detect_unregistered(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.registration_state = DRM_CONNECTOR_UNREGISTERED; + + KUNIT_EXPECT_EQ(test, + dm_dp_mst_detect(&aconnector->base, NULL, false), + (int)connector_status_disconnected); +} + +/** + * dm_mst_test_fp_guarded_public_stubs - Test FP-off public fallbacks + * @test: KUnit test context + * + * When CONFIG_DRM_AMD_DC_FP is disabled, the public DSC validation helper + * has no FP body and must return DC_OK without touching its arguments. + */ +static void dm_mst_test_fp_guarded_public_stubs(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_mst_is_port_support_mode(NULL, NULL), + (enum dc_status)DC_OK); +} + static struct kunit_case dm_mst_types_test_cases[] = { /* needs_dsc_aux_workaround tests */ KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match), @@ -480,11 +1034,17 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_retrieve_downstream_present), /* retrieve_branch_specific_data tests */ KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent), + KUNIT_CASE(dm_mst_test_retrieve_branch_reads_oui), /* dm_dp_aux_transfer_result tests */ KUNIT_CASE(dm_mst_test_aux_result_success), KUNIT_CASE(dm_mst_test_aux_result_eio), KUNIT_CASE(dm_mst_test_aux_result_ebusy), KUNIT_CASE(dm_mst_test_aux_result_timeout), + KUNIT_CASE(dm_mst_test_aux_transfer_native_read), + KUNIT_CASE(dm_mst_test_aux_transfer_native_write), + KUNIT_CASE(dm_mst_test_aux_transfer_partial_write), + KUNIT_CASE(dm_mst_test_aux_transfer_error_result), + KUNIT_CASE(dm_mst_test_aux_transfer_hpd_discon_quirk), /* dm_dp_aux_fill_payload_flags tests */ KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write), KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read), @@ -495,6 +1055,18 @@ static struct kunit_case dm_mst_types_test_cases[] = { /* dm_mst_select_esi_dpcd tests */ KUNIT_CASE(dm_mst_test_select_esi_dpcd_legacy), KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi), + /* dm_handle_mst_sideband_msg_ready_event tests */ + KUNIT_CASE(dm_mst_test_sideband_msg_ready_no_ready_bits), + /* dm_mst_atomic_best_encoder tests */ + KUNIT_CASE(dm_mst_test_atomic_best_encoder), + /* dm_dp_create_fake_mst_encoders tests */ + KUNIT_CASE(dm_mst_test_create_fake_mst_encoders), + /* dm_dp_mst_atomic_check tests */ + KUNIT_CASE(dm_mst_test_atomic_check_no_old_crtc), + /* dm_dp_mst_detect tests */ + KUNIT_CASE(dm_mst_test_detect_unregistered), + /* CONFIG_DRM_AMD_DC_FP disabled public paths */ + KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs), {} }; -- cgit v1.2.3 From 9bcf6af12bacb046b712359a770a9302b8856ae6 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Wed, 17 Jun 2026 15:00:40 -0400 Subject: drm/amd/display: hold a vblank ref while writeback is pending Writeback completion is detected in dm_crtc_high_irq(), the CRTC vblank IRQ handler. The arm path (dm_set_writeback) never took a vblank reference, so the interrupt was only enabled incidentally (by a pageflip on the same commit, fbcon, or a previous vblank's off-delay window). A writeback-only commit right after a fresh drm_crtc_vblank_on() (e.g. a writeback connector detached and re-attached) therefore has no vblank reference: the IRQ never fires, wb_pending is never cleared and the out fence times out. This is reproducible with IGT kms_writeback and was seen via kms_colorop on writeback-capable hardware. The relevant IGT branch is at https://gitlab.freedesktop.org/hwentland/igt-gpu-tools/-/tree/yuv-fm-colorop Take a vblank reference when arming the writeback and release it once completion is signalled. The get is done before arming wb_pending so the completion IRQ cannot drop the reference before it is taken. Factor the shared completion bookkeeping into amdgpu_dm_crtc_complete_writeback() and also call it from the teardown path, so a writeback torn down while still pending signals its out fence and releases the reference instead of leaking both. Fixes: c81e13b929df ("drm/amd/display: Hande writeback request from userspace") Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 64 +++++++++++++++++++++- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 45 ++++++--------- 3 files changed, 82 insertions(+), 29 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 6bcd447f4f5d..67b825cbb88f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4498,10 +4498,55 @@ static void amdgpu_dm_crtc_copy_transient_flags(struct drm_crtc_state *crtc_stat stream_state->mode_changed = drm_atomic_crtc_needs_modeset(crtc_state); } +/** + * amdgpu_dm_crtc_complete_writeback - finish a pending writeback job + * @acrtc: the CRTC whose pending writeback should be completed + * + * Clears the pending state, signals the writeback out fence and releases the + * vblank reference taken in dm_set_writeback() while the writeback was armed. + * The pending flag is tested and cleared under the writeback job lock, so this + * is safe to call concurrently from the completion vblank IRQ + * (dm_crtc_high_irq()) and from the writeback teardown path + * (dm_clear_writeback()); only the caller that observes the pending job + * performs the completion. + * + * Return: true if a pending writeback job was completed by this call. + */ +bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) +{ + unsigned long flags; + bool pending; + + if (!acrtc->wb_conn) + return false; + + spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); + pending = acrtc->wb_pending; + acrtc->wb_pending = false; + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + + if (!pending) + return false; + + drm_writeback_signal_completion(acrtc->wb_conn, 0); + drm_crtc_vblank_put(&acrtc->base); + + return true; +} + static void dm_clear_writeback(struct amdgpu_display_manager *dm, + struct amdgpu_crtc *acrtc, struct dm_crtc_state *crtc_state) { dc_stream_remove_writeback(dm->dc, crtc_state->stream, 0); + + /* + * If the writeback is still pending when it is torn down (its + * completion vblank IRQ never fired), signal the out fence so a + * waiting client does not stall and release the vblank reference + * taken in dm_set_writeback(). + */ + amdgpu_dm_crtc_complete_writeback(acrtc); } /** @@ -4654,7 +4699,7 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state, dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); - dm_clear_writeback(dm, dm_old_crtc_state); + dm_clear_writeback(dm, acrtc, dm_old_crtc_state); acrtc->wb_enabled = false; } @@ -4928,9 +4973,24 @@ static void dm_set_writeback(struct amdgpu_display_manager *dm, dc_stream_add_writeback(dm->dc, crtc_state->stream, wb_info); - acrtc->wb_pending = true; acrtc->wb_conn = wb_conn; drm_writeback_queue_job(wb_conn, new_con_state); + + /* + * Writeback completion is detected in the CRTC vblank IRQ + * (dm_crtc_high_irq()). Take a vblank reference so the vblank interrupt + * stays enabled while the writeback is pending; otherwise a + * writeback-only commit right after drm_crtc_vblank_on() (e.g. + * re-enabling a CRTC that was disabled) has no other vblank reference, + * the IRQ never fires and the out fence times out. The matching put + * happens once completion is signalled in dm_crtc_high_irq(), or when + * the writeback is torn down in dm_clear_writeback(). + * + * Arm wb_pending only after the reference is held so the completion IRQ + * cannot run its matching vblank_put before this get. + */ + WARN_ON(drm_crtc_vblank_get(&acrtc->base)); + acrtc->wb_pending = true; } static void amdgpu_dm_update_hdcp(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 2ace3abe15e5..91affbdb2d6c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1120,6 +1120,8 @@ void dm_free_gpu_mem(struct amdgpu_device *adev, 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); void amdgpu_dm_emulated_link_detect(struct dc_link *link); 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 ffaf2b7bc35d..551901c7598a 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 @@ -1965,7 +1965,6 @@ static void dm_crtc_high_irq(void *interrupt_params) { struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; - struct drm_writeback_job *job; struct amdgpu_crtc *acrtc; unsigned long flags; int vrr_active; @@ -1974,32 +1973,24 @@ static void dm_crtc_high_irq(void *interrupt_params) if (!acrtc) return; - if (acrtc->wb_conn) { - spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); - - if (acrtc->wb_pending) { - job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, - struct drm_writeback_job, - list_entry); - acrtc->wb_pending = false; - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - - if (job) { - unsigned int v_total, refresh_hz; - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - - 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); - - drm_writeback_signal_completion(acrtc->wb_conn, 0); - dc_stream_fc_disable_writeback(adev->dm.dc, - acrtc->dm_irq_params.stream, 0); - } - } else - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + 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)) + dc_stream_fc_disable_writeback(adev->dm.dc, + acrtc->dm_irq_params.stream, 0); } vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); -- cgit v1.2.3 From a532f8d7e4c96a3244e75539665637df9f33d8db Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 18 Jun 2026 14:22:27 -0600 Subject: drm/amd/display: Extract shared KUnit test helpers Extract common allocation and setup patterns from KUnit test files into a dedicated helpers module to reduce duplication. Add tests/amdgpu_dm_kunit_helpers.c with shared helpers: - dm_kunit_alloc_adev: allocate amdgpu_device via DRM mock - dm_kunit_alloc_link: allocate zeroed dc_link - dm_kunit_alloc_link_with_ctx: allocate dc_link with dc_context - dm_kunit_alloc_dm: allocate display_manager with DC state - dm_kunit_alloc_stream: allocate dc_stream_state with link - dm_kunit_add_stream_to_state: wire stream into dc_state - dm_kunit_alloc_connector: allocate connector wired to device Update 10 test files to use the shared helpers, removing duplicated local alloc_test_adev, alloc_test_link, alloc_test_dm, alloc_test_stream, and add_test_stream functions. Add missing MODULE_DESCRIPTION() macro to suppress modpost warning: WARNING: modpost: missing MODULE_DESCRIPTION() in amdgpu_dm_kunit_helpers.o Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 21 +-- .../amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 13 +- .../display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 15 +-- .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 19 +-- .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 37 +----- .../display/amdgpu_dm/tests/amdgpu_dm_ism_test.c | 39 ++---- .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 142 +++++++++++++++++++++ .../amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h | 32 +++++ .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 13 +- .../display/amdgpu_dm/tests/amdgpu_dm_psr_test.c | 126 +++++------------- .../amdgpu_dm/tests/amdgpu_dm_replay_test.c | 19 +-- .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 18 +-- 13 files changed, 252 insertions(+), 243 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4d89ad8a6df6..1592e8dae1a9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -11,6 +11,7 @@ ccflags-y += -I$(src)/../../../amdgpu ccflags-y += -I$(src)/../../../amdkfd ccflags-y += -I$(src)/../../../include +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_kunit_helpers.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o 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 0e9de940e5a8..fff50c1325c6 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 @@ -13,6 +13,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "amd_shared.h" #include "dc/inc/hw/panel_cntl.h" @@ -22,16 +23,6 @@ struct dm_backlight_connector_fixture { struct dc_link *link; }; -static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) -{ - struct amdgpu_display_manager *dm; - - dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm); - - return dm; -} - static void setup_test_connector(struct kunit *test, struct dm_backlight_connector_fixture *fixture, int bl_idx, enum signal_type signal) @@ -57,7 +48,7 @@ static void setup_test_connector(struct kunit *test, */ static void dm_test_backlight_device_index_matches_second(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct backlight_device *bd0; struct backlight_device *bd1; @@ -79,7 +70,7 @@ static void dm_test_backlight_device_index_matches_second(struct kunit *test) */ static void dm_test_backlight_device_index_missing_fallback(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct backlight_device *known_bd; struct backlight_device *unknown_bd; @@ -102,7 +93,7 @@ static void dm_test_backlight_device_index_missing_fallback(struct kunit *test) */ static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; caps->caps_valid = true; @@ -125,7 +116,7 @@ static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test) */ static void dm_test_backlight_caps_aux_support_noop(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; caps->caps_valid = false; @@ -146,7 +137,7 @@ static void dm_test_backlight_caps_aux_support_noop(struct kunit *test) */ static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; caps->caps_valid = false; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c index b28a165b213e..2e557ff66818 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -12,6 +12,7 @@ #include "dc.h" #include "amdgpu.h" #include "amdgpu_dm_colorop.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Tests for amdgpu_dm_supported_degam_tfs */ @@ -222,19 +223,11 @@ static void dm_test_initialize_default_pipeline_caps(struct kunit *test, struct amdgpu_device *adev; struct drm_device *drm; struct drm_plane *plane; - struct device *dev; struct dc *dc; int ret; - 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); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); 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 c83bd3e074f1..0edaf969f16b 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 @@ -15,6 +15,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "amdgpu_dm_irq_params.h" /* Tests for amdgpu_dm_crtc_modeset_required() */ @@ -435,23 +436,13 @@ static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) { struct amdgpu_crtc *acrtc; struct amdgpu_device *adev; - 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); + adev = dm_kunit_alloc_adev(test); acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); - acrtc->base.dev = drm; + acrtc->base.dev = &adev->ddev; acrtc->otg_inst = -1; KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), 0); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 14004ff87c9b..33014a2d2222 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -16,6 +16,7 @@ #include "dm_helpers.h" #include "ddc_service_types.h" #include "amdgpu_dm_helpers.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Tests for edid_extract_panel_id() */ @@ -552,26 +553,14 @@ static void dm_test_mst_start_top_mgr_boot(struct kunit *test) { struct amdgpu_dm_connector *aconnector; struct amdgpu_device *adev; - struct drm_device *drm; - struct device *dev; struct dc_link *link; - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + adev = dm_kunit_alloc_adev(test); - 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); + link = dm_kunit_alloc_link(test); - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - aconnector->base.dev = drm; + aconnector = dm_kunit_alloc_connector(test, adev, NULL); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, link); link->priv = aconnector; KUNIT_EXPECT_TRUE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, true)); 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 525caa0b1f6a..a73a6dd146d6 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 @@ -13,6 +13,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "dmub/dmub_srv.h" static void dm_test_irq_handler(void *arg) @@ -778,17 +779,9 @@ static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test) struct amdgpu_crtc *acrtc_a, *acrtc_b; struct amdgpu_device *adev; 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); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; acrtc_a = kunit_kzalloc(test, sizeof(*acrtc_a), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_a); @@ -819,17 +812,9 @@ static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) struct amdgpu_crtc *acrtc; struct amdgpu_device *adev; 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); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); @@ -851,18 +836,8 @@ static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test) { struct amdgpu_device *adev; - 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); + adev = dm_kunit_alloc_adev(test); KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c index f3b3f77aafd5..7dfb3b351d20 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -9,20 +9,7 @@ #include "dc.h" #include "amdgpu_dm_ism.h" - -/* - * Helper: allocate and zero-initialise a dc_stream_state for timing tests. - * Only the timing sub-struct is accessed by the functions under test. - */ -static struct dc_stream_state *alloc_test_stream(struct kunit *test) -{ - struct dc_stream_state *stream; - - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, stream); - - return stream; -} +#include "amdgpu_dm_kunit_test_helpers.h" /* * Helper: allocate and zero-initialise an ISM instance. @@ -275,7 +262,7 @@ static void dm_test_ism_sso_delay_null_stream(struct kunit *test) static void dm_test_ism_sso_delay_zero_frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -288,7 +275,7 @@ static void dm_test_ism_sso_delay_zero_frames(struct kunit *test) static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t expected_one_frame_ns, expected; /* @@ -311,7 +298,7 @@ static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test) static void dm_test_ism_sso_delay_4k60_1frame(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t expected_one_frame_ns; /* @@ -347,7 +334,7 @@ static void dm_test_ism_idle_delay_null_stream(struct kunit *test) static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -361,7 +348,7 @@ static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test) static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -376,7 +363,7 @@ static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test) static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -392,7 +379,7 @@ static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test) static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns; /* @@ -426,7 +413,7 @@ static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test) static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns, expected; /* @@ -461,7 +448,7 @@ static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test) static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns, expected; /* @@ -497,7 +484,7 @@ static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test) static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns; /* @@ -545,7 +532,7 @@ static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test) static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns; /* @@ -586,7 +573,7 @@ static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test) static void dm_test_ism_idle_delay_entry_count_exceeds_history_size(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns, expected; /* 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 new file mode 100644 index 000000000000..58615cdbe854 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit test helpers for amdgpu_dm tests. + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include + +#include "dc.h" +#include "core_types.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test) +{ + 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(struct amdgpu_device), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + return drm_to_adev(drm); +} +EXPORT_SYMBOL(dm_kunit_alloc_adev); + +struct dc_link *dm_kunit_alloc_link(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + return link; +} +EXPORT_SYMBOL(dm_kunit_alloc_link); + +struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test) +{ + struct dc_link *link; + struct dc_context *ctx; + struct dc *dc; + + link = dm_kunit_alloc_link(test); + + 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; + + return link; +} +EXPORT_SYMBOL(dm_kunit_alloc_link_with_ctx); + +struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct dc *dc; + struct dc_state *state; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + dm->dc = dc; + dc->current_state = state; + + return dm; +} +EXPORT_SYMBOL(dm_kunit_alloc_dm); + +struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test, + struct dc_link *link) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + kref_init(&stream->refcount); + + return stream; +} +EXPORT_SYMBOL(dm_kunit_alloc_stream); + +void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state, + unsigned int index, struct dc_link *link) +{ + struct dc_stream_state *stream; + + KUNIT_ASSERT_LT(test, index, (unsigned int)MAX_PIPES); + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + state->streams[index] = stream; + if (state->stream_count <= index) + state->stream_count = index + 1; +} +EXPORT_SYMBOL(dm_kunit_add_stream_to_state); + +struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, + struct amdgpu_device *adev, + struct dc_link *link) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + if (adev) + aconnector->base.dev = &adev->ddev; + aconnector->dc_link = link; + + return aconnector; +} +EXPORT_SYMBOL(dm_kunit_alloc_connector); + +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 new file mode 100644 index 000000000000..0f1c48fa2128 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * KUnit test helpers for amdgpu_dm tests. + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef AMDGPU_DM_KUNIT_TEST_HELPERS_H +#define AMDGPU_DM_KUNIT_TEST_HELPERS_H + +#include + +struct amdgpu_device; +struct amdgpu_display_manager; +struct amdgpu_dm_connector; +struct dc_link; +struct dc_state; +struct dc_stream_state; + +struct amdgpu_device *dm_kunit_alloc_adev(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 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, + unsigned int index, struct dc_link *link); +struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, + struct amdgpu_device *adev, + struct dc_link *link); + +#endif /* AMDGPU_DM_KUNIT_TEST_HELPERS_H */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index d40ed83d8685..a6b4df091e8e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -20,6 +20,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_mst_types.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "inc/link_service.h" /* @@ -914,18 +915,10 @@ static void dm_mst_test_create_fake_mst_encoders(struct kunit *test) { struct amdgpu_device *adev; struct drm_device *drm; - struct device *dev; int i; - 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 | DRIVER_ATOMIC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; adev->dm.display_indexes_num = 3; adev->mode_info.num_crtc = 3; 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 2dd870f650db..09bd98e93047 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 @@ -12,79 +12,17 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_psr.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "power_helpers.h" -/* - * Helper: allocate and zero-initialise a dc_link sufficient for - * amdgpu_dm_psr_fill_caps() testing. The function only accesses - * embedded members (dpcd_caps, psr_settings) so no pointer fields - * need to be wired up. - */ -static struct dc_link *alloc_test_link(struct kunit *test) -{ - struct dc_link *link; - - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, link); - - return link; -} - -/* - * Helper: allocate and wire the minimal DM/DC state needed for - * amdgpu_dm_psr_is_active_allowed() testing. - */ -static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) -{ - struct amdgpu_display_manager *dm; - struct dc *dc; - struct dc_state *state; - - dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm); - - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dc); - - state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state); - - dm->dc = dc; - dc->current_state = state; - - return dm; -} - -static void add_test_stream(struct kunit *test, struct dc_state *state, - unsigned int index, struct dc_link *link) -{ - struct dc_stream_state *stream; - - KUNIT_ASSERT_LT(test, index, (unsigned int)MAX_PIPES); - - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, stream); - - stream->link = link; - state->streams[index] = stream; - if (state->stream_count <= index) - state->stream_count = index + 1; -} - static struct dc_stream_state *alloc_test_psr_stream(struct kunit *test) { - struct dc_stream_state *stream; struct dc_link *link; - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, stream); - - link = alloc_test_link(test); + link = dm_kunit_alloc_link(test); link->psr_settings.psr_feature_enabled = true; - stream->link = link; - kref_init(&stream->refcount); - return stream; + return dm_kunit_alloc_stream(test, link); } static struct core_power *create_test_power_module(struct kunit *test, @@ -108,7 +46,7 @@ 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 = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct dc_context *ctx; struct dc *dc; @@ -359,7 +297,7 @@ static void dm_test_set_psr_caps_no_dpcd_psr(struct kunit *test) static void dm_test_set_psr_caps_edp1_disabled(struct kunit *test) { struct dc_link *link = alloc_test_psr_caps_link(test); - struct dc_link *edp0 = alloc_test_link(test); + struct dc_link *edp0 = dm_kunit_alloc_link(test); struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); struct dc *dc = link->ctx->dc; @@ -393,7 +331,7 @@ static void dm_test_set_psr_caps_success_psr1(struct kunit *test) static void dm_test_psr_fill_caps_version_1(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -406,7 +344,7 @@ static void dm_test_psr_fill_caps_version_1(struct kunit *test) static void dm_test_psr_fill_caps_version_su1(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -419,7 +357,7 @@ static void dm_test_psr_fill_caps_version_su1(struct kunit *test) static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -438,7 +376,7 @@ static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test) static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -452,7 +390,7 @@ static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test) static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -466,7 +404,7 @@ static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test) static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -482,7 +420,7 @@ static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test) static void dm_test_psr_fill_caps_link_training_required(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -495,7 +433,7 @@ static void dm_test_psr_fill_caps_link_training_required(struct kunit *test) static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -510,7 +448,7 @@ static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test) static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -536,7 +474,7 @@ static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test) static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0xFF, sizeof(caps)); @@ -557,7 +495,7 @@ static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test) static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; /* Pre-fill caps with non-zero to verify overwrite */ @@ -570,7 +508,7 @@ static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test) static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -588,7 +526,7 @@ static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) static void dm_test_psr_fill_caps_power_opts_smu_opt_set(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; unsigned int old_feature_mask; @@ -647,7 +585,7 @@ static void dm_test_psr_set_event_psr_not_enabled(struct kunit *test) */ static void dm_test_psr_set_event_get_event_fails(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_stream_state *stream = alloc_test_psr_stream(test); dm->power_module = NULL; @@ -661,7 +599,7 @@ static void dm_test_psr_set_event_get_event_fails(struct kunit *test) */ static void dm_test_psr_set_event_already_set(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_stream_state *stream = alloc_test_psr_stream(test); struct psr_caps caps = {0}; struct core_power *core_power; @@ -682,7 +620,7 @@ static void dm_test_psr_set_event_already_set(struct kunit *test) */ static void dm_test_psr_set_event_updates_event(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_stream_state *stream = alloc_test_psr_stream(test); struct psr_caps caps = {0}; struct core_power *core_power; @@ -706,7 +644,7 @@ static void dm_test_psr_set_event_updates_event(struct kunit *test) */ static void dm_test_psr_is_active_allowed_no_streams(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); } @@ -717,10 +655,10 @@ static void dm_test_psr_is_active_allowed_no_streams(struct kunit *test) */ static void dm_test_psr_is_active_allowed_null_link(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_state *state = dm->dc->current_state; - add_test_stream(test, state, 0, NULL); + dm_kunit_add_stream_to_state(test, state, 0, NULL); KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); } @@ -732,11 +670,11 @@ static void dm_test_psr_is_active_allowed_null_link(struct kunit *test) */ static void dm_test_psr_is_active_allowed_requires_enabled_and_allowed(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_state *state = dm->dc->current_state; - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); - add_test_stream(test, state, 0, link); + dm_kunit_add_stream_to_state(test, state, 0, link); link->psr_settings.psr_allow_active = true; KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); @@ -752,17 +690,17 @@ static void dm_test_psr_is_active_allowed_requires_enabled_and_allowed(struct ku */ static void dm_test_psr_is_active_allowed_any_stream(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_state *state = dm->dc->current_state; - struct dc_link *disabled_link = alloc_test_link(test); - struct dc_link *allowed_link = alloc_test_link(test); + struct dc_link *disabled_link = dm_kunit_alloc_link(test); + struct dc_link *allowed_link = dm_kunit_alloc_link(test); disabled_link->psr_settings.psr_allow_active = true; allowed_link->psr_settings.psr_feature_enabled = true; allowed_link->psr_settings.psr_allow_active = true; - add_test_stream(test, state, 0, disabled_link); - add_test_stream(test, state, 1, allowed_link); + dm_kunit_add_stream_to_state(test, state, 0, disabled_link); + dm_kunit_add_stream_to_state(test, state, 1, allowed_link); KUNIT_EXPECT_TRUE(test, amdgpu_dm_psr_is_active_allowed(dm)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c index 68f2f4d70407..6f633b1bbaca 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c @@ -12,6 +12,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_replay.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "modules/power/power_helpers.h" #include "dmub/dmub_srv.h" @@ -35,8 +36,9 @@ static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); - ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link = dm_kunit_alloc_link_with_ctx(test); + ctx->dc_ctx = ctx->link->ctx; + ctx->dc = ctx->dc_ctx->dc; ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); @@ -44,21 +46,10 @@ static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); - ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->dc); - - ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); - - ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->stream); + ctx->stream = dm_kunit_alloc_stream(test, ctx->link); /* Wire connector state so to_dm_connector_state() works */ ctx->aconnector->base.state = &ctx->dm_state->base; - ctx->link->ctx = ctx->dc_ctx; - ctx->dc_ctx->dc = ctx->dc; - ctx->dc->ctx = ctx->dc_ctx; - ctx->stream->link = ctx->link; return ctx; } 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 f9a839c10bf4..c71f61a2438d 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 @@ -20,6 +20,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Helper functions */ @@ -71,22 +72,7 @@ static struct drm_connector_state *alloc_test_conn_state(struct kunit *test, return conn_state; } -static struct amdgpu_device *alloc_test_adev(struct kunit *test) -{ - 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(struct amdgpu_device), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET | DRIVER_ATOMIC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - - return drm_to_adev(drm); -} /* Tests for amdgpu_dm_wb_encoder_atomic_check */ @@ -350,7 +336,7 @@ static void dm_test_wb_connector_init_success(struct kunit *test) struct dc *dc; int ret; - adev = alloc_test_adev(test); + adev = dm_kunit_alloc_adev(test); adev->mode_info.num_crtc = 1; dm = &adev->dm; dm->adev = adev; -- cgit v1.2.3 From 1cc0fbdd9578cf9755bf571f5a3ef44dbe388dee Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Fri, 19 Jun 2026 00:11:47 -0400 Subject: drm/amd/display: remove dead code related to forcevrr [why] remove the forcevrr related which are not used any more. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 92f84277c522..04d4eaa784ef 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1149,7 +1149,6 @@ struct dc_debug_options { bool validate_dml_output; bool enable_dmcub_surface_flip; bool usbc_combo_phy_reset_wa; - bool force_vrr; bool force_fva; int max_frl_rate; unsigned int force_frl_rate; -- cgit v1.2.3 From 94b9b67fec954dc7cfdb22164ca00e5980307a1e Mon Sep 17 00:00:00 2001 From: Andrew Lichmanov Date: Fri, 19 Jun 2026 15:04:15 -0400 Subject: drm/amd/display: Disable mem gating for DCHVM on DCHVM init [Why] Hang occurs with global gating enabled if req=1 Reviewed-by: Leo Chen Signed-off-by: Andrew Lichmanov Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.c b/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.c index 82d4e3e0e5e8..5e5a7a74346d 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.c +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.c @@ -571,7 +571,7 @@ void dcn35_dchvm_init(struct hubbub *hubbub) if (riommu_active) { // Disable gating and memory power requests - REG_UPDATE(DCHVM_MEM_CTRL, HVM_GPUVMRET_PWR_REQ_DIS, 1); + REG_UPDATE_2(DCHVM_MEM_CTRL, HVM_GPUVMRET_PWR_REQ_DIS, 1, HVM_GPUVMRET_FORCE_REQ, 0); REG_UPDATE_4(DCHVM_CLK_CTRL, HVM_DISPCLK_R_GATE_DIS, 1, HVM_DISPCLK_G_GATE_DIS, 1, -- cgit v1.2.3 From e007830d289836f906ba43bf790e565a2d4066a1 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 15 Jun 2026 18:07:44 -0400 Subject: drm/amd/display: revert "Enable HUBP/DPP power gate for DCN42" [why] Disabling HUBP/DPP Driver PG as it's causing corruption issues. This reverts commit 8b6ab8bdf835efb91c1d782b7c2cf32dad39238f. Reviewed-by: Charlene Liu Signed-off-by: Leo Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c999db12d0a5..7620da96ffc1 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 @@ -729,8 +729,8 @@ static const struct dc_debug_options debug_defaults_drv = { .clock_trace = true, .disable_pplib_clock_request = false, .ignore_pg = false, - .disable_dpp_power_gate = false, - .disable_hubp_power_gate = false, + .disable_dpp_power_gate = true, + .disable_hubp_power_gate = true, .disable_optc_power_gate = true, .disable_dsc_power_gate = false, .disable_dio_power_gate = true, -- cgit v1.2.3 From 2e9e7234f16d09fb075a1742d8d11271a9b98b48 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jun 2026 21:11:18 -0600 Subject: drm/amd/amdgpu: Fix stack frame size warnings in KUnit tests [WHAT] Replace stack-allocated large structs with kunit_kzalloc() in KUnit test functions that exceed the kernel 1280-byte stack frame limit. Also add CONFIG_FRAME_WARN=1024 to .kunitconfig to enforce the limit. Affected structs and files: - struct dc_link in amdgpu_dm_connector_test.c and amdgpu_dm_mst_types_test.c - struct drm_plane, drm_plane_state, drm_framebuffer in amdgpu_dm_plane_test.c - struct drm_connector_state, drm_atomic_state in amdgpu_dm_mst_types_test.c - struct dm_connector_state in amdgpu_dm_test.c Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606230825.9qMV9L0g-lkp@intel.com/ Assisted-by: Copilot:Claude-Opus-4.6 Acked-by: George Zhang Signed-off-by: Alex Hung Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/tests/.kunitconfig | 3 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 64 ++++++++----- .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 104 ++++++++++++--------- .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 99 ++++++++++++-------- .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 72 ++++++++------ 5 files changed, 208 insertions(+), 134 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig index 1e93bd8b44ce..c7c8527dbb10 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig @@ -15,6 +15,9 @@ CONFIG_I2C=y CONFIG_POWER_SUPPLY=y CONFIG_CRC16=y +# Limit stack size to 1280 +CONFIG_FRAME_WARN=1280 + # Treat warnings as errors CONFIG_WERROR=y 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 34e40d2a9d2c..aa451064b30c 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 @@ -29,10 +29,12 @@ */ static void dm_test_subconnector_type_none(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Native); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_Native); } /** @@ -41,10 +43,12 @@ static void dm_test_subconnector_type_none(struct kunit *test) */ static void dm_test_subconnector_type_vga(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_VGA); + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_VGA); } /** @@ -53,10 +57,12 @@ static void dm_test_subconnector_type_vga(struct kunit *test) */ static void dm_test_subconnector_type_dvi_converter(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_DVID); } /** @@ -65,10 +71,12 @@ static void dm_test_subconnector_type_dvi_converter(struct kunit *test) */ static void dm_test_subconnector_type_dvi_dongle(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_DVID); } /** @@ -77,10 +85,12 @@ static void dm_test_subconnector_type_dvi_dongle(struct kunit *test) */ static void dm_test_subconnector_type_hdmi_converter(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); } /** @@ -89,10 +99,12 @@ static void dm_test_subconnector_type_hdmi_converter(struct kunit *test) */ static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); } /** @@ -101,10 +113,12 @@ static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test) */ static void dm_test_subconnector_type_mismatched(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_Unknown); } /** @@ -113,10 +127,12 @@ static void dm_test_subconnector_type_mismatched(struct kunit *test) */ static void dm_test_subconnector_type_default_unknown(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); + link->dpcd_caps.dongle_type = (typeof(link->dpcd_caps.dongle_type))0x7f; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_Unknown); } /* Tests for get_output_content_type() */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index a6b4df091e8e..3a663ee0ca2b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -129,13 +129,15 @@ static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux, */ static void dm_mst_test_needs_dsc_aux_workaround_match(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(link)); } /** @@ -147,13 +149,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_match(struct kunit *test) */ static void dm_mst_test_needs_dsc_aux_workaround_rev12(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_12; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 3; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_12; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 3; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(link)); } /** @@ -165,13 +169,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_rev12(struct kunit *test) */ static void dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.branch_dev_id = 0x123456; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + link->dpcd_caps.branch_dev_id = 0x123456; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /** @@ -183,13 +189,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id(struct kunit *test */ static void dm_mst_test_needs_dsc_aux_workaround_wrong_rev(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = 0x11; /* DPCD 1.1 */ - link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = 0x11; /* DPCD 1.1 */ + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /** @@ -201,13 +209,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_wrong_rev(struct kunit *test) */ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 1; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 1; - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /** @@ -219,13 +229,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *te */ static void dm_mst_test_needs_dsc_aux_workaround_zero_sink_count(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 0; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 0; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /* Tests for dm_mst_get_pbn_divider */ @@ -943,17 +955,23 @@ static void dm_mst_test_create_fake_mst_encoders(struct kunit *test) */ static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test) { - struct drm_connector_state old_conn_state = { 0 }; - struct drm_connector_state new_conn_state = { 0 }; - struct drm_atomic_commit state = { 0 }; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_atomic_commit *state; struct amdgpu_dm_connector *aconnector; struct amdgpu_dm_connector *root; struct drm_dp_mst_port *port; unsigned int connector_index = 2; + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); root = kunit_kzalloc(test, sizeof(*root), GFP_KERNEL); port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, state); KUNIT_ASSERT_NOT_NULL(test, aconnector); KUNIT_ASSERT_NOT_NULL(test, root); KUNIT_ASSERT_NOT_NULL(test, port); @@ -962,18 +980,18 @@ static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test) aconnector->mst_root = root; aconnector->mst_output_port = port; port->connector = &aconnector->base; - old_conn_state.connector = &aconnector->base; - new_conn_state.connector = &aconnector->base; - state.num_connector = connector_index + 1; - state.connectors = kunit_kzalloc(test, - sizeof(*state.connectors) * state.num_connector, + old_conn_state->connector = &aconnector->base; + new_conn_state->connector = &aconnector->base; + state->num_connector = connector_index + 1; + state->connectors = kunit_kzalloc(test, + sizeof(*state->connectors) * state->num_connector, GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state.connectors); - state.connectors[connector_index].ptr = &aconnector->base; - state.connectors[connector_index].old_state = &old_conn_state; - state.connectors[connector_index].new_state = &new_conn_state; + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + state->connectors[connector_index].ptr = &aconnector->base; + state->connectors[connector_index].old_state = old_conn_state; + state->connectors[connector_index].new_state = new_conn_state; - KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, &state), 0); + KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, state), 0); } /** 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 071c28abaa8a..46c9af432e37 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 @@ -185,22 +185,26 @@ static void dm_test_fill_blending_coverage_alpha_format(struct kunit *test) static void dm_test_fill_blending_global_alpha(struct kunit *test) { struct amdgpu_device *adev; - struct drm_plane plane = {0}; - struct drm_plane_state state = { 0 }; + struct drm_plane *plane; + struct drm_plane_state *state; bool per_pixel_alpha; bool pre_multiplied_alpha; bool global_alpha; int global_alpha_value; adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); - plane.dev = &adev->ddev; - state.plane = &plane; - state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; - state.alpha = 0x8000; + plane->dev = &adev->ddev; + state->plane = plane; + state->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state->alpha = 0x8000; - amdgpu_dm_plane_fill_blending_from_plane_state(&state, + amdgpu_dm_plane_fill_blending_from_plane_state(state, &per_pixel_alpha, &pre_multiplied_alpha, &global_alpha, @@ -250,23 +254,28 @@ static void dm_test_modifier_gfx9_swizzle_mode(struct kunit *test) */ static void dm_test_get_plane_formats(struct kunit *test) { - struct drm_plane plane = {0}; - struct dc_plane_cap cap = {0}; + struct drm_plane *plane; + struct dc_plane_cap *cap; uint32_t formats[32] = {0}; - plane.type = DRM_PLANE_TYPE_PRIMARY; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 14); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + cap = kunit_kzalloc(test, sizeof(*cap), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, cap); + + plane->type = DRM_PLANE_TYPE_PRIMARY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, NULL, formats, 32), 14); - cap.pixel_format_support.nv12 = true; - cap.pixel_format_support.p010 = true; - cap.pixel_format_support.fp16 = true; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, &cap, formats, 32), 20); + cap->pixel_format_support.nv12 = true; + cap->pixel_format_support.p010 = true; + cap->pixel_format_support.fp16 = true; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, cap, formats, 32), 20); - plane.type = DRM_PLANE_TYPE_OVERLAY; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 9); + plane->type = DRM_PLANE_TYPE_OVERLAY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, NULL, formats, 32), 9); - plane.type = DRM_PLANE_TYPE_CURSOR; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 1); + plane->type = DRM_PLANE_TYPE_CURSOR; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, NULL, formats, 32), 1); } /** @@ -433,30 +442,36 @@ static void dm_test_get_cursor_position(struct kunit *test) { struct amdgpu_device *adev; struct amdgpu_crtc *amdgpu_crtc; - struct drm_plane plane = {0}; - struct drm_plane_state state = {0}; - struct drm_framebuffer fb = {0}; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_framebuffer *fb; struct dc_cursor_position position = {0}; adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); amdgpu_crtc = kunit_kzalloc(test, sizeof(*amdgpu_crtc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); KUNIT_ASSERT_NOT_NULL(test, amdgpu_crtc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, fb); adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 0); amdgpu_crtc->max_cursor_width = 64; amdgpu_crtc->max_cursor_height = 64; - plane.dev = &adev->ddev; - plane.state = &state; - state.fb = &fb; - state.crtc_x = -5; - state.crtc_y = -7; - state.crtc_w = 32; - state.crtc_h = 32; + plane->dev = &adev->ddev; + plane->state = state; + state->fb = fb; + state->crtc_x = -5; + state->crtc_y = -7; + state->crtc_w = 32; + state->crtc_h = 32; KUNIT_ASSERT_EQ(test, - amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + amdgpu_dm_plane_get_cursor_position(plane, &amdgpu_crtc->base, &position), 0); KUNIT_EXPECT_TRUE(test, position.enable); KUNIT_EXPECT_EQ(test, position.x, 0); @@ -466,10 +481,10 @@ static void dm_test_get_cursor_position(struct kunit *test) KUNIT_EXPECT_TRUE(test, position.translate_by_source); memset(&position, 0, sizeof(position)); - state.crtc_x = -64; - state.crtc_y = 0; + state->crtc_x = -64; + state->crtc_y = 0; KUNIT_ASSERT_EQ(test, - amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + amdgpu_dm_plane_get_cursor_position(plane, &amdgpu_crtc->base, &position), 0); KUNIT_EXPECT_FALSE(test, position.enable); } @@ -483,35 +498,37 @@ static void dm_test_get_cursor_position(struct kunit *test) static void dm_test_format_mod_supported(struct kunit *test) { struct amdgpu_device *adev; - struct drm_plane plane = {0}; + struct drm_plane *plane; uint64_t listed_mod; adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); adev->family = AMDGPU_FAMILY_NV; - plane.dev = &adev->ddev; + plane->dev = &adev->ddev; KUNIT_EXPECT_TRUE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR)); KUNIT_EXPECT_TRUE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_INVALID)); KUNIT_EXPECT_FALSE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_VENDOR_AMD)); listed_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | AMD_FMT_MOD_SET(DCC, 1); - plane.modifiers = &listed_mod; - plane.modifier_count = 1; + plane->modifiers = &listed_mod; + plane->modifier_count = 1; KUNIT_EXPECT_FALSE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_NV12, listed_mod)); + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_NV12, listed_mod)); } /** 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 31194ab42f04..0b29bf0a7d04 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 @@ -452,14 +452,19 @@ static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) */ static void dm_test_scaling_state_same(struct kunit *test) { - struct dm_connector_state a = { 0 }; - struct dm_connector_state b = { 0 }; + struct dm_connector_state *a; + struct dm_connector_state *b; - a.scaling = RMX_FULL; - a.underscan_enable = false; - b = a; + a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); - KUNIT_EXPECT_FALSE(test, is_scaling_state_different(&a, &b)); + a->scaling = RMX_FULL; + a->underscan_enable = false; + *b = *a; + + KUNIT_EXPECT_FALSE(test, is_scaling_state_different(a, b)); } /** @@ -468,13 +473,18 @@ static void dm_test_scaling_state_same(struct kunit *test) */ static void dm_test_scaling_state_scaling_changed(struct kunit *test) { - struct dm_connector_state a = { 0 }; - struct dm_connector_state b = { 0 }; + struct dm_connector_state *a; + struct dm_connector_state *b; + + a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); - a.scaling = RMX_FULL; - b.scaling = RMX_CENTER; + a->scaling = RMX_FULL; + b->scaling = RMX_CENTER; - KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } /** @@ -483,16 +493,21 @@ static void dm_test_scaling_state_scaling_changed(struct kunit *test) */ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) { - struct dm_connector_state old_state = { 0 }; - struct dm_connector_state new_state = { 0 }; + 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); /* new enables underscan with non-zero borders, old has it disabled */ - new_state.underscan_enable = true; - new_state.underscan_hborder = 16; - new_state.underscan_vborder = 16; - old_state.underscan_enable = false; + new_state->underscan_enable = true; + new_state->underscan_hborder = 16; + new_state->underscan_vborder = 16; + old_state->underscan_enable = false; - KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&new_state, &old_state)); + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); } /** @@ -501,16 +516,21 @@ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) */ static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) { - struct dm_connector_state a = { 0 }; - struct dm_connector_state b = { 0 }; + struct dm_connector_state *a; + struct dm_connector_state *b; + + a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); - a.underscan_enable = true; - a.underscan_hborder = 16; - a.underscan_vborder = 16; - b = a; - b.underscan_hborder = 32; + a->underscan_enable = true; + a->underscan_hborder = 16; + a->underscan_vborder = 16; + *b = *a; + b->underscan_hborder = 32; - KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } /* Tests for is_timing_unchanged_for_freesync() */ -- cgit v1.2.3 From 048130c3cec4fb1627d6c8687f1e39d0e0a68c52 Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Fri, 19 Jun 2026 20:14:06 -0500 Subject: drm/amd/display: Promote DC to 3.2.388 This DC patchset brings improvements in multiple areas. In summary, we have: * Fixes on DCN4, encoder, debugfs output, and others * Enhanced KUnit coverage * Code cleanup Acked-by: George Zhang Signed-off-by: Taimur Hassan 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 04d4eaa784ef..13c1f7cd9d7d 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -65,7 +65,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.387" +#define DC_VER "3.2.388" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC -- cgit v1.2.3 From 6e03e0b1abc2338bd815a861c054434c6806a7b7 Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Fri, 26 Jun 2026 14:04:53 +0800 Subject: drm/amdgpu/ras: Resum RAS IP hw init during nps dynamic switch On an XGMI reset-on-init (NPS memory patition mode switch), RAS IP hw fini, sw fini is called but hw init is skipped due to RAS IP block is not included in hwinit mask, so need call RAS IP hw init during XGMI reset-on-init. Signed-off-by: Stanley.Yang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 14 +++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 10 ++++++++++ drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 22 ++++++++++++++++++++++ drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h | 1 + 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 58dd8f29734e..5fb493dd9705 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -3846,7 +3846,14 @@ int amdgpu_ras_init_badpage_info(struct amdgpu_device *adev) if (!con || amdgpu_sriov_vf(adev)) return 0; - if (amdgpu_uniras_enabled(adev)) + /* + * For the reset-on-init path (e.g. an NPS memory partition, + * switch) the RAS IP block hw_init has not been enabled and + * the amdgpu_uniras_enabled return false, check amdgpu ras + * context uniras_enabled flag, eeprom init will be called + * during RAS IP block hw_init. + */ + if (amdgpu_uniras_enabled(adev) || con->uniras_enabled) return 0; control = &con->eeprom_control; @@ -5841,3 +5848,8 @@ void amdgpu_ras_post_reset(struct amdgpu_device *adev, amdgpu_ras_mgr_post_reset(tmp_adev); } } + +void amdgpu_ras_resume_after_reset(struct amdgpu_device *adev) +{ + amdgpu_ras_mgr_resume_after_reset(adev); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h index a86ab65aa2f0..ad24c7cf8936 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h @@ -1045,4 +1045,5 @@ void amdgpu_ras_pre_reset(struct amdgpu_device *adev, struct list_head *device_list); void amdgpu_ras_post_reset(struct amdgpu_device *adev, struct list_head *device_list); +void amdgpu_ras_resume_after_reset(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index e63d05c477a0..fe1b5b47f609 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -1663,6 +1663,16 @@ static void amdgpu_xgmi_reset_on_init_work(struct work_struct *work) if (r && r != -EHWPOISON) dev_err(tmp_adev->dev, "error during bad page data initialization"); + + /* + * For the reset-on-init path (e.g. an NPS memory partition + * switch) the RAS IP block hw_init was skipped under the + * minimal init level, so uniras was never enabled. Bring it + * up now that the reset domain has been unlocked. This is a + * no-op for any other reset path where RAS is already + * initialized, and for non-uniras devices. + */ + amdgpu_ras_resume_after_reset(tmp_adev); } } 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 5b389a92118a..60412da69b2b 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 @@ -465,6 +465,28 @@ static int amdgpu_ras_mgr_hw_fini(struct amdgpu_ip_block *ip_block) return 0; } +int amdgpu_ras_mgr_resume_after_reset(struct amdgpu_device *adev) +{ + struct amdgpu_ras *con = amdgpu_ras_get_context(adev); + struct amdgpu_ras_mgr *ras_mgr = amdgpu_ras_mgr_get_context(adev); + struct amdgpu_ip_block *ip_block; + + if (!con || !con->uniras_enabled) + return 0; + + if (!ras_mgr || !ras_mgr->ras_core) + return -EINVAL; + + if (ras_mgr->ras_is_ready) + return 0; + + ip_block = amdgpu_device_ip_get_ip_block(adev, AMD_IP_BLOCK_TYPE_RAS); + if (!ip_block) + return -EINVAL; + + return amdgpu_ras_mgr_hw_init(ip_block); +} + struct amdgpu_ras_mgr *amdgpu_ras_mgr_get_context(struct amdgpu_device *adev) { if (!adev || !adev->psp.ras_context.ras) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h index 4f44a917d48b..3f80b9f1f0ac 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h @@ -82,6 +82,7 @@ int amdgpu_ras_mgr_handle_ras_cmd(struct amdgpu_device *adev, void *output, uint32_t out_size); int amdgpu_ras_mgr_pre_reset(struct amdgpu_device *adev); int amdgpu_ras_mgr_post_reset(struct amdgpu_device *adev); +int amdgpu_ras_mgr_resume_after_reset(struct amdgpu_device *adev); int amdgpu_ras_mgr_lookup_bad_pages_in_a_row(struct amdgpu_device *adev, uint64_t addr, uint64_t *nps_page_addr, uint32_t max_page_count); #endif -- cgit v1.2.3 From aa7e29cf9a37092cf5057a612ee09f28df20258f Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Wed, 17 Jun 2026 14:42:48 -0400 Subject: drm/amdkfd: add sanity check in svm_range_is_valid to prevent svm range to be overflow or underflow. Signed-off-by: Eric Huang Reviewed-by: Philip Yang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 0900bb23349e..30ad10bbd47e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -3473,7 +3473,13 @@ svm_range_is_valid(struct kfd_process *p, uint64_t start, uint64_t size) unsigned long start_unchg = start; start <<= PAGE_SHIFT; - end = start + (size << PAGE_SHIFT); + + if (size == 0) + return -EINVAL; + + if (check_add_overflow(start, size << PAGE_SHIFT, &end)) + return -EOVERFLOW; + do { vma = vma_lookup(p->mm, start); if (!vma || (vma->vm_flags & device_vma)) -- cgit v1.2.3 From 070e834f97756f2e592005b51d9a7d6104e3298d Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 24 Jun 2026 09:38:28 +0200 Subject: drm/amdgpu: Simplify filtering rings during IP block soft reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of storing pointers to affected rings in an array, just iterate over all rings of the device and filter the affected rings by type using the type mask. This is done to save memory used by the array of affected rings which was sized AMDGPU_MAX_RINGS. Suggested-by: Srinivasan Shanmugam Signed-off-by: Timur Kristóf Reviewed-by: Tvrtko Ursulin Reviewed-by: Srinivasan Shanmugam # for the series Link: https://patch.msgid.link/20260624073829.40835-1-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c | 30 ++---------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 53 +++++++++++++++++++++----------- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 4 +-- 3 files changed, 40 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c index 65505bc50399..99ed0b0d82e9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c @@ -481,28 +481,6 @@ static u32 amdgpu_ring_mask_from_ip(const enum amd_ip_block_type ip_type) } } -/** - * amdgpu_filter_rings() - Filter rings according to a mask. - * - * @adev: amdgpu_device pointer - * @ring_type_mask: Mask of ring types you are looking for - * @out_rings: Array of rings which is going to be filled - * @out_num_rings: Number of rings which were filtered - */ -static void amdgpu_filter_rings(struct amdgpu_device *adev, const u32 ring_type_mask, - struct amdgpu_ring **out_rings, u32 *out_num_rings) -{ - u32 num_rings = 0; - int i; - - for (i = 0; i < adev->num_rings; ++i) { - if (BIT(adev->rings[i]->funcs->type) & ring_type_mask) - out_rings[num_rings++] = adev->rings[i]; - } - - *out_num_rings = num_rings; -} - /** * amdgpu_device_ip_soft_reset() - Perform a graceful soft reset on an IP block. * @@ -524,10 +502,9 @@ int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, struct amdgpu_fence *guilty_fence) { struct amdgpu_device *adev = guilty_ring->adev; - struct amdgpu_ring *rings[AMDGPU_MAX_RINGS]; struct amdgpu_ip_block *ip_block; enum amd_ip_block_type ip_type; - u32 num_rings, ring_type_mask; + u32 ring_type_mask; int r; ip_type = amdgpu_ip_from_ring(guilty_ring->funcs->type); @@ -543,14 +520,13 @@ int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, ip_block->version->funcs->name); ring_type_mask = amdgpu_ring_mask_from_ip(ip_type); - amdgpu_filter_rings(adev, ring_type_mask, rings, &num_rings); amdgpu_device_lock_reset_domain(adev->reset_domain); - amdgpu_multi_ring_reset_helper_begin(rings, num_rings, guilty_ring, guilty_fence); + amdgpu_multi_ring_reset_helper_begin(ring_type_mask, guilty_ring, guilty_fence); r = ip_block->version->funcs->soft_reset(ip_block); - r = amdgpu_multi_ring_reset_helper_end(rings, num_rings, guilty_ring, r); + r = amdgpu_multi_ring_reset_helper_end(ring_type_mask, guilty_ring, r); amdgpu_device_unlock_reset_domain(adev->reset_domain); if (r) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 3f78aa6ed82f..6e6aadba006c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -938,8 +938,7 @@ int amdgpu_ring_reset_helper_end(struct amdgpu_ring *ring, /** * amdgpu_multi_ring_reset_helper_begin() - Prepare multiple rings for a reset. * - * @rings: Pointer to an array of amdgpu rings that are affected. - * @num_rings: Number of rings in the array. + * @ring_type_mask: Bitmask of affected ring types * @guilty_ring: The ring which is guilty of causing a reset. * @guilty_fence: The fence which didn't signal on the guilty ring. * @@ -958,7 +957,7 @@ int amdgpu_ring_reset_helper_end(struct amdgpu_ring *ring, * After the reset is complete, the caller should then call * amdgpu_multi_ring_reset_helper_end() to restore the rings. */ -void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_rings, +void amdgpu_multi_ring_reset_helper_begin(const u32 ring_type_mask, struct amdgpu_ring *guilty_ring, struct amdgpu_fence *guilty_fence) { @@ -969,8 +968,11 @@ void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_ri int i; u32 t; - for (i = 0; i < num_rings; ++i) { - ring = rings[i]; + for (i = 0; i < adev->num_rings; ++i) { + ring = adev->rings[i]; + + if (!(BIT(ring->funcs->type) & ring_type_mask)) + continue; /* Don't accept new submissions on the ring. */ if (amdgpu_ring_sched_ready(ring) && !drm_sched_is_stopped(&ring->sched)) @@ -1003,8 +1005,11 @@ void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_ri rings_busy = false; /* Check if any of the non-guilty rings are busy */ - for (i = 0; i < num_rings; ++i) { - ring = rings[i]; + for (i = 0; i < adev->num_rings; ++i) { + ring = adev->rings[i]; + + if (!(BIT(ring->funcs->type) & ring_type_mask)) + continue; if (ring == guilty_ring) continue; @@ -1020,8 +1025,11 @@ void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_ri mdelay(10); } - for (i = 0; i < num_rings; ++i) { - ring = rings[i]; + for (i = 0; i < adev->num_rings; ++i) { + ring = adev->rings[i]; + + if (!(BIT(ring->funcs->type) & ring_type_mask)) + continue; /* * Find guilty fences, ie. the fences that didn't signal @@ -1045,8 +1053,7 @@ void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_ri /** * amdgpu_multi_ring_reset_helper_end() - Prepare multiple rings for a reset. * - * @rings: Pointer to an array of amdgpu rings that are affected. - * @num_rings: Number of rings in the array. + * @ring_type_mask: Bitmask of affected ring types * @guilty_ring: The ring which is guilty of causing a reset. * @ret: Return code from the reset function. * @@ -1058,7 +1065,7 @@ void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_ri * be called to restore some state, but it won't attempt to * fully restore the ring contents. */ -int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings, +int amdgpu_multi_ring_reset_helper_end(const u32 ring_type_mask, struct amdgpu_ring *guilty_ring, int ret) { struct amdgpu_device *adev = guilty_ring->adev; @@ -1066,8 +1073,11 @@ int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings int i, r; /* Set preempt condition, rings are now allowed to execute submissions */ - for (i = 0; i < num_rings; ++i) { - ring = rings[i]; + for (i = 0; i < adev->num_rings; ++i) { + ring = adev->rings[i]; + + if (!(BIT(ring->funcs->type) & ring_type_mask)) + continue; if (ring->funcs->init_cond_exec) amdgpu_ring_set_preempt_cond_exec(ring, true); @@ -1081,9 +1091,13 @@ int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings return ret; /* Restore contents of all rings */ - for (i = 0; i < num_rings; ++i) { - ring = rings[i]; + for (i = 0; i < adev->num_rings; ++i) { + ring = adev->rings[i]; + + if (!(BIT(ring->funcs->type) & ring_type_mask)) + continue; + /* Restore contents of the ring */ r = amdgpu_ring_reset_helper_end(ring, ring->guilty_fence); if (r) { dev_err(adev->dev, @@ -1094,8 +1108,11 @@ int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings } /* Accept submissions on all rings again */ - for (i = 0; i < num_rings; ++i) { - ring = rings[i]; + for (i = 0; i < adev->num_rings; ++i) { + ring = adev->rings[i]; + + if (!(BIT(ring->funcs->type) & ring_type_mask)) + continue; if (!amdgpu_ring_sched_ready(ring)) continue; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index c272e0b028ad..9d3934b4f106 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -595,10 +595,10 @@ void amdgpu_ring_reset_helper_begin(struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence); int amdgpu_ring_reset_helper_end(struct amdgpu_ring *ring, struct amdgpu_fence *guilty_fence); -void amdgpu_multi_ring_reset_helper_begin(struct amdgpu_ring **rings, u32 num_rings, +void amdgpu_multi_ring_reset_helper_begin(const u32 ring_type_mask, struct amdgpu_ring *guilty_ring, struct amdgpu_fence *guilty_fence); -int amdgpu_multi_ring_reset_helper_end(struct amdgpu_ring **rings, u32 num_rings, +int amdgpu_multi_ring_reset_helper_end(const u32 ring_type_mask, struct amdgpu_ring *guilty_ring, int ret); bool amdgpu_ring_is_reset_type_supported(struct amdgpu_ring *ring, u32 reset_type); -- cgit v1.2.3 From 2ea9fe3021d0bcfe9f3df15fae317c7376c3ab97 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 24 Jun 2026 09:38:29 +0200 Subject: drm/amdgpu: Fix typos in comments for IP block soft reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These typos were accidentally overlooked. Let's fix them now. Signed-off-by: Timur Kristóf Reviewed-by: Tvrtko Ursulin Link: https://patch.msgid.link/20260624073829.40835-2-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 6e6aadba006c..4d417c4a5cd2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -996,7 +996,7 @@ void amdgpu_multi_ring_reset_helper_begin(const u32 ring_type_mask, * Give some time for non-guilty rings to finish their * current submission, to try to minimize collateral damage. * - * Note that this just a best effort, but really there + * Note that this is just a best effort, but really there * is no way to really know which ring is actually responsible * because different rings may share resources, eg. a compute * ring may hog shader engines, causing a graphics ring to hang. @@ -1057,12 +1057,12 @@ void amdgpu_multi_ring_reset_helper_begin(const u32 ring_type_mask, * @guilty_ring: The ring which is guilty of causing a reset. * @ret: Return code from the reset function. * - * After calling amdgpu_multi_ring_reset_helper_end() + * After calling amdgpu_multi_ring_reset_helper_begin() * and executing the actual reset method, call this * function to restore normal operation. * * In case the reset failed, this function should still - * be called to restore some state, but it won't attempt to + * be called to restore preemption state, but it won't attempt to * fully restore the ring contents. */ int amdgpu_multi_ring_reset_helper_end(const u32 ring_type_mask, @@ -1086,7 +1086,7 @@ int amdgpu_multi_ring_reset_helper_end(const u32 ring_type_mask, /* Flush HDP cache so the GPU can see the updated COND_EXEC values */ amdgpu_device_flush_hdp(adev, NULL); - /* If the reset was unsuccessful, return without restoring anything. */ + /* If the reset was unsuccessful, return without restoring anything else. */ if (ret) return ret; -- cgit v1.2.3 From 45510cf662dcf46b5d8926d454f338809f107b9d Mon Sep 17 00:00:00 2001 From: WenTao Liang Date: Fri, 26 Jun 2026 20:45:55 +0800 Subject: drm/amd/display: detect_link_and_local_sink: DP alt mode timeout path leaks prev_sink reference prev_sink is unconditionally retained via dc_sink_retain at function entry, but the DP alt mode timeout path inside SIGNAL_TYPE_DISPLAY_PORT returns false without releasing prev_sink. All other return paths in the function correctly call dc_sink_release(prev_sink), making this the only missing cleanup. Fixes: 54618888d1ea ("drm/amd/display: break down dc_link.c") Signed-off-by: WenTao Liang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260626124555.36910-1-vulab@iscas.ac.cn Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/link/link_detection.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 24b191d39777..281a7c5acaca 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -1164,8 +1164,11 @@ static bool detect_link_and_local_sink(struct dc_link *link, link->link_enc->features.flags.bits.DP_IS_USB_C == 1) { /* if alt mode times out, return false */ - if (!wait_for_entering_dp_alt_mode(link)) + if (!wait_for_entering_dp_alt_mode(link)) { + if (prev_sink) + dc_sink_release(prev_sink); return false; + } } if (!detect_dp(link, &sink_caps, reason)) { -- cgit v1.2.3 From d4af96bef22de297a7c301bebd6625a7f0152b87 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 22 Jun 2026 12:48:07 +0800 Subject: drm/amd/ras: add set_debug_mode function for uniras add set_debug_mode function for uniras v2: 1.Add validation for mp1->ip_func and mp1->ip_func->set_debug_mode 2.Return -ENOTSUPP error code if the callback is missing Signed-off-by: Ce Sun Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 10 ++++++++++ drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h | 1 + .../gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mp1_v13_0.c | 14 ++++++++++++++ drivers/gpu/drm/amd/ras/rascore/ras.h | 3 +++ drivers/gpu/drm/amd/ras/rascore/ras_core.c | 5 +++++ drivers/gpu/drm/amd/ras/rascore/ras_mp1.c | 19 ++++++++++++++++++- drivers/gpu/drm/amd/ras/rascore/ras_mp1.h | 3 +++ drivers/gpu/drm/amd/ras/rascore/ras_mp1_v13_0.c | 13 +++++++++++++ 8 files changed, 67 insertions(+), 1 deletion(-) 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 60412da69b2b..b62bbb5ea292 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 @@ -795,3 +795,13 @@ int amdgpu_ras_mgr_lookup_bad_pages_in_a_row(struct amdgpu_device *adev, return ras_core_convert_soc_pa_to_cur_nps_pages(ras_mgr->ras_core, addr, nps_page_addr, max_page_count); } + +int amdgpu_ras_mgr_set_debug_mode(struct amdgpu_device *adev, bool enable) +{ + struct amdgpu_ras_mgr *ras_mgr = amdgpu_ras_mgr_get_context(adev); + + if (!ras_mgr || !ras_mgr->ras_core || !ras_mgr->ras_is_ready) + return false; + + return ras_core_set_debug_mode(ras_mgr->ras_core, enable); +} diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h index 3f80b9f1f0ac..a20bb8fdce87 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h @@ -85,4 +85,5 @@ int amdgpu_ras_mgr_post_reset(struct amdgpu_device *adev); int amdgpu_ras_mgr_resume_after_reset(struct amdgpu_device *adev); int amdgpu_ras_mgr_lookup_bad_pages_in_a_row(struct amdgpu_device *adev, uint64_t addr, uint64_t *nps_page_addr, uint32_t max_page_count); +int amdgpu_ras_mgr_set_debug_mode(struct amdgpu_device *adev, bool enable); #endif diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mp1_v13_0.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mp1_v13_0.c index 2098f24d4940..3c4575a5d902 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mp1_v13_0.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mp1_v13_0.c @@ -24,6 +24,7 @@ #include "amdgpu_smu.h" #include "amdgpu_reset.h" #include "amdgpu_ras_mp1_v13_0.h" +#include "smu13_driver_if_v13_0_6.h" #define RAS_MP1_MSG_QueryValidMcaCeCount 0x3A #define RAS_MP1_MSG_McaBankCeDumpDW 0x3B @@ -131,10 +132,23 @@ static int mp1_v13_0_get_ras_enabled_mask(struct ras_core_context *ras_core, return ret; } +static int mp1_v13_0_set_debug_mode(struct ras_core_context *ras_core, bool enable) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)ras_core->dev; + int ret; + u32 smu_msg = SMU_MSG_ClearMcaOnRead; + + ret = amdgpu_smu_ras_send_msg(adev, smu_msg, + enable ? 0 : ClearMcaOnRead_UE_FLAG_MASK | + ClearMcaOnRead_CE_POLL_MASK, NULL); + return ret; +} + const struct ras_mp1_sys_func amdgpu_ras_mp1_sys_func_v13_0 = { .mp1_get_valid_bank_count = mp1_v13_0_get_valid_bank_count, .mp1_dump_valid_bank = mp1_v13_0_dump_valid_bank, .mp1_send_eeprom_msg = mp1_v13_0_eeprom_send_msg, .mp1_get_ras_enabled_mask = mp1_v13_0_get_ras_enabled_mask, + .mp1_set_debug_mode = mp1_v13_0_set_debug_mode, }; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras.h b/drivers/gpu/drm/amd/ras/rascore/ras.h index 5869bad978b0..878dfdfcb18a 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras.h @@ -167,6 +167,7 @@ struct ras_mp1_sys_func { enum ras_fw_eeprom_cmd index, uint32_t param, uint32_t *read_arg); int (*mp1_get_ras_enabled_mask)(struct ras_core_context *ras_core, uint64_t *enabled_mask); + int (*mp1_set_debug_mode)(struct ras_core_context *ras_core, bool enable); }; struct ras_eeprom_sys_func { @@ -400,4 +401,6 @@ int ras_core_get_device_system_info(struct ras_core_context *ras_core, int ras_core_convert_soc_pa_to_cur_nps_pages(struct ras_core_context *ras_core, uint64_t soc_pa, uint64_t *page_pfn, uint32_t max_pages); 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); #endif diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_core.c b/drivers/gpu/drm/amd/ras/rascore/ras_core.c index 2346918c7736..c63a358b7e57 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_core.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_core.c @@ -151,6 +151,11 @@ bool ras_core_gpu_is_rma(struct ras_core_context *ras_core) return ras_core->is_rma; } +int ras_core_set_debug_mode(struct ras_core_context *ras_core, bool enable) +{ + return ras_mp1_set_debug_mode(ras_core, enable); +} + static int ras_core_seqno_fifo_write(struct ras_core_context *ras_core, enum ras_seqno_fifo fifo_type, uint64_t seqno) { diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_mp1.c b/drivers/gpu/drm/amd/ras/rascore/ras_mp1.c index f3321df85021..26af09f3574a 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_mp1.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_mp1.c @@ -59,9 +59,20 @@ int ras_mp1_dump_bank(struct ras_core_context *ras_core, return mp1->ip_func->dump_valid_bank(ras_core, type, idx, reg_idx, val); } +int ras_mp1_set_debug_mode(struct ras_core_context *ras_core, bool enable) +{ + struct ras_mp1 *mp1 = &ras_core->ras_mp1; + + if (!mp1->ip_func || !mp1->ip_func->set_debug_mode) + return -EOPNOTSUPP; + + return mp1->ip_func->set_debug_mode(ras_core, enable); +} + int ras_mp1_hw_init(struct ras_core_context *ras_core) { struct ras_mp1 *mp1 = &ras_core->ras_mp1; + int ret = 0; mp1->mp1_ip_version = ras_core->config->mp1_ip_version; mp1->sys_func = ras_core->config->mp1_cfg.mp1_sys_fn; @@ -71,8 +82,14 @@ int ras_mp1_hw_init(struct ras_core_context *ras_core) } mp1->ip_func = ras_mp1_get_ip_funcs(ras_core, mp1->mp1_ip_version); + if (!mp1->ip_func) + return -EINVAL; + + ret = ras_mp1_set_debug_mode(ras_core, false); + if (ret) + return -EINVAL; - return mp1->ip_func ? RAS_CORE_OK : -EINVAL; + return ret; } int ras_mp1_hw_fini(struct ras_core_context *ras_core) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_mp1.h b/drivers/gpu/drm/amd/ras/rascore/ras_mp1.h index de1d08286f41..5bc7c1b7fdab 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_mp1.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_mp1.h @@ -31,6 +31,7 @@ struct ras_mp1_ip_func { enum ras_err_type type, u32 *count); int (*dump_valid_bank)(struct ras_core_context *ras_core, enum ras_err_type type, u32 idx, u32 reg_idx, u64 *val); + int (*set_debug_mode)(struct ras_core_context *ras_core, bool enable); }; struct ras_mp1 { @@ -47,4 +48,6 @@ int ras_mp1_get_bank_count(struct ras_core_context *ras_core, int ras_mp1_dump_bank(struct ras_core_context *ras_core, u32 ecc_type, u32 idx, u32 reg_idx, u64 *val); + +int ras_mp1_set_debug_mode(struct ras_core_context *ras_core, bool enable); #endif diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_mp1_v13_0.c b/drivers/gpu/drm/amd/ras/rascore/ras_mp1_v13_0.c index 310d39fc816b..1fcfc1995ad3 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_mp1_v13_0.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_mp1_v13_0.c @@ -99,7 +99,20 @@ static int mp1_v13_0_dump_bank(struct ras_core_context *ras_core, return sys_func->mp1_dump_valid_bank(ras_core, msg, idx, reg_idx, val); } +static int mp1_v13_0_set_debug_mode(struct ras_core_context *ras_core, bool enable) +{ + struct ras_mp1 *mp1 = &ras_core->ras_mp1; + const struct ras_mp1_sys_func *sys_func = mp1->sys_func; + + if (!sys_func || !sys_func->mp1_set_debug_mode) + return -RAS_CORE_NOT_SUPPORTED; + + return sys_func->mp1_set_debug_mode(ras_core, enable); +} + + const struct ras_mp1_ip_func mp1_ras_func_v13_0 = { .get_valid_bank_count = mp1_v13_0_get_bank_count, .dump_valid_bank = mp1_v13_0_dump_bank, + .set_debug_mode = mp1_v13_0_set_debug_mode, }; -- cgit v1.2.3 From 9d5f1c0db1d37db24bb9556dd1e433eb30fbd3b6 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Thu, 25 Jun 2026 23:09:10 -0400 Subject: drm/amdgpu: Fix false error return to non-KCQ amdgpu_gfx_reset_mes_compute is used to coordinate suspend_all, reset, and resume_all between KCQ and compute user queues. When a hung queue comes from the compute user queues and the reset is successful, the KCQ failure after reset should be sent to KCQ only and not the compute user queues. Compute user queues can operate after a successful reset without a mode reset. Fixes: a4e4d945cba8 ("drm/amdgpu/gfx: defer per-queue helper_end until after MES resume") Signed-off-by: Amber Lin Acked-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 982b41606d48..419992589df3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -2282,6 +2282,7 @@ int amdgpu_gfx_reset_mes_compute(struct amdgpu_device *adev, struct mes_remove_queue_input *queue_input = (struct mes_remove_queue_input *)faulty_queue_input; struct amdgpu_gfx_deferred_entry deferred_end[AMDGPU_MAX_COMPUTE_RINGS + 1]; int n_deferred = 0; + int ring_err; guard(mutex)(&adev->gfx.mec.reset_mutex); /* stop the drm schedulers for all compute queues */ @@ -2375,17 +2376,23 @@ out: /* Now CP is running again — replay backed-up commands and ring * doorbells on each reset queue. */ + ring_err = r; for (i = 0; i < n_deferred; i++) { int er = amdgpu_ring_reset_helper_end(deferred_end[i].ring, deferred_end[i].fence); - if (er && !r) - r = er; + + if (er && !ring_err) + ring_err = er; } - if (!r) + if (!ring_err) amdgpu_gfx_reset_start_compute_scheds(adev, ring); - return r; + /* If this reset is triggered by non-KCQ, the KCQ result after resume must + * not override the reset result; otherwise a false reset failure is returned + * to the non-KCQ caller + */ + return ring ? ring_err : r; } int amdgpu_gfx_cleaner_shader_sw_init(struct amdgpu_device *adev, -- cgit v1.2.3 From 89db46e455abf1654f88d36e5429cb408abbc95e Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 24 Jun 2026 12:32:18 +1000 Subject: drm/amdgpu,amdkfd: correct setting MES queue type MES ADD_QUEUE programs the firmware with the queue type from the driver input, but MES REMOVE_QUEUE leaves queue_type at the zero-initialized value. Zero decodes as GFX in the MES REMOVE_QUEUE packet. That means removing a KFD compute queue can be submitted to MES as a GFX queue. In a debug-trap suspend/remove sequence this can leave MES looking for the doorbell in the wrong queue class and the REMOVE_QUEUE command may never complete. The observed failing packet removed doorbell 0x1002 with queue_type=GFX even though the corresponding ADD_QUEUE for the same doorbell was queue_type=COMPUTE. Populate REMOVE_QUEUE.queue_type the same way ADD_QUEUE does. Signed-off-by: Geoffrey McRae Reviewed-by: Sunil Khatri Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 1 + drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 1 + drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/mes_v12_1.c | 3 +++ drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 2 ++ 6 files changed, 11 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 5255360353f4..dbedb1e47c3f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -274,6 +274,7 @@ struct mes_remove_queue_input { uint32_t xcc_id; uint32_t doorbell_offset; uint64_t gang_context_addr; + uint32_t queue_type; bool remove_queue_after_reset; }; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index dba3707c2659..e947c16e694d 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -170,6 +170,7 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); queue_input.doorbell_offset = queue->doorbell_index; queue_input.gang_context_addr = ctx->gpu_addr; + queue_input.queue_type = queue->queue_type; amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->remove_hw_queue(&adev->mes, &queue_input); diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 9e27d01cbfa3..76e6769cf7ac 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -383,6 +383,8 @@ static int mes_v11_0_remove_hw_queue(struct amdgpu_mes *mes, mes_remove_queue_pkt.doorbell_offset = input->doorbell_offset; mes_remove_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_remove_queue_pkt.queue_type = + convert_to_mes_queue_type(input->queue_type); if (mes_rev >= 0x60) mes_remove_queue_pkt.remove_queue_after_reset = input->remove_queue_after_reset; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 20f4fd57b1da..1b0c649d97a2 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -371,6 +371,8 @@ static int mes_v12_0_remove_hw_queue(struct amdgpu_mes *mes, mes_remove_queue_pkt.doorbell_offset = input->doorbell_offset; mes_remove_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_remove_queue_pkt.queue_type = + convert_to_mes_queue_type(input->queue_type); if (mes_rev >= 0x5a) mes_remove_queue_pkt.remove_queue_after_reset = input->remove_queue_after_reset; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c index 8007a6e69305..c449efa70b60 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c @@ -362,6 +362,8 @@ static int mes_v12_1_remove_hw_queue(struct amdgpu_mes *mes, mes_remove_queue_pkt.doorbell_offset = input->doorbell_offset; mes_remove_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_remove_queue_pkt.queue_type = + convert_to_mes_queue_type(input->queue_type); return mes_v12_1_submit_pkt_and_poll_completion(mes, xcc_id, AMDGPU_MES_SCHED_PIPE, @@ -2270,6 +2272,7 @@ static int mes_v12_1_test_queue(struct amdgpu_device *adev, int xcc_id, remove_queue.xcc_id = xcc_id; remove_queue.doorbell_offset = doorbell_idx; remove_queue.gang_context_addr = add_queue.gang_context_addr; + remove_queue.queue_type = queue_type; r = mes_v12_1_remove_hw_queue(&adev->mes, &remove_queue); error: 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 ce28a7c77704..9dc65d5fb2b3 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -299,6 +299,7 @@ static int remove_queue_mes_on_reset_option(struct device_queue_manager *dqm, st memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); queue_input.doorbell_offset = q->properties.doorbell_off; queue_input.gang_context_addr = q->gang_ctx_gpu_addr; + queue_input.queue_type = convert_to_mes_queue_type(q->properties.type); queue_input.remove_queue_after_reset = flush_mes_queue; queue_input.xcc_id = ffs(dqm->dev->xcc_mask) - 1; @@ -467,6 +468,7 @@ static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); queue_input.doorbell_offset = q->properties.doorbell_off; queue_input.gang_context_addr = q->gang_ctx_gpu_addr; + queue_input.queue_type = convert_to_mes_queue_type(q->properties.type); queue_input.remove_queue_after_reset = false; queue_input.xcc_id = ffs(dqm->dev->xcc_mask) - 1; /* pass the known bad queue info to the reset function */ -- cgit v1.2.3 From 20f7f9b6fb40b55f94f75849baa30c899fb3a0ed Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 24 Jun 2026 12:34:06 +1000 Subject: drm/amdkfd: use amdgpu ring types for MES queue The MES interface takes queue types as enum amdgpu_ring_type values. The MES backend is responsible for converting those values to firmware-facing MES_QUEUE_TYPE values when building MES packets. The KFD queue manager was converting KFD queue types directly to MES_QUEUE_TYPE values before filling the MES input structures. That is the wrong abstraction level for the generic MES interface. Change the KFD helper to return AMDGPU_RING_TYPE_* values and rename it to make the expected type explicit. Use the helper for the add, remove, and reset MES paths. Signed-off-by: Geoffrey McRae Reviewed-by: Sunil Khatri Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 9dc65d5fb2b3..97402e6c8f83 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -37,7 +37,8 @@ #include "amdgpu_amdkfd.h" #include "amdgpu_reset.h" #include "amdgpu_sdma.h" -#include "mes_v11_api_def.h" +#include "amdgpu_ring.h" +#include "amdgpu_mes.h" #include "kfd_debug.h" /* Size of the per-pipe EOP queue */ @@ -183,24 +184,24 @@ static void kfd_hws_hang(struct device_queue_manager *dqm) amdgpu_amdkfd_gpu_reset(dqm->dev->adev); } -static int convert_to_mes_queue_type(int queue_type) +static int convert_to_amdgpu_ring_type(int queue_type) { - int mes_queue_type; + int amdgpu_ring_type; switch (queue_type) { case KFD_QUEUE_TYPE_COMPUTE: - mes_queue_type = MES_QUEUE_TYPE_COMPUTE; + amdgpu_ring_type = AMDGPU_RING_TYPE_COMPUTE; break; case KFD_QUEUE_TYPE_SDMA: - mes_queue_type = MES_QUEUE_TYPE_SDMA; + amdgpu_ring_type = AMDGPU_RING_TYPE_SDMA; break; default: WARN(1, "Invalid queue type %d", queue_type); - mes_queue_type = -EINVAL; + amdgpu_ring_type = -EINVAL; break; } - return mes_queue_type; + return amdgpu_ring_type; } static int add_queue_mes(struct device_queue_manager *dqm, struct queue *q, @@ -250,7 +251,7 @@ static int add_queue_mes(struct device_queue_manager *dqm, struct queue *q, (qpd->pqm->process->debug_trap_enabled || kfd_dbg_has_ttmps_always_setup(q->device)); - queue_type = convert_to_mes_queue_type(q->properties.type); + queue_type = convert_to_amdgpu_ring_type(q->properties.type); if (queue_type < 0) { dev_err(adev->dev, "Queue type not supported with MES, queue:%d\n", q->properties.type); @@ -299,7 +300,7 @@ static int remove_queue_mes_on_reset_option(struct device_queue_manager *dqm, st memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); queue_input.doorbell_offset = q->properties.doorbell_off; queue_input.gang_context_addr = q->gang_ctx_gpu_addr; - queue_input.queue_type = convert_to_mes_queue_type(q->properties.type); + queue_input.queue_type = convert_to_amdgpu_ring_type(q->properties.type); queue_input.remove_queue_after_reset = flush_mes_queue; queue_input.xcc_id = ffs(dqm->dev->xcc_mask) - 1; @@ -468,7 +469,7 @@ static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); queue_input.doorbell_offset = q->properties.doorbell_off; queue_input.gang_context_addr = q->gang_ctx_gpu_addr; - queue_input.queue_type = convert_to_mes_queue_type(q->properties.type); + queue_input.queue_type = convert_to_amdgpu_ring_type(q->properties.type); queue_input.remove_queue_after_reset = false; queue_input.xcc_id = ffs(dqm->dev->xcc_mask) - 1; /* pass the known bad queue info to the reset function */ -- cgit v1.2.3 From e38837e7e70e72ae7765b81e0699e4da8728ad83 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 09:35:58 +0800 Subject: drm/amdgpu: Retire legacy page retirement RAS code Remove the deprecated legacy RAS code path for page retirement Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 304 +------------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h | 23 --- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 13 +- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 17 -- 4 files changed, 2 insertions(+), 355 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 5fb493dd9705..3b864a0b70c2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -128,12 +128,6 @@ const char *get_ras_block_str(struct ras_common_if *ras_block) /* typical ECC bad page rate is 1 bad page per 100MB VRAM */ #define RAS_BAD_PAGE_COVER (100 * 1024 * 1024ULL) -#define MAX_UMC_POISON_POLLING_TIME_ASYNC 10 - -#define AMDGPU_RAS_RETIRE_PAGE_INTERVAL 100 //ms - -#define MAX_FLUSH_RETIRE_DWORK_TIMES 100 - #define BYPASS_ALLOCATED_ADDRESS 0x0 #define BYPASS_INITIALIZATION_ADDRESS 0x1 @@ -2489,14 +2483,6 @@ static void amdgpu_ras_interrupt_poison_creation_handler(struct ras_manager *obj event_id = amdgpu_ras_acquire_event_id(adev, type); RAS_EVENT_LOG(adev, event_id, "Poison is created\n"); - if (amdgpu_ip_version(obj->adev, UMC_HWIP, 0) >= IP_VERSION(12, 0, 0)) { - struct amdgpu_ras *con = amdgpu_ras_get_context(obj->adev); - - atomic_inc(&con->page_retirement_req_cnt); - atomic_inc(&con->poison_creation_count); - - wake_up(&con->page_retirement_wq); - } } static void amdgpu_ras_interrupt_umc_handler(struct ras_manager *obj, @@ -3550,38 +3536,6 @@ static void amdgpu_ras_validate_threshold(struct amdgpu_device *adev, } } -int amdgpu_ras_put_poison_req(struct amdgpu_device *adev, - enum amdgpu_ras_block block, uint16_t pasid, - pasid_notify pasid_fn, void *data, uint32_t reset) -{ - int ret = 0; - struct ras_poison_msg poison_msg; - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - - memset(&poison_msg, 0, sizeof(poison_msg)); - poison_msg.block = block; - poison_msg.pasid = pasid; - poison_msg.reset = reset; - poison_msg.pasid_fn = pasid_fn; - poison_msg.data = data; - - ret = kfifo_put(&con->poison_fifo, poison_msg); - if (!ret) { - dev_err(adev->dev, "Poison message fifo is full!\n"); - return -ENOSPC; - } - - return 0; -} - -static int amdgpu_ras_get_poison_req(struct amdgpu_device *adev, - struct ras_poison_msg *poison_msg) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - - return kfifo_get(&con->poison_fifo, poison_msg); -} - static void amdgpu_ras_ecc_log_init(struct ras_ecc_log_info *ecc_log) { mutex_init(&ecc_log->lock); @@ -3611,232 +3565,6 @@ static void amdgpu_ras_ecc_log_fini(struct ras_ecc_log_info *ecc_log) ecc_log->consumption_q_count = 0; } -static bool amdgpu_ras_schedule_retirement_dwork(struct amdgpu_ras *con, - uint32_t delayed_ms) -{ - int ret; - - mutex_lock(&con->umc_ecc_log.lock); - ret = radix_tree_tagged(&con->umc_ecc_log.de_page_tree, - UMC_ECC_NEW_DETECTED_TAG); - mutex_unlock(&con->umc_ecc_log.lock); - - if (ret) - schedule_delayed_work(&con->page_retirement_dwork, - msecs_to_jiffies(delayed_ms)); - - return ret ? true : false; -} - -static void amdgpu_ras_do_page_retirement(struct work_struct *work) -{ - struct amdgpu_ras *con = container_of(work, struct amdgpu_ras, - page_retirement_dwork.work); - struct amdgpu_device *adev = con->adev; - struct ras_err_data err_data; - - /* If gpu reset is ongoing, delay retiring the bad pages */ - if (amdgpu_in_reset(adev) || amdgpu_ras_in_recovery(adev)) { - amdgpu_ras_schedule_retirement_dwork(con, - AMDGPU_RAS_RETIRE_PAGE_INTERVAL * 3); - return; - } - - amdgpu_ras_error_data_init(&err_data); - - amdgpu_umc_handle_bad_pages(adev, &err_data); - - amdgpu_ras_error_data_fini(&err_data); - - amdgpu_ras_schedule_retirement_dwork(con, - AMDGPU_RAS_RETIRE_PAGE_INTERVAL); -} - -static int amdgpu_ras_poison_creation_handler(struct amdgpu_device *adev, - uint32_t poison_creation_count) -{ - int ret = 0; - struct ras_ecc_log_info *ecc_log; - struct ras_query_if info; - u32 timeout = MAX_UMC_POISON_POLLING_TIME_ASYNC; - struct amdgpu_ras *ras = amdgpu_ras_get_context(adev); - u64 de_queried_count; - u64 consumption_q_count; - enum ras_event_type type = RAS_EVENT_TYPE_POISON_CREATION; - - memset(&info, 0, sizeof(info)); - info.head.block = AMDGPU_RAS_BLOCK__UMC; - - ecc_log = &ras->umc_ecc_log; - ecc_log->de_queried_count = 0; - ecc_log->consumption_q_count = 0; - - do { - ret = amdgpu_ras_query_error_status_with_event(adev, &info, type); - if (ret) - return ret; - - de_queried_count = ecc_log->de_queried_count; - consumption_q_count = ecc_log->consumption_q_count; - - if (de_queried_count && consumption_q_count) - break; - - msleep(100); - } while (--timeout); - - if (de_queried_count) - schedule_delayed_work(&ras->page_retirement_dwork, 0); - - if (amdgpu_ras_is_rma(adev) && atomic_cmpxchg(&ras->rma_in_recovery, 0, 1) == 0) - amdgpu_ras_reset_gpu(adev); - - return 0; -} - -static void amdgpu_ras_clear_poison_fifo(struct amdgpu_device *adev) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - struct ras_poison_msg msg; - int ret; - - do { - ret = kfifo_get(&con->poison_fifo, &msg); - } while (ret); -} - -static int amdgpu_ras_poison_consumption_handler(struct amdgpu_device *adev, - uint32_t msg_count, uint32_t *gpu_reset) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - uint32_t reset_flags = 0, reset = 0; - struct ras_poison_msg msg; - int ret, i; - - kgd2kfd_set_sram_ecc_flag(adev->kfd.dev); - - for (i = 0; i < msg_count; i++) { - ret = amdgpu_ras_get_poison_req(adev, &msg); - if (!ret) - continue; - - if (msg.pasid_fn) - msg.pasid_fn(adev, msg.pasid, msg.data); - - reset_flags |= msg.reset; - } - - /* - * Try to ensure poison creation handler is completed first - * to set rma if bad page exceed threshold. - */ - flush_delayed_work(&con->page_retirement_dwork); - - /* for RMA, amdgpu_ras_poison_creation_handler will trigger gpu reset */ - if (reset_flags && !amdgpu_ras_is_rma(adev)) { - if (reset_flags & AMDGPU_RAS_GPU_RESET_MODE1_RESET) - reset = AMDGPU_RAS_GPU_RESET_MODE1_RESET; - else if (reset_flags & AMDGPU_RAS_GPU_RESET_MODE2_RESET) - reset = AMDGPU_RAS_GPU_RESET_MODE2_RESET; - else - reset = reset_flags; - - con->gpu_reset_flags |= reset; - amdgpu_ras_reset_gpu(adev); - - *gpu_reset = reset; - - /* Wait for gpu recovery to complete */ - flush_work(&con->recovery_work); - } - - return 0; -} - -static int amdgpu_ras_page_retirement_thread(void *param) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)param; - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - uint32_t poison_creation_count, msg_count; - uint32_t gpu_reset; - int ret; - - while (!kthread_should_stop()) { - - wait_event_interruptible(con->page_retirement_wq, - kthread_should_stop() || - atomic_read(&con->page_retirement_req_cnt)); - - if (kthread_should_stop()) - break; - - mutex_lock(&con->poison_lock); - gpu_reset = 0; - - do { - poison_creation_count = atomic_read(&con->poison_creation_count); - ret = amdgpu_ras_poison_creation_handler(adev, poison_creation_count); - if (ret == -EIO) - break; - - if (poison_creation_count) { - atomic_sub(poison_creation_count, &con->poison_creation_count); - atomic_sub(poison_creation_count, &con->page_retirement_req_cnt); - } - } while (atomic_read(&con->poison_creation_count) && - !atomic_read(&con->poison_consumption_count)); - - if (ret != -EIO) { - msg_count = kfifo_len(&con->poison_fifo); - if (msg_count) { - ret = amdgpu_ras_poison_consumption_handler(adev, - msg_count, &gpu_reset); - if ((ret != -EIO) && - (gpu_reset != AMDGPU_RAS_GPU_RESET_MODE1_RESET)) - atomic_sub(msg_count, &con->page_retirement_req_cnt); - } - } - - if ((ret == -EIO) || (gpu_reset == AMDGPU_RAS_GPU_RESET_MODE1_RESET)) { - /* gpu mode-1 reset is ongoing or just completed ras mode-1 reset */ - /* Clear poison creation request */ - atomic_set(&con->poison_creation_count, 0); - atomic_set(&con->poison_consumption_count, 0); - - /* Clear poison fifo */ - amdgpu_ras_clear_poison_fifo(adev); - - /* Clear all poison requests */ - atomic_set(&con->page_retirement_req_cnt, 0); - - if (ret == -EIO) { - /* Wait for mode-1 reset to complete */ - down_read(&adev->reset_domain->sem); - up_read(&adev->reset_domain->sem); - } - - /* Wake up work to save bad pages to eeprom */ - schedule_delayed_work(&con->page_retirement_dwork, 0); - } else if (gpu_reset) { - /* gpu just completed mode-2 reset or other reset */ - /* Clear poison consumption messages cached in fifo */ - msg_count = kfifo_len(&con->poison_fifo); - if (msg_count) { - amdgpu_ras_clear_poison_fifo(adev); - atomic_sub(msg_count, &con->page_retirement_req_cnt); - } - - atomic_set(&con->poison_consumption_count, 0); - - /* Wake up work to save bad pages to eeprom */ - schedule_delayed_work(&con->page_retirement_dwork, 0); - } - mutex_unlock(&con->poison_lock); - } - - return 0; -} - int amdgpu_ras_init_badpage_info(struct amdgpu_device *adev) { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); @@ -3924,10 +3652,8 @@ int amdgpu_ras_recovery_init(struct amdgpu_device *adev, bool init_bp_info) } mutex_init(&con->recovery_lock); - mutex_init(&con->poison_lock); INIT_WORK(&con->recovery_work, amdgpu_ras_do_recovery); atomic_set(&con->in_recovery, 0); - atomic_set(&con->rma_in_recovery, 0); con->eeprom_control.bad_channel_bitmap = 0; max_eeprom_records_count = amdgpu_ras_eeprom_max_record_count(&con->eeprom_control); @@ -3940,20 +3666,8 @@ int amdgpu_ras_recovery_init(struct amdgpu_device *adev, bool init_bp_info) } mutex_init(&con->page_rsv_lock); - INIT_KFIFO(con->poison_fifo); mutex_init(&con->page_retirement_lock); - init_waitqueue_head(&con->page_retirement_wq); - atomic_set(&con->page_retirement_req_cnt, 0); - atomic_set(&con->poison_creation_count, 0); - atomic_set(&con->poison_consumption_count, 0); - con->page_retirement_thread = - kthread_run(amdgpu_ras_page_retirement_thread, adev, "umc_page_retirement"); - if (IS_ERR(con->page_retirement_thread)) { - con->page_retirement_thread = NULL; - dev_warn(adev->dev, "Failed to create umc_page_retirement thread!!!\n"); - } - - INIT_DELAYED_WORK(&con->page_retirement_dwork, amdgpu_ras_do_page_retirement); + amdgpu_ras_ecc_log_init(&con->umc_ecc_log); #ifdef CONFIG_X86_MCE_AMD if ((adev->asic_type == CHIP_ALDEBARAN) && @@ -3985,31 +3699,15 @@ static int amdgpu_ras_recovery_fini(struct amdgpu_device *adev) { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); struct ras_err_handler_data *data = con->eh_data; - int max_flush_timeout = MAX_FLUSH_RETIRE_DWORK_TIMES; - bool ret; /* recovery_init failed to init it, fini is useless */ if (!data) return 0; - /* Save all cached bad pages to eeprom */ - do { - flush_delayed_work(&con->page_retirement_dwork); - ret = amdgpu_ras_schedule_retirement_dwork(con, 0); - } while (ret && max_flush_timeout--); - - if (con->page_retirement_thread) - kthread_stop(con->page_retirement_thread); - - atomic_set(&con->page_retirement_req_cnt, 0); - atomic_set(&con->poison_creation_count, 0); - mutex_destroy(&con->page_rsv_lock); cancel_work_sync(&con->recovery_work); - cancel_delayed_work_sync(&con->page_retirement_dwork); - amdgpu_ras_ecc_log_fini(&con->umc_ecc_log); mutex_lock(&con->recovery_lock); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h index ad24c7cf8936..f511af205af6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h @@ -466,14 +466,6 @@ struct ras_query_context { typedef int (*pasid_notify)(struct amdgpu_device *adev, uint16_t pasid, void *data); -struct ras_poison_msg { - enum amdgpu_ras_block block; - uint16_t pasid; - uint32_t reset; - pasid_notify pasid_fn; - void *data; -}; - struct ras_err_pages { uint32_t count; uint64_t *pfn; @@ -549,7 +541,6 @@ struct amdgpu_ras { /* gpu recovery */ struct work_struct recovery_work; atomic_t in_recovery; - atomic_t rma_in_recovery; struct amdgpu_device *adev; /* error handler data */ struct ras_err_handler_data *eh_data; @@ -587,16 +578,9 @@ struct amdgpu_ras { /* Record special requirements of gpu reset caller */ uint32_t gpu_reset_flags; - struct task_struct *page_retirement_thread; - wait_queue_head_t page_retirement_wq; struct mutex page_retirement_lock; - atomic_t page_retirement_req_cnt; - atomic_t poison_creation_count; - atomic_t poison_consumption_count; struct mutex page_rsv_lock; - DECLARE_KFIFO(poison_fifo, struct ras_poison_msg, 128); struct ras_ecc_log_info umc_ecc_log; - struct delayed_work page_retirement_dwork; /* ras errors detected */ unsigned long ras_err_state; @@ -615,9 +599,6 @@ struct amdgpu_ras { struct list_head critical_region_head; struct mutex critical_region_lock; - /* Protect poison injection */ - struct mutex poison_lock; - /* Disable/Enable uniras switch */ bool uniras_enabled; const struct ras_smu_drv *ras_smu_drv; @@ -1029,10 +1010,6 @@ int amdgpu_ras_reserve_page(struct amdgpu_device *adev, uint64_t pfn); int amdgpu_ras_add_critical_region(struct amdgpu_device *adev, struct amdgpu_bo *bo); bool amdgpu_ras_check_critical_address(struct amdgpu_device *adev, uint64_t addr); -int amdgpu_ras_put_poison_req(struct amdgpu_device *adev, - enum amdgpu_ras_block block, uint16_t pasid, - pasid_notify pasid_fn, void *data, uint32_t reset); - bool amdgpu_ras_in_recovery(struct amdgpu_device *adev); __printf(3, 4) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index b8ed931f8a40..254aacc7138b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -276,7 +276,7 @@ int amdgpu_umc_pasid_poison_handler(struct amdgpu_device *adev, } amdgpu_ras_error_data_fini(&err_data); - } else if (amdgpu_uniras_enabled(adev)) { + } else { struct ras_ih_info ih_info = {0}; ih_info.block = block; @@ -285,17 +285,6 @@ int amdgpu_umc_pasid_poison_handler(struct amdgpu_device *adev, ih_info.pasid_fn = pasid_fn; ih_info.data = data; amdgpu_ras_mgr_handle_consumer_interrupt(adev, &ih_info); - } else { - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - int ret; - - ret = amdgpu_ras_put_poison_req(adev, - block, pasid, pasid_fn, data, reset); - if (!ret) { - atomic_inc(&con->page_retirement_req_cnt); - atomic_inc(&con->poison_consumption_count); - wake_up(&con->page_retirement_wq); - } } } else { if (adev->virt.ops && adev->virt.ops->ras_poison_handler) diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index 14092150336a..106f361d402a 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -656,23 +656,6 @@ static int umc_v12_0_update_ecc_status(struct amdgpu_device *adev, for (i = 0; i < count; i++) amdgpu_ras_reserve_page(adev, page_pfn[i]); - /* The problem case is as follows: - * 1. GPU A triggers a gpu ras reset, and GPU A drives - * GPU B to also perform a gpu ras reset. - * 2. After gpu B ras reset started, gpu B queried a DE - * data. Since the DE data was queried in the ras reset - * thread instead of the page retirement thread, bad - * page retirement work would not be triggered. Then - * even if all gpu resets are completed, the bad pages - * will be cached in RAM until GPU B's bad page retirement - * work is triggered again and then saved to eeprom. - * Trigger delayed work to save the bad pages to eeprom in time - * after gpu ras reset is completed. - */ - if (amdgpu_ras_in_recovery(adev)) - schedule_delayed_work(&con->page_retirement_dwork, - msecs_to_jiffies(DELAYED_TIME_FOR_GPU_RESET)); - return 0; } -- cgit v1.2.3 From ea33aa1545535fdb4c1a208b7bfd63314c3a4aa2 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 22 Jan 2026 15:47:28 +0800 Subject: drm/amdgpu: Drop legacy ACA log RAS error data code The legacy code for parsing RAS error data from ACA logs is obsolete and has been replaced by the unified RAS module Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c | 537 +------------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h | 3 - drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 34 +- 3 files changed, 4 insertions(+), 570 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c index db7858fe0c3d..4c78de1bdb79 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c @@ -26,206 +26,6 @@ #include "amdgpu_aca.h" #include "amdgpu_ras.h" -#define ACA_BANK_HWID(type, hwid, mcatype) [ACA_HWIP_TYPE_##type] = {hwid, mcatype} - -typedef int bank_handler_t(struct aca_handle *handle, struct aca_bank *bank, enum aca_smu_type type, void *data); - -static struct aca_hwip aca_hwid_mcatypes[ACA_HWIP_TYPE_COUNT] = { - ACA_BANK_HWID(SMU, 0x01, 0x01), - ACA_BANK_HWID(PCS_XGMI, 0x50, 0x00), - ACA_BANK_HWID(UMC, 0x96, 0x00), -}; - -static void aca_banks_init(struct aca_banks *banks) -{ - if (!banks) - return; - - memset(banks, 0, sizeof(*banks)); - INIT_LIST_HEAD(&banks->list); -} - -static int aca_banks_add_bank(struct aca_banks *banks, struct aca_bank *bank) -{ - struct aca_bank_node *node; - - if (!bank) - return -EINVAL; - - node = kvzalloc_obj(*node); - if (!node) - return -ENOMEM; - - memcpy(&node->bank, bank, sizeof(*bank)); - - INIT_LIST_HEAD(&node->node); - list_add_tail(&node->node, &banks->list); - - banks->nr_banks++; - - return 0; -} - -static void aca_banks_release(struct aca_banks *banks) -{ - struct aca_bank_node *node, *tmp; - - if (list_empty(&banks->list)) - return; - - list_for_each_entry_safe(node, tmp, &banks->list, node) { - list_del(&node->node); - kvfree(node); - banks->nr_banks--; - } -} - -static int aca_smu_get_valid_aca_count(struct amdgpu_device *adev, enum aca_smu_type type, u32 *count) -{ - struct amdgpu_aca *aca = &adev->aca; - const struct aca_smu_funcs *smu_funcs = aca->smu_funcs; - - if (!count) - return -EINVAL; - - if (!smu_funcs || !smu_funcs->get_valid_aca_count) - return -EOPNOTSUPP; - - return smu_funcs->get_valid_aca_count(adev, type, count); -} - -static struct aca_regs_dump { - const char *name; - int reg_idx; -} aca_regs[] = { - {"CONTROL", ACA_REG_IDX_CTL}, - {"STATUS", ACA_REG_IDX_STATUS}, - {"ADDR", ACA_REG_IDX_ADDR}, - {"MISC", ACA_REG_IDX_MISC0}, - {"CONFIG", ACA_REG_IDX_CONFIG}, - {"IPID", ACA_REG_IDX_IPID}, - {"SYND", ACA_REG_IDX_SYND}, - {"DESTAT", ACA_REG_IDX_DESTAT}, - {"DEADDR", ACA_REG_IDX_DEADDR}, - {"CONTROL_MASK", ACA_REG_IDX_CTL_MASK}, -}; - -static void aca_smu_bank_dump(struct amdgpu_device *adev, int idx, int total, struct aca_bank *bank, - struct ras_query_context *qctx) -{ - u64 event_id = qctx ? qctx->evid.event_id : RAS_EVENT_INVALID_ID; - int i; - - if (adev->debug_disable_ce_logs && - bank->smu_err_type == ACA_SMU_TYPE_CE && - !ACA_BANK_ERR_IS_DEFFERED(bank)) - return; - - RAS_EVENT_LOG(adev, event_id, HW_ERR "Accelerator Check Architecture events logged\n"); - /* plus 1 for output format, e.g: ACA[08/08]: xxxx */ - for (i = 0; i < ARRAY_SIZE(aca_regs); i++) - RAS_EVENT_LOG(adev, event_id, HW_ERR "ACA[%02d/%02d].%s=0x%016llx\n", - idx + 1, total, aca_regs[i].name, bank->regs[aca_regs[i].reg_idx]); - - if (ACA_REG__STATUS__SCRUB(bank->regs[ACA_REG_IDX_STATUS])) - RAS_EVENT_LOG(adev, event_id, HW_ERR "hardware error logged by the scrubber\n"); -} - -static bool aca_bank_hwip_is_matched(struct aca_bank *bank, enum aca_hwip_type type) -{ - - struct aca_hwip *hwip; - int hwid, mcatype; - u64 ipid; - - if (!bank || type == ACA_HWIP_TYPE_UNKNOW) - return false; - - hwip = &aca_hwid_mcatypes[type]; - if (!hwip->hwid) - return false; - - ipid = bank->regs[ACA_REG_IDX_IPID]; - hwid = ACA_REG__IPID__HARDWAREID(ipid); - mcatype = ACA_REG__IPID__MCATYPE(ipid); - - return hwip->hwid == hwid && hwip->mcatype == mcatype; -} - -static int aca_smu_get_valid_aca_banks(struct amdgpu_device *adev, enum aca_smu_type type, - int start, int count, - struct aca_banks *banks, struct ras_query_context *qctx) -{ - struct amdgpu_aca *aca = &adev->aca; - const struct aca_smu_funcs *smu_funcs = aca->smu_funcs; - struct aca_bank bank; - int i, max_count, ret; - - if (!count) - return 0; - - if (!smu_funcs || !smu_funcs->get_valid_aca_bank) - return -EOPNOTSUPP; - - switch (type) { - case ACA_SMU_TYPE_UE: - max_count = smu_funcs->max_ue_bank_count; - break; - case ACA_SMU_TYPE_CE: - max_count = smu_funcs->max_ce_bank_count; - break; - default: - return -EINVAL; - } - - if (start + count > max_count) - return -EINVAL; - - count = min_t(int, count, max_count); - for (i = 0; i < count; i++) { - memset(&bank, 0, sizeof(bank)); - ret = smu_funcs->get_valid_aca_bank(adev, type, start + i, &bank); - if (ret) - return ret; - - bank.smu_err_type = type; - - /* - * Poison being consumed when injecting a UE while running background workloads, - * which are unexpected. - */ - if (type == ACA_SMU_TYPE_UE && - ACA_REG__STATUS__POISON(bank.regs[ACA_REG_IDX_STATUS]) && - !aca_bank_hwip_is_matched(&bank, ACA_HWIP_TYPE_UMC)) - continue; - - aca_smu_bank_dump(adev, i, count, &bank, qctx); - - ret = aca_banks_add_bank(banks, &bank); - if (ret) - return ret; - } - - return 0; -} - -static bool aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, enum aca_smu_type type) -{ - const struct aca_bank_ops *bank_ops = handle->bank_ops; - - /* Parse all deferred errors with UMC aca handle */ - if (ACA_BANK_ERR_IS_DEFFERED(bank)) - return handle->hwip == ACA_HWIP_TYPE_UMC; - - if (!aca_bank_hwip_is_matched(bank, handle->hwip)) - return false; - - if (!bank_ops->aca_bank_is_valid) - return true; - - return bank_ops->aca_bank_is_valid(handle, bank, type, handle->data); -} - static struct aca_bank_error *new_bank_error(struct aca_error *aerr, struct aca_bank_info *info) { struct aca_bank_error *bank_error; @@ -315,303 +115,6 @@ int aca_error_cache_log_bank_error(struct aca_handle *handle, struct aca_bank_in return 0; } -static int aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, enum aca_smu_type type) -{ - const struct aca_bank_ops *bank_ops = handle->bank_ops; - - if (!bank) - return -EINVAL; - - if (!bank_ops->aca_bank_parser) - return -EOPNOTSUPP; - - return bank_ops->aca_bank_parser(handle, bank, type, - handle->data); -} - -static int handler_aca_log_bank_error(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - int ret; - - ret = aca_bank_parser(handle, bank, type); - if (ret) - return ret; - - return 0; -} - -static int aca_dispatch_bank(struct aca_handle_manager *mgr, struct aca_bank *bank, - enum aca_smu_type type, bank_handler_t handler, void *data) -{ - struct aca_handle *handle; - int ret; - - if (list_empty(&mgr->list)) - return 0; - - list_for_each_entry(handle, &mgr->list, node) { - if (!aca_bank_is_valid(handle, bank, type)) - continue; - - ret = handler(handle, bank, type, data); - if (ret) - return ret; - } - - return 0; -} - -static int aca_dispatch_banks(struct aca_handle_manager *mgr, struct aca_banks *banks, - enum aca_smu_type type, bank_handler_t handler, void *data) -{ - struct aca_bank_node *node; - struct aca_bank *bank; - int ret; - - if (!mgr || !banks) - return -EINVAL; - - /* pre check to avoid unnecessary operations */ - if (list_empty(&mgr->list) || list_empty(&banks->list)) - return 0; - - list_for_each_entry(node, &banks->list, node) { - bank = &node->bank; - - ret = aca_dispatch_bank(mgr, bank, type, handler, data); - if (ret) - return ret; - } - - return 0; -} - -static bool aca_bank_should_update(struct amdgpu_device *adev, enum aca_smu_type type) -{ - struct amdgpu_aca *aca = &adev->aca; - bool ret = true; - - /* - * Because the UE Valid MCA count will only be cleared after reset, - * in order to avoid repeated counting of the error count, - * the aca bank is only updated once during the gpu recovery stage. - */ - if (type == ACA_SMU_TYPE_UE) { - if (amdgpu_ras_intr_triggered()) - ret = atomic_cmpxchg(&aca->ue_update_flag, 0, 1) == 0; - else - atomic_set(&aca->ue_update_flag, 0); - } - - return ret; -} - -static void aca_banks_generate_cper(struct amdgpu_device *adev, - enum aca_smu_type type, - struct aca_banks *banks, - int count) -{ - struct aca_bank_node *node; - struct aca_bank *bank; - int r; - - if (!adev->cper.enabled) - return; - - if (!banks || !count) { - dev_warn(adev->dev, "fail to generate cper records\n"); - return; - } - - /* UEs must be encoded into separate CPER entries */ - if (type == ACA_SMU_TYPE_UE) { - struct aca_banks de_banks; - - aca_banks_init(&de_banks); - list_for_each_entry(node, &banks->list, node) { - bank = &node->bank; - if (bank->aca_err_type == ACA_ERROR_TYPE_DEFERRED) { - r = aca_banks_add_bank(&de_banks, bank); - if (r) - dev_warn(adev->dev, "fail to add de banks, ret = %d\n", r); - } else { - if (amdgpu_cper_generate_ue_record(adev, bank)) - dev_warn(adev->dev, "fail to generate ue cper records\n"); - } - } - - if (!list_empty(&de_banks.list)) { - if (amdgpu_cper_generate_ce_records(adev, &de_banks, de_banks.nr_banks)) - dev_warn(adev->dev, "fail to generate de cper records\n"); - } - - aca_banks_release(&de_banks); - } else { - /* - * SMU_TYPE_CE banks are combined into 1 CPER entries, - * they could be CEs or DEs or both - */ - if (amdgpu_cper_generate_ce_records(adev, banks, count)) - dev_warn(adev->dev, "fail to generate ce cper records\n"); - } -} - -static int aca_banks_update(struct amdgpu_device *adev, enum aca_smu_type type, - bank_handler_t handler, struct ras_query_context *qctx, void *data) -{ - struct amdgpu_aca *aca = &adev->aca; - struct aca_banks banks; - u32 count = 0; - int ret; - - if (list_empty(&aca->mgr.list)) - return 0; - - if (!aca_bank_should_update(adev, type)) - return 0; - - ret = aca_smu_get_valid_aca_count(adev, type, &count); - if (ret) - return ret; - - if (!count) - return 0; - - aca_banks_init(&banks); - - ret = aca_smu_get_valid_aca_banks(adev, type, 0, count, &banks, qctx); - if (ret) - goto err_release_banks; - - if (list_empty(&banks.list)) { - ret = 0; - goto err_release_banks; - } - - ret = aca_dispatch_banks(&aca->mgr, &banks, type, - handler, data); - if (ret) - goto err_release_banks; - - aca_banks_generate_cper(adev, type, &banks, count); - -err_release_banks: - aca_banks_release(&banks); - - return ret; -} - -static int aca_log_aca_error_data(struct aca_bank_error *bank_error, enum aca_error_type type, struct ras_err_data *err_data) -{ - struct aca_bank_info *info; - struct amdgpu_smuio_mcm_config_info mcm_info; - u64 count; - - if (type >= ACA_ERROR_TYPE_COUNT) - return -EINVAL; - - count = bank_error->count; - if (!count) - return 0; - - info = &bank_error->info; - mcm_info.die_id = info->die_id; - mcm_info.socket_id = info->socket_id; - - switch (type) { - case ACA_ERROR_TYPE_UE: - amdgpu_ras_error_statistic_ue_count(err_data, &mcm_info, count); - break; - case ACA_ERROR_TYPE_CE: - amdgpu_ras_error_statistic_ce_count(err_data, &mcm_info, count); - break; - case ACA_ERROR_TYPE_DEFERRED: - amdgpu_ras_error_statistic_de_count(err_data, &mcm_info, count); - break; - default: - break; - } - - return 0; -} - -static int aca_log_aca_error(struct aca_handle *handle, enum aca_error_type type, struct ras_err_data *err_data) -{ - struct aca_error_cache *error_cache = &handle->error_cache; - struct aca_error *aerr = &error_cache->errors[type]; - struct aca_bank_error *bank_error, *tmp; - - mutex_lock(&aerr->lock); - - if (list_empty(&aerr->list)) - goto out_unlock; - - list_for_each_entry_safe(bank_error, tmp, &aerr->list, node) { - aca_log_aca_error_data(bank_error, type, err_data); - aca_bank_error_remove(aerr, bank_error); - } - -out_unlock: - mutex_unlock(&aerr->lock); - - return 0; -} - -static int __aca_get_error_data(struct amdgpu_device *adev, struct aca_handle *handle, enum aca_error_type type, - struct ras_err_data *err_data, struct ras_query_context *qctx) -{ - enum aca_smu_type smu_type; - int ret; - - switch (type) { - case ACA_ERROR_TYPE_UE: - smu_type = ACA_SMU_TYPE_UE; - break; - case ACA_ERROR_TYPE_CE: - case ACA_ERROR_TYPE_DEFERRED: - smu_type = ACA_SMU_TYPE_CE; - break; - default: - return -EINVAL; - } - - /* update aca bank to aca source error_cache first */ - ret = aca_banks_update(adev, smu_type, handler_aca_log_bank_error, qctx, NULL); - if (ret) - return ret; - - /* DEs may contain in CEs or UEs */ - if (type != ACA_ERROR_TYPE_DEFERRED) - aca_log_aca_error(handle, ACA_ERROR_TYPE_DEFERRED, err_data); - - return aca_log_aca_error(handle, type, err_data); -} - -static bool aca_handle_is_valid(struct aca_handle *handle) -{ - if (!handle->mask || !list_empty(&handle->node)) - return false; - - return true; -} - -int amdgpu_aca_get_error_data(struct amdgpu_device *adev, struct aca_handle *handle, - enum aca_error_type type, struct ras_err_data *err_data, - struct ras_query_context *qctx) -{ - if (!handle || !err_data) - return -EINVAL; - - if (aca_handle_is_valid(handle)) - return -EOPNOTSUPP; - - if ((type < 0) || (!(BIT(type) & handle->mask))) - return 0; - - return __aca_get_error_data(adev, handle, type, err_data, qctx); -} - static void aca_error_init(struct aca_error *aerr, enum aca_error_type type) { mutex_init(&aerr->lock); @@ -890,47 +393,9 @@ static int amdgpu_aca_smu_debug_mode_set(void *data, u64 val) return 0; } -static void aca_dump_entry(struct seq_file *m, struct aca_bank *bank, enum aca_smu_type type, int idx) -{ - struct aca_bank_info info; - int i, ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return; - - seq_printf(m, "aca entry[%d].type: %s\n", idx, type == ACA_SMU_TYPE_UE ? "UE" : "CE"); - seq_printf(m, "aca entry[%d].info: socketid:%d aid:%d hwid:0x%03x mcatype:0x%04x\n", - idx, info.socket_id, info.die_id, info.hwid, info.mcatype); - - for (i = 0; i < ARRAY_SIZE(aca_regs); i++) - seq_printf(m, "aca entry[%d].regs[%d]: 0x%016llx\n", idx, aca_regs[i].reg_idx, bank->regs[aca_regs[i].reg_idx]); -} - -struct aca_dump_context { - struct seq_file *m; - int idx; -}; - -static int handler_aca_bank_dump(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_dump_context *ctx = (struct aca_dump_context *)data; - - aca_dump_entry(ctx->m, bank, type, ctx->idx++); - - return handler_aca_log_bank_error(handle, bank, type, NULL); -} - static int aca_dump_show(struct seq_file *m, enum aca_smu_type type) { - struct amdgpu_device *adev = (struct amdgpu_device *)m->private; - struct aca_dump_context context = { - .m = m, - .idx = 0, - }; - - return aca_banks_update(adev, type, handler_aca_bank_dump, NULL, (void *)&context); + return 0; } static int aca_dump_ce_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h index 38c88897e1ec..93a70a350f34 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h @@ -222,9 +222,6 @@ int aca_bank_check_error_codes(struct amdgpu_device *adev, struct aca_bank *bank int amdgpu_aca_add_handle(struct amdgpu_device *adev, struct aca_handle *handle, const char *name, const struct aca_info *aca_info, void *data); void amdgpu_aca_remove_handle(struct aca_handle *handle); -int amdgpu_aca_get_error_data(struct amdgpu_device *adev, struct aca_handle *handle, - enum aca_error_type type, struct ras_err_data *err_data, - struct ras_query_context *qctx); int amdgpu_aca_smu_set_debug_mode(struct amdgpu_device *adev, bool en); void amdgpu_aca_smu_debugfs_init(struct amdgpu_device *adev, struct dentry *root); int aca_error_cache_log_bank_error(struct aca_handle *handle, struct aca_bank_info *info, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 3b864a0b70c2..64e1872ef210 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -1414,19 +1414,6 @@ int amdgpu_ras_unbind_aca(struct amdgpu_device *adev, enum amdgpu_ras_block blk) return 0; } -static int amdgpu_aca_log_ras_error_data(struct amdgpu_device *adev, enum amdgpu_ras_block blk, - enum aca_error_type type, struct ras_err_data *err_data, - struct ras_query_context *qctx) -{ - struct ras_manager *obj; - - obj = get_ras_manager(adev, blk); - if (!obj) - return -EINVAL; - - return amdgpu_aca_get_error_data(adev, &obj->aca_handle, type, err_data, qctx); -} - ssize_t amdgpu_ras_aca_sysfs_read(struct device *dev, struct device_attribute *attr, struct aca_handle *handle, char *buf, void *data) { @@ -1453,7 +1440,6 @@ static int amdgpu_ras_query_error_status_helper(struct amdgpu_device *adev, { enum amdgpu_ras_block blk = info ? info->head.block : AMDGPU_RAS_BLOCK_COUNT; struct amdgpu_ras_block_object *block_obj = NULL; - int ret; if (blk == AMDGPU_RAS_BLOCK_COUNT) return -EINVAL; @@ -1485,23 +1471,9 @@ static int amdgpu_ras_query_error_status_helper(struct amdgpu_device *adev, } } } else { - if (amdgpu_aca_is_enabled(adev)) { - ret = amdgpu_aca_log_ras_error_data(adev, blk, ACA_ERROR_TYPE_UE, err_data, qctx); - if (ret) - return ret; - - ret = amdgpu_aca_log_ras_error_data(adev, blk, ACA_ERROR_TYPE_CE, err_data, qctx); - if (ret) - return ret; - - ret = amdgpu_aca_log_ras_error_data(adev, blk, ACA_ERROR_TYPE_DEFERRED, err_data, qctx); - if (ret) - return ret; - } else { - /* FIXME: add code to check return value later */ - amdgpu_mca_smu_log_ras_error(adev, blk, AMDGPU_MCA_ERROR_TYPE_UE, err_data, qctx); - amdgpu_mca_smu_log_ras_error(adev, blk, AMDGPU_MCA_ERROR_TYPE_CE, err_data, qctx); - } + /* FIXME: add code to check return value later */ + amdgpu_mca_smu_log_ras_error(adev, blk, AMDGPU_MCA_ERROR_TYPE_UE, err_data, qctx); + amdgpu_mca_smu_log_ras_error(adev, blk, AMDGPU_MCA_ERROR_TYPE_CE, err_data, qctx); } return 0; -- cgit v1.2.3 From 1d3ce48f867b206302d601e6c797feb08a82873b Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 13:41:32 +0800 Subject: drm/amdgpu: retire ACA support for jpeg v4.0.3 Retire ACA support for jpeg v4.0.3 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c | 71 -------------------------------- 1 file changed, 71 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c index b0bdb449538e..4c57871b810a 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c @@ -1442,72 +1442,6 @@ static const struct amdgpu_ras_block_hw_ops jpeg_v4_0_3_ras_hw_ops = { .query_poison_status = jpeg_v4_0_3_query_ras_poison_status, }; -static int jpeg_v4_0_3_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_bank_info info; - u64 misc0; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, - 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -/* reference to smu driver if header file */ -static int jpeg_v4_0_3_err_codes[] = { - 16, 17, 18, 19, 20, 21, 22, 23, /* JPEG[0-7][S|D] */ - 24, 25, 26, 27, 28, 29, 30, 31 -}; - -static bool jpeg_v4_0_3_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - - if (instlo != mmSMNAID_AID0_MCA_SMU) - return false; - - if (aca_bank_check_error_codes(handle->adev, bank, - jpeg_v4_0_3_err_codes, - ARRAY_SIZE(jpeg_v4_0_3_err_codes))) - return false; - - return true; -} - -static const struct aca_bank_ops jpeg_v4_0_3_aca_bank_ops = { - .aca_bank_parser = jpeg_v4_0_3_aca_bank_parser, - .aca_bank_is_valid = jpeg_v4_0_3_aca_bank_is_valid, -}; - -static const struct aca_info jpeg_v4_0_3_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK, - .bank_ops = &jpeg_v4_0_3_aca_bank_ops, -}; - static int jpeg_v4_0_3_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) { int r; @@ -1523,11 +1457,6 @@ static int jpeg_v4_0_3_ras_late_init(struct amdgpu_device *adev, struct ras_comm goto late_fini; } - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__JPEG, - &jpeg_v4_0_3_aca_info, NULL); - if (r) - goto late_fini; - return 0; late_fini: -- cgit v1.2.3 From 6b51f51c13b74a8c6535f2f95029cb448fc63a43 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 13:54:03 +0800 Subject: drm/amdgpu: retire ACA support for vcn v5.0.1 Retire ACA support for vcn v5.0.1 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 70 --------------------------------- 1 file changed, 70 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 9c23055cf5ce..1a07c3bf4425 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1727,71 +1727,6 @@ static const struct amdgpu_ras_block_hw_ops vcn_v5_0_1_ras_hw_ops = { .query_poison_status = vcn_v5_0_1_query_poison_status, }; -static int vcn_v5_0_1_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_bank_info info; - u64 misc0; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, - 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -/* reference to smu driver if header file */ -static int vcn_v5_0_1_err_codes[] = { - 14, 15, 47, /* VCN [D|V|S] */ -}; - -static bool vcn_v5_0_1_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - - if (instlo != mmSMNAID_AID0_MCA_SMU) - return false; - - if (aca_bank_check_error_codes(handle->adev, bank, - vcn_v5_0_1_err_codes, - ARRAY_SIZE(vcn_v5_0_1_err_codes))) - return false; - - return true; -} - -static const struct aca_bank_ops vcn_v5_0_1_aca_bank_ops = { - .aca_bank_parser = vcn_v5_0_1_aca_bank_parser, - .aca_bank_is_valid = vcn_v5_0_1_aca_bank_is_valid, -}; - -static const struct aca_info vcn_v5_0_1_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK, - .bank_ops = &vcn_v5_0_1_aca_bank_ops, -}; - static int vcn_v5_0_1_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) { int r; @@ -1800,11 +1735,6 @@ static int vcn_v5_0_1_ras_late_init(struct amdgpu_device *adev, struct ras_commo if (r) return r; - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__VCN, - &vcn_v5_0_1_aca_info, NULL); - if (r) - goto late_fini; - if (amdgpu_ras_is_supported(adev, ras_block->block) && adev->vcn.inst->ras_poison_irq.funcs) { r = amdgpu_irq_get(adev, &adev->vcn.inst->ras_poison_irq, 0); -- cgit v1.2.3 From 0906c091e6020829e1261d0f82db8b5fec765de5 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 14:02:39 +0800 Subject: drm/amdgpu: retire ACA support for jpeg v5.0.1 Retire ACA support for jpeg v5.0.1 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 72 -------------------------------- 1 file changed, 72 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index a562369d2d81..324d5899bd80 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -1017,73 +1017,6 @@ static const struct amdgpu_ras_block_hw_ops jpeg_v5_0_1_ras_hw_ops = { .query_poison_status = jpeg_v5_0_1_query_ras_poison_status, }; -static int jpeg_v5_0_1_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_bank_info info; - u64 misc0; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, - 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -/* reference to smu driver if header file */ -static int jpeg_v5_0_1_err_codes[] = { - 16, 17, 18, 19, 20, 21, 22, 23, /* JPEG[0-9][S|D] */ - 24, 25, 26, 27, 28, 29, 30, 31, - 48, 49, 50, 51, -}; - -static bool jpeg_v5_0_1_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - - if (instlo != mmSMNAID_AID0_MCA_SMU) - return false; - - if (aca_bank_check_error_codes(handle->adev, bank, - jpeg_v5_0_1_err_codes, - ARRAY_SIZE(jpeg_v5_0_1_err_codes))) - return false; - - return true; -} - -static const struct aca_bank_ops jpeg_v5_0_1_aca_bank_ops = { - .aca_bank_parser = jpeg_v5_0_1_aca_bank_parser, - .aca_bank_is_valid = jpeg_v5_0_1_aca_bank_is_valid, -}; - -static const struct aca_info jpeg_v5_0_1_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK, - .bank_ops = &jpeg_v5_0_1_aca_bank_ops, -}; - static int jpeg_v5_0_1_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) { int r; @@ -1092,11 +1025,6 @@ static int jpeg_v5_0_1_ras_late_init(struct amdgpu_device *adev, struct ras_comm if (r) return r; - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__JPEG, - &jpeg_v5_0_1_aca_info, NULL); - if (r) - goto late_fini; - if (amdgpu_ras_is_supported(adev, ras_block->block) && adev->jpeg.inst->ras_poison_irq.funcs) { r = amdgpu_irq_get(adev, &adev->jpeg.inst->ras_poison_irq, 0); -- cgit v1.2.3 From 8cc0394d4a4e40fe9fb17bb2ce7640576918e5d3 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 14:05:34 +0800 Subject: drm/amdgpu: retire ACA support for vcn v4.0.3 Retire ACA support for vcn v4.0.3 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c | 70 --------------------------------- 1 file changed, 70 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c index 3c3f3d1a040d..179b892fb410 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c @@ -2163,71 +2163,6 @@ static const struct amdgpu_ras_block_hw_ops vcn_v4_0_3_ras_hw_ops = { .query_poison_status = vcn_v4_0_3_query_poison_status, }; -static int vcn_v4_0_3_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_bank_info info; - u64 misc0; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, - 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -/* reference to smu driver if header file */ -static int vcn_v4_0_3_err_codes[] = { - 14, 15, /* VCN */ -}; - -static bool vcn_v4_0_3_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - - if (instlo != mmSMNAID_AID0_MCA_SMU) - return false; - - if (aca_bank_check_error_codes(handle->adev, bank, - vcn_v4_0_3_err_codes, - ARRAY_SIZE(vcn_v4_0_3_err_codes))) - return false; - - return true; -} - -static const struct aca_bank_ops vcn_v4_0_3_aca_bank_ops = { - .aca_bank_parser = vcn_v4_0_3_aca_bank_parser, - .aca_bank_is_valid = vcn_v4_0_3_aca_bank_is_valid, -}; - -static const struct aca_info vcn_v4_0_3_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK, - .bank_ops = &vcn_v4_0_3_aca_bank_ops, -}; - static int vcn_v4_0_3_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) { int r; @@ -2243,11 +2178,6 @@ static int vcn_v4_0_3_ras_late_init(struct amdgpu_device *adev, struct ras_commo goto late_fini; } - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__VCN, - &vcn_v4_0_3_aca_info, NULL); - if (r) - goto late_fini; - return 0; late_fini: -- cgit v1.2.3 From c005b25a6272d1bde90035c1afce884187393fe6 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 09:51:10 +0800 Subject: drm/amdgpu: retire xgmi v6.4.0 ACA support retire xgmi v6.4.0 ACA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 78 +------------------------------- 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index fe1b5b47f609..d8e1bd01eded 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -1152,91 +1152,15 @@ int amdgpu_xgmi_remove_device(struct amdgpu_device *adev) return 0; } -static int xgmi_v6_4_0_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct amdgpu_device *adev = handle->adev; - struct aca_bank_info info; - const char *error_str; - u64 status, count; - int ret, ext_error_code; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - status = bank->regs[ACA_REG_IDX_STATUS]; - ext_error_code = ACA_REG__STATUS__ERRORCODEEXT(status); - - error_str = ext_error_code < ARRAY_SIZE(xgmi_v6_4_0_ras_error_code_ext) ? - xgmi_v6_4_0_ras_error_code_ext[ext_error_code] : NULL; - if (error_str) - dev_info(adev->dev, "%s detected\n", error_str); - - count = ACA_REG__MISC0__ERRCNT(bank->regs[ACA_REG_IDX_MISC0]); - - switch (type) { - case ACA_SMU_TYPE_UE: - if (ext_error_code != 0 && ext_error_code != 1 && ext_error_code != 9) - count = 0ULL; - - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, count); - break; - case ACA_SMU_TYPE_CE: - count = ext_error_code == 6 ? count : 0ULL; - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, count); - break; - default: - return -EINVAL; - } - - return ret; -} - -static const struct aca_bank_ops xgmi_v6_4_0_aca_bank_ops = { - .aca_bank_parser = xgmi_v6_4_0_aca_bank_parser, -}; - -static const struct aca_info xgmi_v6_4_0_aca_info = { - .hwip = ACA_HWIP_TYPE_PCS_XGMI, - .mask = ACA_ERROR_UE_MASK | ACA_ERROR_CE_MASK, - .bank_ops = &xgmi_v6_4_0_aca_bank_ops, -}; - static int amdgpu_xgmi_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) { - int r; - if (!adev->gmc.xgmi.supported || adev->gmc.xgmi.num_physical_nodes == 0) return 0; amdgpu_ras_reset_error_count(adev, AMDGPU_RAS_BLOCK__XGMI_WAFL); - r = amdgpu_ras_block_late_init(adev, ras_block); - if (r) - return r; - - switch (amdgpu_ip_version(adev, XGMI_HWIP, 0)) { - case IP_VERSION(6, 4, 0): - case IP_VERSION(6, 4, 1): - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__XGMI_WAFL, - &xgmi_v6_4_0_aca_info, NULL); - if (r) - goto late_fini; - break; - default: - break; - } - - return 0; - -late_fini: - amdgpu_ras_block_late_fini(adev, ras_block); - - return r; + return amdgpu_ras_block_late_init(adev, ras_block); } uint64_t amdgpu_xgmi_get_relative_phy_addr(struct amdgpu_device *adev, -- cgit v1.2.3 From 2e45940e383b6d34d33af4aa9c36c42fc628ae2f Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 14:45:59 +0800 Subject: drm/amdgpu: retire gfx v9.4.3 ACA support retire gfx v9.4.3 ACA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 91 --------------------------------- 1 file changed, 91 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 5f5577f52a98..d67ac6f96481 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -39,7 +39,6 @@ #include "gfx_v9_4_3.h" #include "gfx_v9_4_3_cleaner_shader.h" #include "amdgpu_xcp.h" -#include "amdgpu_aca.h" MODULE_FIRMWARE("amdgpu/gc_9_4_3_mec.bin"); MODULE_FIRMWARE("amdgpu/gc_9_4_4_mec.bin"); @@ -851,73 +850,6 @@ static const struct amdgpu_gfx_funcs gfx_v9_4_3_gfx_funcs = { .get_hdp_flush_mask = &amdgpu_gfx_get_hdp_flush_mask, }; -static int gfx_v9_4_3_aca_bank_parser(struct aca_handle *handle, - struct aca_bank *bank, enum aca_smu_type type, - void *data) -{ - struct aca_bank_info info; - u64 misc0; - u32 instlo; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - /* NOTE: overwrite info.die_id with xcd id for gfx */ - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - info.die_id = instlo == mmSMNAID_XCD0_MCA_SMU ? 0 : 1; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -static bool gfx_v9_4_3_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - switch (instlo) { - case mmSMNAID_XCD0_MCA_SMU: - case mmSMNAID_XCD1_MCA_SMU: - case mmSMNXCD_XCD0_MCA_SMU: - return true; - default: - break; - } - - return false; -} - -static const struct aca_bank_ops gfx_v9_4_3_aca_bank_ops = { - .aca_bank_parser = gfx_v9_4_3_aca_bank_parser, - .aca_bank_is_valid = gfx_v9_4_3_aca_bank_is_valid, -}; - -static const struct aca_info gfx_v9_4_3_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK | ACA_ERROR_CE_MASK, - .bank_ops = &gfx_v9_4_3_aca_bank_ops, -}; - static int gfx_v9_4_3_gpu_early_init(struct amdgpu_device *adev) { adev->gfx.funcs = &gfx_v9_4_3_gfx_funcs; @@ -5189,32 +5121,9 @@ struct amdgpu_ras_block_hw_ops gfx_v9_4_3_ras_ops = { .reset_ras_error_count = &gfx_v9_4_3_reset_ras_error_count, }; -static int gfx_v9_4_3_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) -{ - int r; - - r = amdgpu_ras_block_late_init(adev, ras_block); - if (r) - return r; - - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__GFX, - &gfx_v9_4_3_aca_info, - NULL); - if (r) - goto late_fini; - - return 0; - -late_fini: - amdgpu_ras_block_late_fini(adev, ras_block); - - return r; -} - struct amdgpu_gfx_ras gfx_v9_4_3_ras = { .ras_block = { .hw_ops = &gfx_v9_4_3_ras_ops, - .ras_late_init = &gfx_v9_4_3_ras_late_init, }, .enable_watchdog_timer = &gfx_v9_4_3_enable_watchdog_timer, }; -- cgit v1.2.3 From ef7017879dbfb56b2e9c17da18bcd4de1232329e Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 14:49:05 +0800 Subject: drm/amdgpu: retire sdma v4.4.2 ACA support retire sdma v4.4.2 ACA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 78 -------------------------------- 1 file changed, 78 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c index a7685b516f19..0d7e22060a92 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c @@ -95,8 +95,6 @@ static const struct amdgpu_hwip_reg_entry sdma_reg_list_4_4_2[] = { SOC15_REG_ENTRY_STR(GC, 0, regSDMA_VM_CNTL) }; -#define mmSMNAID_AID0_MCA_SMU 0x03b30400 - #define WREG32_SDMA(instance, offset, value) \ WREG32(sdma_v4_4_2_get_reg_offset(adev, (instance), (offset)), value) #define RREG32_SDMA(instance, offset) \ @@ -2520,85 +2518,9 @@ static const struct amdgpu_ras_block_hw_ops sdma_v4_4_2_ras_hw_ops = { .reset_ras_error_count = sdma_v4_4_2_reset_ras_error_count, }; -static int sdma_v4_4_2_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_bank_info info; - u64 misc0; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, - 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -/* CODE_SDMA0 - CODE_SDMA4, reference to smu driver if header file */ -static int sdma_v4_4_2_err_codes[] = { 33, 34, 35, 36 }; - -static bool sdma_v4_4_2_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - - if (instlo != mmSMNAID_AID0_MCA_SMU) - return false; - - if (aca_bank_check_error_codes(handle->adev, bank, - sdma_v4_4_2_err_codes, - ARRAY_SIZE(sdma_v4_4_2_err_codes))) - return false; - - return true; -} - -static const struct aca_bank_ops sdma_v4_4_2_aca_bank_ops = { - .aca_bank_parser = sdma_v4_4_2_aca_bank_parser, - .aca_bank_is_valid = sdma_v4_4_2_aca_bank_is_valid, -}; - -static const struct aca_info sdma_v4_4_2_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK, - .bank_ops = &sdma_v4_4_2_aca_bank_ops, -}; - -static int sdma_v4_4_2_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) -{ - int r; - - r = amdgpu_sdma_ras_late_init(adev, ras_block); - if (r) - return r; - - return amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__SDMA, - &sdma_v4_4_2_aca_info, NULL); -} - static struct amdgpu_sdma_ras sdma_v4_4_2_ras = { .ras_block = { .hw_ops = &sdma_v4_4_2_ras_hw_ops, - .ras_late_init = sdma_v4_4_2_ras_late_init, }, }; -- cgit v1.2.3 From 4a63d64b0396860ef8920b6bdb3943b6457a0250 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Sat, 28 Mar 2026 18:01:50 +0800 Subject: drm/amdgpu: re-set ClearMcaOnRead CE/UE in late init for uniras Re-set the ClearMcaOnRead flags for UE and CE errors during RAS late init to maintain correct MCA error handling behavior Signed-off-by: Ce Sun Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 64e1872ef210..4ab6eccb5691 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -4370,6 +4370,9 @@ int amdgpu_ras_late_init(struct amdgpu_device *adev) if (amdgpu_sriov_vf(adev) && !amdgpu_sriov_ras_telemetry_en(adev)) return 0; + if (amdgpu_uniras_enabled(adev)) + amdgpu_ras_mgr_set_debug_mode(adev, false); + list_for_each_entry_safe(node, tmp, &adev->ras_list, node) { obj = node->ras_obj; if (!obj) { -- cgit v1.2.3 From ff40ab2fa490e3fcf1a7e7fc2ab70e9b409d5831 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 15:03:48 +0800 Subject: drm/amdgpu: retire umc v12.0 ACA support retire umc v12.0 ACA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 68 ---------------------------------- 1 file changed, 68 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index 106f361d402a..e441270a91ec 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -502,73 +502,6 @@ const struct amdgpu_ras_block_hw_ops umc_v12_0_ras_hw_ops = { .query_ras_error_address = umc_v12_0_query_ras_error_address, }; -static int umc_v12_0_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct amdgpu_device *adev = handle->adev; - struct aca_bank_info info; - enum aca_error_type err_type; - u64 status, count; - u32 ext_error_code; - int ret; - - status = bank->regs[ACA_REG_IDX_STATUS]; - if (umc_v12_0_is_deferred_error(adev, status)) - err_type = ACA_ERROR_TYPE_DEFERRED; - else if (umc_v12_0_is_uncorrectable_error(adev, status)) - err_type = ACA_ERROR_TYPE_UE; - else if (umc_v12_0_is_correctable_error(adev, status)) - err_type = ACA_ERROR_TYPE_CE; - else - return 0; - bank->aca_err_type = err_type; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - amdgpu_umc_update_ecc_status(adev, - bank->regs[ACA_REG_IDX_STATUS], - bank->regs[ACA_REG_IDX_IPID], - bank->regs[ACA_REG_IDX_ADDR]); - - ext_error_code = ACA_REG__STATUS__ERRORCODEEXT(status); - if (umc_v12_0_is_deferred_error(adev, status)) - count = ext_error_code == 0 ? - adev->umc.err_addr_cnt / adev->umc.retire_unit : 1ULL; - else - count = ext_error_code == 0 ? - ACA_REG__MISC0__ERRCNT(bank->regs[ACA_REG_IDX_MISC0]) : 1ULL; - - return aca_error_cache_log_bank_error(handle, &info, err_type, count); -} - -static const struct aca_bank_ops umc_v12_0_aca_bank_ops = { - .aca_bank_parser = umc_v12_0_aca_bank_parser, -}; - -const struct aca_info umc_v12_0_aca_info = { - .hwip = ACA_HWIP_TYPE_UMC, - .mask = ACA_ERROR_UE_MASK | ACA_ERROR_CE_MASK | ACA_ERROR_DEFERRED_MASK, - .bank_ops = &umc_v12_0_aca_bank_ops, -}; - -static int umc_v12_0_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) -{ - int ret; - - ret = amdgpu_umc_ras_late_init(adev, ras_block); - if (ret) - return ret; - - ret = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__UMC, - &umc_v12_0_aca_info, NULL); - if (ret) - return ret; - - return 0; -} - static int umc_v12_0_update_ecc_status(struct amdgpu_device *adev, uint64_t status, uint64_t ipid, uint64_t addr) { @@ -758,7 +691,6 @@ static void umc_v12_0_mca_ipid_parse(struct amdgpu_device *adev, uint64_t ipid, struct amdgpu_umc_ras umc_v12_0_ras = { .ras_block = { .hw_ops = &umc_v12_0_ras_hw_ops, - .ras_late_init = umc_v12_0_ras_late_init, }, .err_cnt_init = umc_v12_0_err_cnt_init, .query_ras_poison_mode = umc_v12_0_query_ras_poison_mode, -- cgit v1.2.3 From 07298ef00b1a8e87eeb2c8df0fa505a5b8c1ecce Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 26 Jan 2026 10:47:03 +0800 Subject: drm/amdgpu: retire funcs for generating legacy cper record retire funcs for generating legacy cper record Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c | 111 ------------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h | 8 --- 2 files changed, 119 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c index d5e59c24d907..34a70e479f60 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c @@ -289,40 +289,6 @@ struct cper_hdr *amdgpu_cper_alloc_entry(struct amdgpu_device *adev, return hdr; } -int amdgpu_cper_generate_ue_record(struct amdgpu_device *adev, - struct aca_bank *bank) -{ - struct cper_hdr *fatal = NULL; - struct cper_sec_crashdump_reg_data reg_data = { 0 }; - struct amdgpu_ring *ring = &adev->cper.ring_buf; - int ret; - - fatal = amdgpu_cper_alloc_entry(adev, AMDGPU_CPER_TYPE_FATAL, 1); - if (!fatal) { - dev_err(adev->dev, "fail to alloc cper entry for ue record\n"); - return -ENOMEM; - } - - reg_data.status_lo = lower_32_bits(bank->regs[ACA_REG_IDX_STATUS]); - reg_data.status_hi = upper_32_bits(bank->regs[ACA_REG_IDX_STATUS]); - reg_data.addr_lo = lower_32_bits(bank->regs[ACA_REG_IDX_ADDR]); - reg_data.addr_hi = upper_32_bits(bank->regs[ACA_REG_IDX_ADDR]); - reg_data.ipid_lo = lower_32_bits(bank->regs[ACA_REG_IDX_IPID]); - reg_data.ipid_hi = upper_32_bits(bank->regs[ACA_REG_IDX_IPID]); - reg_data.synd_lo = lower_32_bits(bank->regs[ACA_REG_IDX_SYND]); - reg_data.synd_hi = upper_32_bits(bank->regs[ACA_REG_IDX_SYND]); - - amdgpu_cper_entry_fill_hdr(adev, fatal, AMDGPU_CPER_TYPE_FATAL, CPER_SEV_FATAL_UNCORRECTED); - ret = amdgpu_cper_entry_fill_fatal_section(adev, fatal, 0, reg_data); - if (ret) - return ret; - - amdgpu_cper_ring_write(ring, fatal, fatal->record_length); - kfree(fatal); - - return 0; -} - int amdgpu_cper_generate_bp_threshold_record(struct amdgpu_device *adev) { struct cper_hdr *bp_threshold = NULL; @@ -348,83 +314,6 @@ int amdgpu_cper_generate_bp_threshold_record(struct amdgpu_device *adev) return 0; } -static enum cper_error_severity amdgpu_aca_err_type_to_cper_sev(struct amdgpu_device *adev, - enum aca_error_type aca_err_type) -{ - switch (aca_err_type) { - case ACA_ERROR_TYPE_UE: - return CPER_SEV_FATAL_UNCORRECTED; - case ACA_ERROR_TYPE_CE: - return CPER_SEV_NON_FATAL_CORRECTED; - case ACA_ERROR_TYPE_DEFERRED: - return CPER_SEV_NON_FATAL_UNCORRECTED; - default: - dev_err(adev->dev, "Unknown ACA error type!\n"); - return CPER_SEV_FATAL_UNCORRECTED; - } -} - -int amdgpu_cper_generate_ce_records(struct amdgpu_device *adev, - struct aca_banks *banks, - uint16_t bank_count) -{ - struct cper_hdr *corrected = NULL; - enum cper_error_severity sev = CPER_SEV_NON_FATAL_CORRECTED; - struct amdgpu_ring *ring = &adev->cper.ring_buf; - uint32_t reg_data[CPER_ACA_REG_COUNT] = { 0 }; - struct aca_bank_node *node; - struct aca_bank *bank; - uint32_t i = 0; - int ret; - - corrected = amdgpu_cper_alloc_entry(adev, AMDGPU_CPER_TYPE_RUNTIME, bank_count); - if (!corrected) { - dev_err(adev->dev, "fail to allocate cper entry for ce records\n"); - return -ENOMEM; - } - - /* Raise severity if any DE is detected in the ACA bank list */ - list_for_each_entry(node, &banks->list, node) { - bank = &node->bank; - if (bank->aca_err_type == ACA_ERROR_TYPE_DEFERRED) { - sev = CPER_SEV_NON_FATAL_UNCORRECTED; - break; - } - } - - amdgpu_cper_entry_fill_hdr(adev, corrected, AMDGPU_CPER_TYPE_RUNTIME, sev); - - /* Combine CE and DE in cper record */ - list_for_each_entry(node, &banks->list, node) { - bank = &node->bank; - reg_data[CPER_ACA_REG_CTL_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_CTL]); - reg_data[CPER_ACA_REG_CTL_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_CTL]); - reg_data[CPER_ACA_REG_STATUS_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_STATUS]); - reg_data[CPER_ACA_REG_STATUS_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_STATUS]); - reg_data[CPER_ACA_REG_ADDR_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_ADDR]); - reg_data[CPER_ACA_REG_ADDR_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_ADDR]); - reg_data[CPER_ACA_REG_MISC0_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_MISC0]); - reg_data[CPER_ACA_REG_MISC0_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_MISC0]); - reg_data[CPER_ACA_REG_CONFIG_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_CONFIG]); - reg_data[CPER_ACA_REG_CONFIG_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_CONFIG]); - reg_data[CPER_ACA_REG_IPID_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_IPID]); - reg_data[CPER_ACA_REG_IPID_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_IPID]); - reg_data[CPER_ACA_REG_SYND_LO] = lower_32_bits(bank->regs[ACA_REG_IDX_SYND]); - reg_data[CPER_ACA_REG_SYND_HI] = upper_32_bits(bank->regs[ACA_REG_IDX_SYND]); - - ret = amdgpu_cper_entry_fill_runtime_section(adev, corrected, i++, - amdgpu_aca_err_type_to_cper_sev(adev, bank->aca_err_type), - reg_data, CPER_ACA_REG_COUNT); - if (ret) - return ret; - } - - amdgpu_cper_ring_write(ring, corrected, corrected->record_length); - kfree(corrected); - - return 0; -} - static bool amdgpu_cper_is_hdr(struct amdgpu_ring *ring, u64 pos) { char signature[CPER_SIGNATURE_SZ]; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h index 353421807387..d12c98077d9d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h @@ -26,7 +26,6 @@ #define __AMDGPU_CPER_H__ #include "amd_cper.h" -#include "amdgpu_aca.h" #define CPER_MAX_ALLOWED_COUNT 0x1000 #define CPER_MAX_RING_SIZE 0X100000 @@ -88,13 +87,6 @@ int amdgpu_cper_entry_fill_bad_page_threshold_section(struct amdgpu_device *adev struct cper_hdr *amdgpu_cper_alloc_entry(struct amdgpu_device *adev, enum amdgpu_cper_type type, uint16_t section_count); -/* UE must be encoded into separated cper entries, 1 UE 1 cper */ -int amdgpu_cper_generate_ue_record(struct amdgpu_device *adev, - struct aca_bank *bank); -/* CEs and DEs are combined into 1 cper entry */ -int amdgpu_cper_generate_ce_records(struct amdgpu_device *adev, - struct aca_banks *banks, - uint16_t bank_count); /* Bad page threshold is encoded into separated cper entry */ int amdgpu_cper_generate_bp_threshold_record(struct amdgpu_device *adev); void amdgpu_cper_ring_write(struct amdgpu_ring *ring, -- cgit v1.2.3 From 9ebb70d268223987f20dae98f74c045b81ffb837 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 09:59:22 +0800 Subject: drm/amdgpu: retire pcs xgmi v6.4.0 legacy ras support retire pcs xgmi v6.4.0 legacy ras support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 162 +----------------------------- drivers/gpu/drm/amd/amdgpu/soc15_common.h | 6 -- 2 files changed, 2 insertions(+), 166 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index d8e1bd01eded..4ccc1bb6b22f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -116,43 +116,6 @@ static const int xgmi3x16_pcs_err_noncorrectable_mask_reg_v6_4[] = { smnPCS_XGMI3X16_PCS_ERROR_NONCORRECTABLE_MASK + 0x100000 }; -static const u64 xgmi_v6_4_0_mca_base_array[] = { - 0x11a09200, - 0x11b09200, -}; - -static const char *xgmi_v6_4_0_ras_error_code_ext[32] = { - [0x00] = "XGMI PCS DataLossErr", - [0x01] = "XGMI PCS TrainingErr", - [0x02] = "XGMI PCS FlowCtrlAckErr", - [0x03] = "XGMI PCS RxFifoUnderflowErr", - [0x04] = "XGMI PCS RxFifoOverflowErr", - [0x05] = "XGMI PCS CRCErr", - [0x06] = "XGMI PCS BERExceededErr", - [0x07] = "XGMI PCS TxMetaDataErr", - [0x08] = "XGMI PCS ReplayBufParityErr", - [0x09] = "XGMI PCS DataParityErr", - [0x0a] = "XGMI PCS ReplayFifoOverflowErr", - [0x0b] = "XGMI PCS ReplayFifoUnderflowErr", - [0x0c] = "XGMI PCS ElasticFifoOverflowErr", - [0x0d] = "XGMI PCS DeskewErr", - [0x0e] = "XGMI PCS FlowCtrlCRCErr", - [0x0f] = "XGMI PCS DataStartupLimitErr", - [0x10] = "XGMI PCS FCInitTimeoutErr", - [0x11] = "XGMI PCS RecoveryTimeoutErr", - [0x12] = "XGMI PCS ReadySerialTimeoutErr", - [0x13] = "XGMI PCS ReadySerialAttemptErr", - [0x14] = "XGMI PCS RecoveryAttemptErr", - [0x15] = "XGMI PCS RecoveryRelockAttemptErr", - [0x16] = "XGMI PCS ReplayAttemptErr", - [0x17] = "XGMI PCS SyncHdrErr", - [0x18] = "XGMI PCS TxReplayTimeoutErr", - [0x19] = "XGMI PCS RxReplayTimeoutErr", - [0x1a] = "XGMI PCS LinkSubTxTimeoutErr", - [0x1b] = "XGMI PCS LinkSubRxTimeoutErr", - [0x1c] = "XGMI PCS RxCMDPktErr", -}; - static const struct amdgpu_pcs_ras_field xgmi_pcs_ras_fields[] = { {"XGMI PCS DataLossErr", SOC15_REG_FIELD(XGMI0_PCS_GOPX16_PCS_ERROR_STATUS, DataLossErr)}, @@ -1176,7 +1139,7 @@ static void pcs_clear_status(struct amdgpu_device *adev, uint32_t pcs_status_reg WREG32_PCIE(pcs_status_reg, 0); } -static void amdgpu_xgmi_legacy_reset_ras_error_count(struct amdgpu_device *adev) +static void amdgpu_xgmi_reset_ras_error_count(struct amdgpu_device *adev) { uint32_t i; @@ -1215,43 +1178,6 @@ static void amdgpu_xgmi_legacy_reset_ras_error_count(struct amdgpu_device *adev) } } -static void __xgmi_v6_4_0_reset_error_count(struct amdgpu_device *adev, int xgmi_inst, u64 mca_base) -{ - uint64_t smn_base = - amdgpu_reg_get_smn_base64(adev, XGMI_HWIP, xgmi_inst); - - WREG64_MCA(smn_base, mca_base, ACA_REG_IDX_STATUS, 0ULL); -} - -static void xgmi_v6_4_0_reset_error_count(struct amdgpu_device *adev, int xgmi_inst) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(xgmi_v6_4_0_mca_base_array); i++) - __xgmi_v6_4_0_reset_error_count(adev, xgmi_inst, xgmi_v6_4_0_mca_base_array[i]); -} - -static void xgmi_v6_4_0_reset_ras_error_count(struct amdgpu_device *adev) -{ - int i; - - for_each_inst(i, adev->aid_mask) - xgmi_v6_4_0_reset_error_count(adev, i); -} - -static void amdgpu_xgmi_reset_ras_error_count(struct amdgpu_device *adev) -{ - switch (amdgpu_ip_version(adev, XGMI_HWIP, 0)) { - case IP_VERSION(6, 4, 0): - case IP_VERSION(6, 4, 1): - xgmi_v6_4_0_reset_ras_error_count(adev); - break; - default: - amdgpu_xgmi_legacy_reset_ras_error_count(adev); - break; - } -} - static int amdgpu_xgmi_query_pcs_error_status(struct amdgpu_device *adev, uint32_t value, uint32_t mask_value, @@ -1305,7 +1231,7 @@ static int amdgpu_xgmi_query_pcs_error_status(struct amdgpu_device *adev, return 0; } -static void amdgpu_xgmi_legacy_query_ras_error_count(struct amdgpu_device *adev, +static void amdgpu_xgmi_query_ras_error_count(struct amdgpu_device *adev, void *ras_error_status) { struct ras_err_data *err_data = (struct ras_err_data *)ras_error_status; @@ -1402,90 +1328,6 @@ static void amdgpu_xgmi_legacy_query_ras_error_count(struct amdgpu_device *adev, err_data->ce_count += ce_cnt; } -static enum aca_error_type xgmi_v6_4_0_pcs_mca_get_error_type(struct amdgpu_device *adev, u64 status) -{ - const char *error_str; - int ext_error_code; - - ext_error_code = ACA_REG__STATUS__ERRORCODEEXT(status); - - error_str = ext_error_code < ARRAY_SIZE(xgmi_v6_4_0_ras_error_code_ext) ? - xgmi_v6_4_0_ras_error_code_ext[ext_error_code] : NULL; - if (error_str) - dev_info(adev->dev, "%s detected\n", error_str); - - switch (ext_error_code) { - case 0: - return ACA_ERROR_TYPE_UE; - case 6: - return ACA_ERROR_TYPE_CE; - default: - return -EINVAL; - } - - return -EINVAL; -} - -static void __xgmi_v6_4_0_query_error_count(struct amdgpu_device *adev, struct amdgpu_smuio_mcm_config_info *mcm_info, - u64 mca_base, struct ras_err_data *err_data) -{ - int xgmi_inst = mcm_info->die_id; - uint64_t smn_base; - u64 status = 0; - - status = RREG64_MCA(xgmi_inst, mca_base, ACA_REG_IDX_STATUS); - if (!ACA_REG__STATUS__VAL(status)) - return; - - switch (xgmi_v6_4_0_pcs_mca_get_error_type(adev, status)) { - case ACA_ERROR_TYPE_UE: - amdgpu_ras_error_statistic_ue_count(err_data, mcm_info, 1ULL); - break; - case ACA_ERROR_TYPE_CE: - amdgpu_ras_error_statistic_ce_count(err_data, mcm_info, 1ULL); - break; - default: - break; - } - smn_base = amdgpu_reg_get_smn_base64(adev, XGMI_HWIP, xgmi_inst); - WREG64_MCA(smn_base, mca_base, ACA_REG_IDX_STATUS, 0ULL); -} - -static void xgmi_v6_4_0_query_error_count(struct amdgpu_device *adev, int xgmi_inst, struct ras_err_data *err_data) -{ - struct amdgpu_smuio_mcm_config_info mcm_info = { - .socket_id = adev->smuio.funcs->get_socket_id(adev), - .die_id = xgmi_inst, - }; - int i; - - for (i = 0; i < ARRAY_SIZE(xgmi_v6_4_0_mca_base_array); i++) - __xgmi_v6_4_0_query_error_count(adev, &mcm_info, xgmi_v6_4_0_mca_base_array[i], err_data); -} - -static void xgmi_v6_4_0_query_ras_error_count(struct amdgpu_device *adev, void *ras_error_status) -{ - struct ras_err_data *err_data = (struct ras_err_data *)ras_error_status; - int i; - - for_each_inst(i, adev->aid_mask) - xgmi_v6_4_0_query_error_count(adev, i, err_data); -} - -static void amdgpu_xgmi_query_ras_error_count(struct amdgpu_device *adev, - void *ras_error_status) -{ - switch (amdgpu_ip_version(adev, XGMI_HWIP, 0)) { - case IP_VERSION(6, 4, 0): - case IP_VERSION(6, 4, 1): - xgmi_v6_4_0_query_ras_error_count(adev, ras_error_status); - break; - default: - amdgpu_xgmi_legacy_query_ras_error_count(adev, ras_error_status); - break; - } -} - /* Trigger XGMI/WAFL error */ static int amdgpu_ras_error_inject_xgmi(struct amdgpu_device *adev, void *inject_if, uint32_t instance_mask) diff --git a/drivers/gpu/drm/amd/amdgpu/soc15_common.h b/drivers/gpu/drm/amd/amdgpu/soc15_common.h index e8c1d0f207e7..47e0329b6f3f 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc15_common.h +++ b/drivers/gpu/drm/amd/amdgpu/soc15_common.h @@ -210,10 +210,4 @@ do { \ amdgpu_reg_get_smn_base64(adev, ip##_HWIP, inst), \ value) -#define RREG64_MCA(smn_base, mca_base, idx) \ - RREG64_PCIE_EXT(smn_base + mca_base + (idx * 8)) - -#define WREG64_MCA(smn_base, mca_base, idx, val) \ - WREG64_PCIE_EXT(smn_base + mca_base + (idx * 8), val) - #endif -- cgit v1.2.3 From bea975345eaaef045c5ac62c6c9d8b22acd095a9 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 22 Jun 2026 16:32:56 +0800 Subject: drm/amd/pm: retire aca smu backend support for smu retire aca smu backend support for smu v13.0.6 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c | 153 --------------------- 1 file changed, 153 deletions(-) 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 b12388134489..ee3cd9c7777b 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 @@ -45,7 +45,6 @@ #include #include "amdgpu_ras.h" #include "amdgpu_mca.h" -#include "amdgpu_aca.h" #include "smu_cmn.h" #include "mp/mp_13_0_6_offset.h" #include "mp/mp_13_0_6_sh_mask.h" @@ -3764,157 +3763,6 @@ static const struct amdgpu_mca_smu_funcs smu_v13_0_6_mca_smu_funcs = { .mca_get_valid_mca_count = mca_smu_get_valid_mca_count, }; -static int aca_smu_set_debug_mode(struct amdgpu_device *adev, bool enable) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - - return smu_v13_0_6_mca_set_debug_mode(smu, enable); -} - -static int smu_v13_0_6_get_valid_aca_count(struct smu_context *smu, enum aca_smu_type type, u32 *count) -{ - uint32_t msg; - int ret; - - if (!count) - return -EINVAL; - - switch (type) { - case ACA_SMU_TYPE_UE: - msg = SMU_MSG_QueryValidMcaCount; - break; - case ACA_SMU_TYPE_CE: - msg = SMU_MSG_QueryValidMcaCeCount; - break; - default: - return -EINVAL; - } - - ret = smu_cmn_send_smc_msg(smu, msg, count); - if (ret) { - *count = 0; - return ret; - } - - return 0; -} - -static int aca_smu_get_valid_aca_count(struct amdgpu_device *adev, - enum aca_smu_type type, u32 *count) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - int ret; - - switch (type) { - case ACA_SMU_TYPE_UE: - case ACA_SMU_TYPE_CE: - ret = smu_v13_0_6_get_valid_aca_count(smu, type, count); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static int __smu_v13_0_6_aca_bank_dump(struct smu_context *smu, enum aca_smu_type type, - int idx, int offset, u32 *val) -{ - uint32_t msg, param; - - switch (type) { - case ACA_SMU_TYPE_UE: - msg = SMU_MSG_McaBankDumpDW; - break; - case ACA_SMU_TYPE_CE: - msg = SMU_MSG_McaBankCeDumpDW; - break; - default: - return -EINVAL; - } - - param = ((idx & 0xffff) << 16) | (offset & 0xfffc); - - return smu_cmn_send_smc_msg_with_param(smu, msg, param, (uint32_t *)val); -} - -static int smu_v13_0_6_aca_bank_dump(struct smu_context *smu, enum aca_smu_type type, - int idx, int offset, u32 *val, int count) -{ - int ret, i; - - if (!val) - return -EINVAL; - - for (i = 0; i < count; i++) { - ret = __smu_v13_0_6_aca_bank_dump(smu, type, idx, offset + (i << 2), &val[i]); - if (ret) - return ret; - } - - return 0; -} - -static int aca_bank_read_reg(struct amdgpu_device *adev, enum aca_smu_type type, - int idx, int reg_idx, u64 *val) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - u32 data[2] = {0, 0}; - int ret; - - if (!val || reg_idx >= ACA_REG_IDX_COUNT) - return -EINVAL; - - ret = smu_v13_0_6_aca_bank_dump(smu, type, idx, reg_idx * 8, data, ARRAY_SIZE(data)); - if (ret) - return ret; - - *val = (u64)data[1] << 32 | data[0]; - - dev_dbg(adev->dev, "mca read bank reg: type:%s, index: %d, reg_idx: %d, val: 0x%016llx\n", - type == ACA_SMU_TYPE_UE ? "UE" : "CE", idx, reg_idx, *val); - - return 0; -} - -static int aca_smu_get_valid_aca_bank(struct amdgpu_device *adev, - enum aca_smu_type type, int idx, struct aca_bank *bank) -{ - int i, ret, count; - - count = min_t(int, 16, ARRAY_SIZE(bank->regs)); - for (i = 0; i < count; i++) { - ret = aca_bank_read_reg(adev, type, idx, i, &bank->regs[i]); - if (ret) - return ret; - } - - return 0; -} - -static int aca_smu_parse_error_code(struct amdgpu_device *adev, struct aca_bank *bank) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - int error_code; - - if (smu_v13_0_6_cap_supported(smu, SMU_CAP(ACA_SYND))) - error_code = ACA_REG__SYND__ERRORINFORMATION(bank->regs[ACA_REG_IDX_SYND]); - else - error_code = ACA_REG__STATUS__ERRORCODE(bank->regs[ACA_REG_IDX_STATUS]); - - return error_code & 0xff; -} - -static const struct aca_smu_funcs smu_v13_0_6_aca_smu_funcs = { - .max_ue_bank_count = 12, - .max_ce_bank_count = 12, - .set_debug_mode = aca_smu_set_debug_mode, - .get_valid_aca_count = aca_smu_get_valid_aca_count, - .get_valid_aca_bank = aca_smu_get_valid_aca_bank, - .parse_error_code = aca_smu_parse_error_code, -}; - static void smu_v13_0_6_set_temp_funcs(struct smu_context *smu) { smu->smu_temp.temp_funcs = (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) @@ -4021,6 +3869,5 @@ void smu_v13_0_6_set_ppt_funcs(struct smu_context *smu) smu_v13_0_init_msg_ctl(smu, message_map); smu_v13_0_6_set_temp_funcs(smu); amdgpu_mca_smu_init_funcs(smu->adev, &smu_v13_0_6_mca_smu_funcs); - amdgpu_aca_set_smu_funcs(smu->adev, &smu_v13_0_6_aca_smu_funcs); } -- cgit v1.2.3 From 4623b958dd6da0f4c3026afdf330626a09ecb0f0 Mon Sep 17 00:00:00 2001 From: Harish Kasiviswanathan Date: Fri, 26 Jun 2026 12:21:54 -0400 Subject: drm/amdgpu: Fix kernel panic during driver load failure Avoid kernel panic if MES init fails during driver load. The KIQ ring is falsely marked as ready as ASICs that use MES, KIQ is owned by MES. BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:gfx_v12_1_wait_reg_mem+0x5a/0x1f0 [amdgpu] Call Trace: gfx_v12_1_ring_emit_reg_write_reg_wait+0x1f/0x30 [amdgpu] amdgpu_gmc_fw_reg_write_reg_wait+0xb2/0x190 [amdgpu] amdgpu_gmc_flush_gpu_tlb+0x1cc/0x230 [amdgpu] amdgpu_gart_invalidate_tlb+0x81/0xa0 [amdgpu] amdgpu_gart_unbind+0x72/0x90 [amdgpu] amdgpu_ttm_backend_unbind+0xa4/0xb0 [amdgpu] amdgpu_ttm_tt_unpopulate+0x13/0xd0 [amdgpu] amdttm_tt_unpopulate+0x29/0x70 [amdttm] ttm_bo_put+0x1eb/0x360 [amdttm] amdgpu_bo_free_kernel+0xf9/0x1f0 [amdgpu] amdgpu_ih_ring_fini+0x5a/0x90 [amdgpu] amdgpu_irq_fini_hw+0x58/0x80 [amdgpu] amdgpu_device_fini_hw+0x4e0/0x5b0 [amdgpu] amdgpu_driver_load_kms+0x60/0xa0 [amdgpu] amdgpu_pci_probe+0x28e/0x6d0 [amdgpu] pci_device_probe+0x19f/0x220 really_probe+0x1ed/0x340 driver_probe_device+0x1e/0x80 __driver_attach+0xd3/0x1a0 bus_for_each_dev+0x68/0xa0 bus_add_driver+0x19f/0x270 driver_register+0x5d/0xf0 do_one_initcall+0xac/0x200 do_init_module+0x1ec/0x280 __se_sys_finit_module+0x2de/0x310 do_syscall_64+0x6a/0x250 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Signed-off-by: Harish Kasiviswanathan Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 13 +++++++++++-- drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index cd6c1b6f8894..c765af54669c 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -3524,10 +3524,19 @@ static int gfx_v12_0_cp_resume(struct amdgpu_device *adev) gfx_v12_0_cp_gfx_enable(adev, true); } - if (adev->enable_mes_kiq && adev->mes.kiq_hw_init) + if (adev->enable_mes_kiq && adev->mes.kiq_hw_init) { r = amdgpu_mes_kiq_hw_init(adev, 0); - else + /* + * With MES, GFX KIQ ring is owned by the MES and is never + * initialized/used directly by the driver, so it must + * not be left flagged as ready. mes_v12_0_hw_init() clears + * but clear here if MES init fails + */ + if (r) + adev->gfx.kiq[0].ring.sched.ready = false; + } else { r = gfx_v12_0_kiq_resume(adev); + } if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index aaa8f4212a15..e87f1baf5cb6 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -2549,10 +2549,19 @@ static int gfx_v12_1_xcc_cp_resume(struct amdgpu_device *adev, uint16_t xcc_mask gfx_v12_1_xcc_cp_compute_enable(adev, true, xcc_id); - if (adev->enable_mes_kiq && adev->mes.kiq_hw_init) + if (adev->enable_mes_kiq && adev->mes.kiq_hw_init) { r = amdgpu_mes_kiq_hw_init(adev, xcc_id); - else + /* + * With MES, GFX KIQ ring is owned by the MES and is never + * initialized/used directly by the driver, so it must + * not be left flagged as ready. mes_v12_0_hw_init() clears + * but clear here if MES init fails + */ + if (r) + adev->gfx.kiq[xcc_id].ring.sched.ready = false; + } else { r = gfx_v12_1_xcc_kiq_resume(adev, xcc_id); + } if (r) return r; -- cgit v1.2.3 From a49c84d6fd58c416a5b4bcc363e915b3eddc7750 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 23 Jan 2026 14:53:39 +0800 Subject: drm/amdgpu: retire mmhub v1.8 ACA support retire mmhub v1.8 ACA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c | 92 --------------------------------- 1 file changed, 92 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c b/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c index cc688ae79e84..2a6a5ac4f374 100644 --- a/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c +++ b/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c @@ -772,100 +772,8 @@ static const struct amdgpu_ras_block_hw_ops mmhub_v1_8_ras_hw_ops = { .reset_ras_error_count = mmhub_v1_8_reset_ras_error_count, }; -static int mmhub_v1_8_aca_bank_parser(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - struct aca_bank_info info; - u64 misc0; - int ret; - - ret = aca_bank_info_decode(bank, &info); - if (ret) - return ret; - - misc0 = bank->regs[ACA_REG_IDX_MISC0]; - switch (type) { - case ACA_SMU_TYPE_UE: - bank->aca_err_type = ACA_ERROR_TYPE_UE; - ret = aca_error_cache_log_bank_error(handle, &info, ACA_ERROR_TYPE_UE, - 1ULL); - break; - case ACA_SMU_TYPE_CE: - bank->aca_err_type = ACA_ERROR_TYPE_CE; - ret = aca_error_cache_log_bank_error(handle, &info, bank->aca_err_type, - ACA_REG__MISC0__ERRCNT(misc0)); - break; - default: - return -EINVAL; - } - - return ret; -} - -/* reference to smu driver if header file */ -static int mmhub_v1_8_err_codes[] = { - 0, 1, 2, 3, 4, /* CODE_DAGB0 - 4 */ - 5, 6, 7, 8, 9, /* CODE_EA0 - 4 */ - 10, /* CODE_UTCL2_ROUTER */ - 11, /* CODE_VML2 */ - 12, /* CODE_VML2_WALKER */ - 13, /* CODE_MMCANE */ -}; - -static bool mmhub_v1_8_aca_bank_is_valid(struct aca_handle *handle, struct aca_bank *bank, - enum aca_smu_type type, void *data) -{ - u32 instlo; - - instlo = ACA_REG__IPID__INSTANCEIDLO(bank->regs[ACA_REG_IDX_IPID]); - instlo &= GENMASK(31, 1); - - if (instlo != mmSMNAID_AID0_MCA_SMU) - return false; - - if (aca_bank_check_error_codes(handle->adev, bank, - mmhub_v1_8_err_codes, - ARRAY_SIZE(mmhub_v1_8_err_codes))) - return false; - - return true; -} - -static const struct aca_bank_ops mmhub_v1_8_aca_bank_ops = { - .aca_bank_parser = mmhub_v1_8_aca_bank_parser, - .aca_bank_is_valid = mmhub_v1_8_aca_bank_is_valid, -}; - -static const struct aca_info mmhub_v1_8_aca_info = { - .hwip = ACA_HWIP_TYPE_SMU, - .mask = ACA_ERROR_UE_MASK, - .bank_ops = &mmhub_v1_8_aca_bank_ops, -}; - -static int mmhub_v1_8_ras_late_init(struct amdgpu_device *adev, struct ras_common_if *ras_block) -{ - int r; - - r = amdgpu_ras_block_late_init(adev, ras_block); - if (r) - return r; - - r = amdgpu_ras_bind_aca(adev, AMDGPU_RAS_BLOCK__MMHUB, - &mmhub_v1_8_aca_info, NULL); - if (r) - goto late_fini; - - return 0; - -late_fini: - amdgpu_ras_block_late_fini(adev, ras_block); - - return r; -} - struct amdgpu_mmhub_ras mmhub_v1_8_ras = { .ras_block = { .hw_ops = &mmhub_v1_8_ras_hw_ops, - .ras_late_init = mmhub_v1_8_ras_late_init, }, }; -- cgit v1.2.3 From 75b10b24fd1055a36fcd8ad9b659653c8d89aa0f Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Tue, 24 Feb 2026 10:12:33 +0800 Subject: drm/amdgpu: retire legacy RAS reset/query operations for sdma v4_4_2 retire legacy RAS reset/query operations for sdma v4_4_2 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.h | 28 --------- drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 104 +------------------------------ 2 files changed, 1 insertion(+), 131 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.h index 2bf365609775..4f4e56022c97 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.h @@ -85,34 +85,6 @@ struct amdgpu_sdma_instance { const struct amdgpu_sdma_funcs *funcs; }; -enum amdgpu_sdma_ras_memory_id { - AMDGPU_SDMA_MBANK_DATA_BUF0 = 1, - AMDGPU_SDMA_MBANK_DATA_BUF1 = 2, - AMDGPU_SDMA_MBANK_DATA_BUF2 = 3, - AMDGPU_SDMA_MBANK_DATA_BUF3 = 4, - AMDGPU_SDMA_MBANK_DATA_BUF4 = 5, - AMDGPU_SDMA_MBANK_DATA_BUF5 = 6, - AMDGPU_SDMA_MBANK_DATA_BUF6 = 7, - AMDGPU_SDMA_MBANK_DATA_BUF7 = 8, - AMDGPU_SDMA_MBANK_DATA_BUF8 = 9, - AMDGPU_SDMA_MBANK_DATA_BUF9 = 10, - AMDGPU_SDMA_MBANK_DATA_BUF10 = 11, - AMDGPU_SDMA_MBANK_DATA_BUF11 = 12, - AMDGPU_SDMA_MBANK_DATA_BUF12 = 13, - AMDGPU_SDMA_MBANK_DATA_BUF13 = 14, - AMDGPU_SDMA_MBANK_DATA_BUF14 = 15, - AMDGPU_SDMA_MBANK_DATA_BUF15 = 16, - AMDGPU_SDMA_UCODE_BUF = 17, - AMDGPU_SDMA_RB_CMD_BUF = 18, - AMDGPU_SDMA_IB_CMD_BUF = 19, - AMDGPU_SDMA_UTCL1_RD_FIFO = 20, - AMDGPU_SDMA_UTCL1_RDBST_FIFO = 21, - AMDGPU_SDMA_UTCL1_WR_FIFO = 22, - AMDGPU_SDMA_DATA_LUT_FIFO = 23, - AMDGPU_SDMA_SPLIT_DAT_BUF = 24, - AMDGPU_SDMA_MEMORY_BLOCK_LAST, -}; - struct amdgpu_sdma_ras { struct amdgpu_ras_block_object ras_block; }; diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c index 0d7e22060a92..484f1a6b5fbc 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c @@ -2416,111 +2416,9 @@ struct amdgpu_xcp_ip_funcs sdma_v4_4_2_xcp_funcs = { .resume = &sdma_v4_4_2_xcp_resume }; -static const struct amdgpu_ras_err_status_reg_entry sdma_v4_2_2_ue_reg_list[] = { - {AMDGPU_RAS_REG_ENTRY(SDMA0, 0, regSDMA_UE_ERR_STATUS_LO, regSDMA_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SDMA"}, -}; - -static const struct amdgpu_ras_memory_id_entry sdma_v4_4_2_ras_memory_list[] = { - {AMDGPU_SDMA_MBANK_DATA_BUF0, "SDMA_MBANK_DATA_BUF0"}, - {AMDGPU_SDMA_MBANK_DATA_BUF1, "SDMA_MBANK_DATA_BUF1"}, - {AMDGPU_SDMA_MBANK_DATA_BUF2, "SDMA_MBANK_DATA_BUF2"}, - {AMDGPU_SDMA_MBANK_DATA_BUF3, "SDMA_MBANK_DATA_BUF3"}, - {AMDGPU_SDMA_MBANK_DATA_BUF4, "SDMA_MBANK_DATA_BUF4"}, - {AMDGPU_SDMA_MBANK_DATA_BUF5, "SDMA_MBANK_DATA_BUF5"}, - {AMDGPU_SDMA_MBANK_DATA_BUF6, "SDMA_MBANK_DATA_BUF6"}, - {AMDGPU_SDMA_MBANK_DATA_BUF7, "SDMA_MBANK_DATA_BUF7"}, - {AMDGPU_SDMA_MBANK_DATA_BUF8, "SDMA_MBANK_DATA_BUF8"}, - {AMDGPU_SDMA_MBANK_DATA_BUF9, "SDMA_MBANK_DATA_BUF9"}, - {AMDGPU_SDMA_MBANK_DATA_BUF10, "SDMA_MBANK_DATA_BUF10"}, - {AMDGPU_SDMA_MBANK_DATA_BUF11, "SDMA_MBANK_DATA_BUF11"}, - {AMDGPU_SDMA_MBANK_DATA_BUF12, "SDMA_MBANK_DATA_BUF12"}, - {AMDGPU_SDMA_MBANK_DATA_BUF13, "SDMA_MBANK_DATA_BUF13"}, - {AMDGPU_SDMA_MBANK_DATA_BUF14, "SDMA_MBANK_DATA_BUF14"}, - {AMDGPU_SDMA_MBANK_DATA_BUF15, "SDMA_MBANK_DATA_BUF15"}, - {AMDGPU_SDMA_UCODE_BUF, "SDMA_UCODE_BUF"}, - {AMDGPU_SDMA_RB_CMD_BUF, "SDMA_RB_CMD_BUF"}, - {AMDGPU_SDMA_IB_CMD_BUF, "SDMA_IB_CMD_BUF"}, - {AMDGPU_SDMA_UTCL1_RD_FIFO, "SDMA_UTCL1_RD_FIFO"}, - {AMDGPU_SDMA_UTCL1_RDBST_FIFO, "SDMA_UTCL1_RDBST_FIFO"}, - {AMDGPU_SDMA_UTCL1_WR_FIFO, "SDMA_UTCL1_WR_FIFO"}, - {AMDGPU_SDMA_DATA_LUT_FIFO, "SDMA_DATA_LUT_FIFO"}, - {AMDGPU_SDMA_SPLIT_DAT_BUF, "SDMA_SPLIT_DAT_BUF"}, -}; - -static void sdma_v4_4_2_inst_query_ras_error_count(struct amdgpu_device *adev, - uint32_t sdma_inst, - void *ras_err_status) -{ - struct ras_err_data *err_data = (struct ras_err_data *)ras_err_status; - uint32_t sdma_dev_inst = GET_INST(SDMA0, sdma_inst); - unsigned long ue_count = 0; - struct amdgpu_smuio_mcm_config_info mcm_info = { - .socket_id = adev->smuio.funcs->get_socket_id(adev), - .die_id = adev->sdma.instance[sdma_inst].aid_id, - }; - - /* sdma v4_4_2 doesn't support query ce counts */ - amdgpu_ras_inst_query_ras_error_count(adev, - sdma_v4_2_2_ue_reg_list, - ARRAY_SIZE(sdma_v4_2_2_ue_reg_list), - sdma_v4_4_2_ras_memory_list, - ARRAY_SIZE(sdma_v4_4_2_ras_memory_list), - sdma_dev_inst, - AMDGPU_RAS_ERROR__MULTI_UNCORRECTABLE, - &ue_count); - - amdgpu_ras_error_statistic_ue_count(err_data, &mcm_info, ue_count); -} - -static void sdma_v4_4_2_query_ras_error_count(struct amdgpu_device *adev, - void *ras_err_status) -{ - uint32_t inst_mask; - int i = 0; - - inst_mask = GENMASK(adev->sdma.num_instances - 1, 0); - if (amdgpu_ras_is_supported(adev, AMDGPU_RAS_BLOCK__SDMA)) { - for_each_inst(i, inst_mask) - sdma_v4_4_2_inst_query_ras_error_count(adev, i, ras_err_status); - } else { - dev_warn(adev->dev, "SDMA RAS is not supported\n"); - } -} - -static void sdma_v4_4_2_inst_reset_ras_error_count(struct amdgpu_device *adev, - uint32_t sdma_inst) -{ - uint32_t sdma_dev_inst = GET_INST(SDMA0, sdma_inst); - - amdgpu_ras_inst_reset_ras_error_count(adev, - sdma_v4_2_2_ue_reg_list, - ARRAY_SIZE(sdma_v4_2_2_ue_reg_list), - sdma_dev_inst); -} - -static void sdma_v4_4_2_reset_ras_error_count(struct amdgpu_device *adev) -{ - uint32_t inst_mask; - int i = 0; - - inst_mask = GENMASK(adev->sdma.num_instances - 1, 0); - if (amdgpu_ras_is_supported(adev, AMDGPU_RAS_BLOCK__SDMA)) { - for_each_inst(i, inst_mask) - sdma_v4_4_2_inst_reset_ras_error_count(adev, i); - } else { - dev_warn(adev->dev, "SDMA RAS is not supported\n"); - } -} - -static const struct amdgpu_ras_block_hw_ops sdma_v4_4_2_ras_hw_ops = { - .query_ras_error_count = sdma_v4_4_2_query_ras_error_count, - .reset_ras_error_count = sdma_v4_4_2_reset_ras_error_count, -}; - static struct amdgpu_sdma_ras sdma_v4_4_2_ras = { .ras_block = { - .hw_ops = &sdma_v4_4_2_ras_hw_ops, + .hw_ops = NULL, }, }; -- cgit v1.2.3 From ceac787584225883e590b8141503f54c28c2e2cf Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 10:17:14 +0800 Subject: drm/amd/pm: retire smu_13_0_6 mca dump support retire smu_13_0_6 mca dump support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c | 497 --------------------- 1 file changed, 497 deletions(-) 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 ee3cd9c7777b..957c158c8e2a 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 @@ -44,7 +44,6 @@ #include "amdgpu_xgmi.h" #include #include "amdgpu_ras.h" -#include "amdgpu_mca.h" #include "smu_cmn.h" #include "mp/mp_13_0_6_offset.h" #include "mp/mp_13_0_6_sh_mask.h" @@ -98,25 +97,6 @@ static const struct smu_feature_bits smu_v13_0_6_dpm_features = { #define PCIE_LC_SPEED_CNTL__LC_CURRENT_DATA_RATE_MASK 0xE0 #define PCIE_LC_SPEED_CNTL__LC_CURRENT_DATA_RATE__SHIFT 0x5 #define LINK_SPEED_MAX 4 -#define MCA_BANK_IPID(_ip, _hwid, _type) \ - [AMDGPU_MCA_IP_##_ip] = { .hwid = _hwid, .mcatype = _type, } - -struct mca_bank_ipid { - enum amdgpu_mca_ip ip; - uint16_t hwid; - uint16_t mcatype; -}; - -struct mca_ras_info { - enum amdgpu_ras_block blkid; - enum amdgpu_mca_ip ip; - int *err_code_array; - int err_code_count; - int (*get_err_count)(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry, uint32_t *count); - bool (*bank_is_valid)(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry); -}; #define P2S_TABLE_ID_A 0x50325341 #define P2S_TABLE_ID_X 0x50325358 @@ -1942,17 +1922,6 @@ static int smu_v13_0_6_notify_unload(struct smu_context *smu) return 0; } -static int smu_v13_0_6_mca_set_debug_mode(struct smu_context *smu, bool enable) -{ - /* NOTE: this ClearMcaOnRead message is only supported for smu version 85.72.0 or higher */ - if (!smu_v13_0_6_cap_supported(smu, SMU_CAP(MCA_DEBUG_MODE))) - return 0; - - return smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_ClearMcaOnRead, - enable ? 0 : ClearMcaOnRead_UE_FLAG_MASK | ClearMcaOnRead_CE_POLL_MASK, - NULL); -} - static int smu_v13_0_6_system_features_control(struct smu_context *smu, bool enable) { @@ -3298,471 +3267,6 @@ static int smu_v13_0_6_post_init(struct smu_context *smu) return 0; } -static int mca_smu_set_debug_mode(struct amdgpu_device *adev, bool enable) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - - return smu_v13_0_6_mca_set_debug_mode(smu, enable); -} - -static int smu_v13_0_6_get_valid_mca_count(struct smu_context *smu, enum amdgpu_mca_error_type type, uint32_t *count) -{ - uint32_t msg; - int ret; - - if (!count) - return -EINVAL; - - switch (type) { - case AMDGPU_MCA_ERROR_TYPE_UE: - msg = SMU_MSG_QueryValidMcaCount; - break; - case AMDGPU_MCA_ERROR_TYPE_CE: - msg = SMU_MSG_QueryValidMcaCeCount; - break; - default: - return -EINVAL; - } - - ret = smu_cmn_send_smc_msg(smu, msg, count); - if (ret) { - *count = 0; - return ret; - } - - return 0; -} - -static int __smu_v13_0_6_mca_dump_bank(struct smu_context *smu, enum amdgpu_mca_error_type type, - int idx, int offset, uint32_t *val) -{ - uint32_t msg, param; - - switch (type) { - case AMDGPU_MCA_ERROR_TYPE_UE: - msg = SMU_MSG_McaBankDumpDW; - break; - case AMDGPU_MCA_ERROR_TYPE_CE: - msg = SMU_MSG_McaBankCeDumpDW; - break; - default: - return -EINVAL; - } - - param = ((idx & 0xffff) << 16) | (offset & 0xfffc); - - return smu_cmn_send_smc_msg_with_param(smu, msg, param, val); -} - -static int smu_v13_0_6_mca_dump_bank(struct smu_context *smu, enum amdgpu_mca_error_type type, - int idx, int offset, uint32_t *val, int count) -{ - int ret, i; - - if (!val) - return -EINVAL; - - for (i = 0; i < count; i++) { - ret = __smu_v13_0_6_mca_dump_bank(smu, type, idx, offset + (i << 2), &val[i]); - if (ret) - return ret; - } - - return 0; -} - -static const struct mca_bank_ipid smu_v13_0_6_mca_ipid_table[AMDGPU_MCA_IP_COUNT] = { - MCA_BANK_IPID(UMC, 0x96, 0x0), - MCA_BANK_IPID(SMU, 0x01, 0x1), - MCA_BANK_IPID(MP5, 0x01, 0x2), - MCA_BANK_IPID(PCS_XGMI, 0x50, 0x0), -}; - -static void mca_bank_entry_info_decode(struct mca_bank_entry *entry, struct mca_bank_info *info) -{ - u64 ipid = entry->regs[MCA_REG_IDX_IPID]; - u32 instidhi, instid; - - /* NOTE: All MCA IPID register share the same format, - * so the driver can share the MCMP1 register header file. - * */ - - info->hwid = REG_GET_FIELD(ipid, MCMP1_IPIDT0, HardwareID); - info->mcatype = REG_GET_FIELD(ipid, MCMP1_IPIDT0, McaType); - - /* - * Unfied DieID Format: SAASS. A:AID, S:Socket. - * Unfied DieID[4] = InstanceId[0] - * Unfied DieID[0:3] = InstanceIdHi[0:3] - */ - instidhi = REG_GET_FIELD(ipid, MCMP1_IPIDT0, InstanceIdHi); - instid = REG_GET_FIELD(ipid, MCMP1_IPIDT0, InstanceIdLo); - info->aid = ((instidhi >> 2) & 0x03); - info->socket_id = ((instid & 0x1) << 2) | (instidhi & 0x03); -} - -static int mca_bank_read_reg(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, - int idx, int reg_idx, uint64_t *val) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - uint32_t data[2] = {0, 0}; - int ret; - - if (!val || reg_idx >= MCA_REG_IDX_COUNT) - return -EINVAL; - - ret = smu_v13_0_6_mca_dump_bank(smu, type, idx, reg_idx * 8, data, ARRAY_SIZE(data)); - if (ret) - return ret; - - *val = (uint64_t)data[1] << 32 | data[0]; - - dev_dbg(adev->dev, "mca read bank reg: type:%s, index: %d, reg_idx: %d, val: 0x%016llx\n", - type == AMDGPU_MCA_ERROR_TYPE_UE ? "UE" : "CE", idx, reg_idx, *val); - - return 0; -} - -static int mca_get_mca_entry(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, - int idx, struct mca_bank_entry *entry) -{ - int i, ret; - - /* NOTE: populated all mca register by default */ - for (i = 0; i < ARRAY_SIZE(entry->regs); i++) { - ret = mca_bank_read_reg(adev, type, idx, i, &entry->regs[i]); - if (ret) - return ret; - } - - entry->idx = idx; - entry->type = type; - - mca_bank_entry_info_decode(entry, &entry->info); - - return 0; -} - -static int mca_decode_ipid_to_hwip(uint64_t val) -{ - const struct mca_bank_ipid *ipid; - uint16_t hwid, mcatype; - int i; - - hwid = REG_GET_FIELD(val, MCMP1_IPIDT0, HardwareID); - mcatype = REG_GET_FIELD(val, MCMP1_IPIDT0, McaType); - - for (i = 0; i < ARRAY_SIZE(smu_v13_0_6_mca_ipid_table); i++) { - ipid = &smu_v13_0_6_mca_ipid_table[i]; - - if (!ipid->hwid) - continue; - - if (ipid->hwid == hwid && ipid->mcatype == mcatype) - return i; - } - - return AMDGPU_MCA_IP_UNKNOW; -} - -static int mca_umc_mca_get_err_count(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry, uint32_t *count) -{ - uint64_t status0; - uint32_t ext_error_code; - uint32_t odecc_err_cnt; - - status0 = entry->regs[MCA_REG_IDX_STATUS]; - ext_error_code = MCA_REG__STATUS__ERRORCODEEXT(status0); - odecc_err_cnt = MCA_REG__MISC0__ERRCNT(entry->regs[MCA_REG_IDX_MISC0]); - - if (!REG_GET_FIELD(status0, MCMP1_STATUST0, Val)) { - *count = 0; - return 0; - } - - if (umc_v12_0_is_deferred_error(adev, status0) || - umc_v12_0_is_uncorrectable_error(adev, status0) || - umc_v12_0_is_correctable_error(adev, status0)) - *count = (ext_error_code == 0) ? odecc_err_cnt : 1; - - amdgpu_umc_update_ecc_status(adev, - entry->regs[MCA_REG_IDX_STATUS], - entry->regs[MCA_REG_IDX_IPID], - entry->regs[MCA_REG_IDX_ADDR]); - - return 0; -} - -static int mca_pcs_xgmi_mca_get_err_count(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry, - uint32_t *count) -{ - u32 ext_error_code; - u32 err_cnt; - - ext_error_code = MCA_REG__STATUS__ERRORCODEEXT(entry->regs[MCA_REG_IDX_STATUS]); - err_cnt = MCA_REG__MISC0__ERRCNT(entry->regs[MCA_REG_IDX_MISC0]); - - if (type == AMDGPU_MCA_ERROR_TYPE_UE && - (ext_error_code == 0 || ext_error_code == 9)) - *count = err_cnt; - else if (type == AMDGPU_MCA_ERROR_TYPE_CE && ext_error_code == 6) - *count = err_cnt; - - return 0; -} - -static bool mca_smu_check_error_code(struct amdgpu_device *adev, const struct mca_ras_info *mca_ras, - uint32_t errcode) -{ - int i; - - if (!mca_ras->err_code_count || !mca_ras->err_code_array) - return true; - - for (i = 0; i < mca_ras->err_code_count; i++) { - if (errcode == mca_ras->err_code_array[i]) - return true; - } - - return false; -} - -static int mca_gfx_mca_get_err_count(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry, uint32_t *count) -{ - uint64_t status0, misc0; - - status0 = entry->regs[MCA_REG_IDX_STATUS]; - if (!REG_GET_FIELD(status0, MCMP1_STATUST0, Val)) { - *count = 0; - return 0; - } - - if (type == AMDGPU_MCA_ERROR_TYPE_UE && - REG_GET_FIELD(status0, MCMP1_STATUST0, UC) == 1 && - REG_GET_FIELD(status0, MCMP1_STATUST0, PCC) == 1) { - *count = 1; - return 0; - } else { - misc0 = entry->regs[MCA_REG_IDX_MISC0]; - *count = REG_GET_FIELD(misc0, MCMP1_MISC0T0, ErrCnt); - } - - return 0; -} - -static int mca_smu_mca_get_err_count(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry, uint32_t *count) -{ - uint64_t status0, misc0; - - status0 = entry->regs[MCA_REG_IDX_STATUS]; - if (!REG_GET_FIELD(status0, MCMP1_STATUST0, Val)) { - *count = 0; - return 0; - } - - if (type == AMDGPU_MCA_ERROR_TYPE_UE && - REG_GET_FIELD(status0, MCMP1_STATUST0, UC) == 1 && - REG_GET_FIELD(status0, MCMP1_STATUST0, PCC) == 1) { - if (count) - *count = 1; - return 0; - } - - misc0 = entry->regs[MCA_REG_IDX_MISC0]; - *count = REG_GET_FIELD(misc0, MCMP1_MISC0T0, ErrCnt); - - return 0; -} - -static bool mca_gfx_smu_bank_is_valid(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry) -{ - uint32_t instlo; - - instlo = REG_GET_FIELD(entry->regs[MCA_REG_IDX_IPID], MCMP1_IPIDT0, InstanceIdLo); - instlo &= GENMASK(31, 1); - switch (instlo) { - case 0x36430400: /* SMNAID XCD 0 */ - case 0x38430400: /* SMNAID XCD 1 */ - case 0x40430400: /* SMNXCD XCD 0, NOTE: FIXME: fix this error later */ - return true; - default: - return false; - } - - return false; -}; - -static bool mca_smu_bank_is_valid(const struct mca_ras_info *mca_ras, struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - uint32_t errcode, instlo; - - instlo = REG_GET_FIELD(entry->regs[MCA_REG_IDX_IPID], MCMP1_IPIDT0, InstanceIdLo); - instlo &= GENMASK(31, 1); - if (instlo != 0x03b30400) - return false; - - if (smu_v13_0_6_cap_supported(smu, SMU_CAP(ACA_SYND))) { - errcode = MCA_REG__SYND__ERRORINFORMATION(entry->regs[MCA_REG_IDX_SYND]); - errcode &= 0xff; - } else { - errcode = REG_GET_FIELD(entry->regs[MCA_REG_IDX_STATUS], MCMP1_STATUST0, ErrorCode); - } - - return mca_smu_check_error_code(adev, mca_ras, errcode); -} - -static int sdma_err_codes[] = { CODE_SDMA0, CODE_SDMA1, CODE_SDMA2, CODE_SDMA3 }; -static int mmhub_err_codes[] = { - CODE_DAGB0, CODE_DAGB0 + 1, CODE_DAGB0 + 2, CODE_DAGB0 + 3, CODE_DAGB0 + 4, /* DAGB0-4 */ - CODE_EA0, CODE_EA0 + 1, CODE_EA0 + 2, CODE_EA0 + 3, CODE_EA0 + 4, /* MMEA0-4*/ - CODE_VML2, CODE_VML2_WALKER, CODE_MMCANE, -}; - -static int vcn_err_codes[] = { - CODE_VIDD, CODE_VIDV, -}; -static int jpeg_err_codes[] = { - CODE_JPEG0S, CODE_JPEG0D, CODE_JPEG1S, CODE_JPEG1D, - CODE_JPEG2S, CODE_JPEG2D, CODE_JPEG3S, CODE_JPEG3D, - CODE_JPEG4S, CODE_JPEG4D, CODE_JPEG5S, CODE_JPEG5D, - CODE_JPEG6S, CODE_JPEG6D, CODE_JPEG7S, CODE_JPEG7D, -}; - -static const struct mca_ras_info mca_ras_table[] = { - { - .blkid = AMDGPU_RAS_BLOCK__UMC, - .ip = AMDGPU_MCA_IP_UMC, - .get_err_count = mca_umc_mca_get_err_count, - }, { - .blkid = AMDGPU_RAS_BLOCK__GFX, - .ip = AMDGPU_MCA_IP_SMU, - .get_err_count = mca_gfx_mca_get_err_count, - .bank_is_valid = mca_gfx_smu_bank_is_valid, - }, { - .blkid = AMDGPU_RAS_BLOCK__SDMA, - .ip = AMDGPU_MCA_IP_SMU, - .err_code_array = sdma_err_codes, - .err_code_count = ARRAY_SIZE(sdma_err_codes), - .get_err_count = mca_smu_mca_get_err_count, - .bank_is_valid = mca_smu_bank_is_valid, - }, { - .blkid = AMDGPU_RAS_BLOCK__MMHUB, - .ip = AMDGPU_MCA_IP_SMU, - .err_code_array = mmhub_err_codes, - .err_code_count = ARRAY_SIZE(mmhub_err_codes), - .get_err_count = mca_smu_mca_get_err_count, - .bank_is_valid = mca_smu_bank_is_valid, - }, { - .blkid = AMDGPU_RAS_BLOCK__XGMI_WAFL, - .ip = AMDGPU_MCA_IP_PCS_XGMI, - .get_err_count = mca_pcs_xgmi_mca_get_err_count, - }, { - .blkid = AMDGPU_RAS_BLOCK__VCN, - .ip = AMDGPU_MCA_IP_SMU, - .err_code_array = vcn_err_codes, - .err_code_count = ARRAY_SIZE(vcn_err_codes), - .get_err_count = mca_smu_mca_get_err_count, - .bank_is_valid = mca_smu_bank_is_valid, - }, { - .blkid = AMDGPU_RAS_BLOCK__JPEG, - .ip = AMDGPU_MCA_IP_SMU, - .err_code_array = jpeg_err_codes, - .err_code_count = ARRAY_SIZE(jpeg_err_codes), - .get_err_count = mca_smu_mca_get_err_count, - .bank_is_valid = mca_smu_bank_is_valid, - }, -}; - -static const struct mca_ras_info *mca_get_mca_ras_info(struct amdgpu_device *adev, enum amdgpu_ras_block blkid) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(mca_ras_table); i++) { - if (mca_ras_table[i].blkid == blkid) - return &mca_ras_table[i]; - } - - return NULL; -} - -static int mca_get_valid_mca_count(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, uint32_t *count) -{ - struct smu_context *smu = adev->powerplay.pp_handle; - int ret; - - switch (type) { - case AMDGPU_MCA_ERROR_TYPE_UE: - case AMDGPU_MCA_ERROR_TYPE_CE: - ret = smu_v13_0_6_get_valid_mca_count(smu, type, count); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static bool mca_bank_is_valid(struct amdgpu_device *adev, const struct mca_ras_info *mca_ras, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry) -{ - if (mca_decode_ipid_to_hwip(entry->regs[MCA_REG_IDX_IPID]) != mca_ras->ip) - return false; - - if (mca_ras->bank_is_valid) - return mca_ras->bank_is_valid(mca_ras, adev, type, entry); - - return true; -} - -static int mca_smu_parse_mca_error_count(struct amdgpu_device *adev, enum amdgpu_ras_block blk, enum amdgpu_mca_error_type type, - struct mca_bank_entry *entry, uint32_t *count) -{ - const struct mca_ras_info *mca_ras; - - if (!entry || !count) - return -EINVAL; - - mca_ras = mca_get_mca_ras_info(adev, blk); - if (!mca_ras) - return -EOPNOTSUPP; - - if (!mca_bank_is_valid(adev, mca_ras, type, entry)) { - *count = 0; - return 0; - } - - return mca_ras->get_err_count(mca_ras, adev, type, entry, count); -} - -static int mca_smu_get_mca_entry(struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, int idx, struct mca_bank_entry *entry) -{ - return mca_get_mca_entry(adev, type, idx, entry); -} - -static int mca_smu_get_valid_mca_count(struct amdgpu_device *adev, - enum amdgpu_mca_error_type type, uint32_t *count) -{ - return mca_get_valid_mca_count(adev, type, count); -} - -static const struct amdgpu_mca_smu_funcs smu_v13_0_6_mca_smu_funcs = { - .max_ue_count = 12, - .max_ce_count = 12, - .mca_set_debug_mode = mca_smu_set_debug_mode, - .mca_parse_mca_error_count = mca_smu_parse_mca_error_count, - .mca_get_mca_entry = mca_smu_get_mca_entry, - .mca_get_valid_mca_count = mca_smu_get_valid_mca_count, -}; - static void smu_v13_0_6_set_temp_funcs(struct smu_context *smu) { smu->smu_temp.temp_funcs = (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) @@ -3868,6 +3372,5 @@ void smu_v13_0_6_set_ppt_funcs(struct smu_context *smu) smu->smc_fw_caps |= SMU_FW_CAP_RAS_PRI; smu_v13_0_init_msg_ctl(smu, message_map); smu_v13_0_6_set_temp_funcs(smu); - amdgpu_mca_smu_init_funcs(smu->adev, &smu_v13_0_6_mca_smu_funcs); } -- cgit v1.2.3 From 60b048c93f7a3add39757ad65fe2bb6e58eeae23 Mon Sep 17 00:00:00 2001 From: David Francis Date: Thu, 25 Jun 2026 10:09:13 -0400 Subject: drm/amdkfd: Use kvcalloc to allocate arrays There were a few instances in kfd_chardev.c of kvzalloc being used to allocate memory for an array. Switch those to kvcalloc, which - is the standard way of allocating a zero-initialized array - does a check for the mul overflowing Signed-off-by: David Francis Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 38c6cb1f49a6..411ee894f623 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1917,13 +1917,13 @@ static int criu_checkpoint_devices(struct kfd_process *p, struct kfd_criu_device_bucket *device_buckets = NULL; int ret = 0, i; - device_buckets = kvzalloc(num_devices * sizeof(*device_buckets), GFP_KERNEL); + device_buckets = kvcalloc(num_devices, sizeof(*device_buckets), GFP_KERNEL); if (!device_buckets) { ret = -ENOMEM; goto exit; } - device_priv = kvzalloc(num_devices * sizeof(*device_priv), GFP_KERNEL); + device_priv = kvcalloc(num_devices, sizeof(*device_priv), GFP_KERNEL); if (!device_priv) { ret = -ENOMEM; goto exit; @@ -2043,17 +2043,17 @@ static int criu_checkpoint_bos(struct kfd_process *p, int ret = 0, pdd_index, bo_index = 0, id; void *mem; - bo_buckets = kvzalloc(num_bos * sizeof(*bo_buckets), GFP_KERNEL); + bo_buckets = kvcalloc(num_bos, sizeof(*bo_buckets), GFP_KERNEL); if (!bo_buckets) return -ENOMEM; - bo_privs = kvzalloc(num_bos * sizeof(*bo_privs), GFP_KERNEL); + bo_privs = kvcalloc(num_bos, sizeof(*bo_privs), GFP_KERNEL); if (!bo_privs) { ret = -ENOMEM; goto exit; } - files = kvzalloc(num_bos * sizeof(struct file *), GFP_KERNEL); + files = kvcalloc(num_bos, sizeof(struct file *), GFP_KERNEL); if (!files) { ret = -ENOMEM; goto exit; @@ -2584,7 +2584,7 @@ static int criu_restore_bos(struct kfd_process *p, if (!bo_buckets) return -ENOMEM; - files = kvzalloc(args->num_bos * sizeof(struct file *), GFP_KERNEL); + files = kvcalloc(args->num_bos, sizeof(struct file *), GFP_KERNEL); if (!files) { ret = -ENOMEM; goto exit; -- cgit v1.2.3 From 4986757d3207383c052689532e505edccc6df7c8 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Thu, 25 Jun 2026 10:25:24 +0530 Subject: drm/amdgpu/powerplay: Align get_tonga_state_array() header with prototype The function header above get_tonga_state_array() still refers to check_powerplay_tables() and does not describe all of the function parameters. Update it to match the current function prototype and include the missing parameter description. Fixes: 1ac24df78c56 ("drm/amd/pm: Validate Tonga PowerPlay state array bounds") Cc: Yang Wang Cc: Hawking Zhang Signed-off-by: Srinivasan Shanmugam Reviewed-by: Yang Wang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c index 71017ca154f0..c5673077c895 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c @@ -1539,11 +1539,12 @@ static int init_thermal_controller( } /** - * check_powerplay_tables - Private Function used during initialization. - * Inspect the PowerPlay table for obvious signs of corruption. + * get_tonga_state_array - Get the Tonga state array from the PowerPlay table. * @hwmgr: Pointer to the hardware manager. * @powerplay_table: Pointer to the PowerPlay Table. - * Exception: 2 if the powerplay table is incorrect. + * @state_array: Pointer to the returned Tonga state array. + * + * Return: 0 on success, negative error code on failure. */ static int get_tonga_state_array(struct pp_hwmgr *hwmgr, const ATOM_Tonga_POWERPLAYTABLE *powerplay_table, -- cgit v1.2.3 From 27213b776a666d3030de5acc3cd75278197b0494 Mon Sep 17 00:00:00 2001 From: Donet Tom Date: Thu, 25 Jun 2026 13:22:06 +0530 Subject: drm/amdgpu: Fix AMDGPU_GTT_MAX_TRANSFER_SIZE for non-4K systems Running RCCL unit tests on a system with a 64K PAGE_SIZE triggers the following warning and causes the test to terminate on latest upstream kernel: WARNING: drivers/gpu/drm/amd/amdgpu/amdgpu_object.c:1335 at amdgpu_bo_release_notify+0x1bc/0x280 [amdgpu], CPU#18: rccl-UnitTests/33151 Call trace: amdgpu_bo_release_notify ttm_bo_release amdgpu_gem_object_free drm_gem_object_free amdgpu_bo_unref amdgpu_bo_create amdgpu_bo_create_user amdgpu_gem_object_create amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu kfd_ioctl_alloc_memory_of_gpu kfd_ioctl sys_ioctl The warning is triggered because amdgpu_ttm_next_clear_entity() returns NULL when a clear buffer operation is requested. This happens because the GART window allocation for the default_entity, clear_entity and move_entity fails during initialization. Commit [1] introduced separate GART windows for the default_entity, clear_entity and move_entity of each SDMA instance. Their sizes are derived from AMDGPU_GTT_MAX_TRANSFER_SIZE, which is currently defined as 1024 pages. This implicitly assumes a 4K PAGE_SIZE, where 1024 pages correspond to a 4MB transfer. On a 64K PAGE_SIZE system, however, the same value expands to 64MB. The default_entity and clear_entity each allocate one AMDGPU_GTT_MAX_TRANSFER_SIZE GART window, while the move_entity allocates two such windows. This results in 16MB of GART space per SDMA instance on a 4K PAGE_SIZE system, but 256MB per SDMA instance on a 64K PAGE_SIZE system. On an MI210 system with five SDMA instances and a 512MB GART aperture, the total GART space required becomes 1.25GB, exceeding the available GART aperture. Consequently, GART window allocation fails, amdgpu_ttm_next_clear_entity() returns NULL, and the above warning is triggered. Redefine AMDGPU_GTT_MAX_TRANSFER_SIZE in bytes instead of page units. Where a page count is required, convert it using PAGE_SHIFT. This preserves the existing 4MB transfer size across all PAGE_SIZE configurations while keeping GART window allocations within the available GART aperture. [1] https://lore.kernel.org/all/20260408100327.1372-3-pierre-eric.pelloux-prayer@amd.com/#t Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5435 Fixes: 897ee11ec020 ("drm/amdgpu: create multiple clear/move ttm entities") Signed-off-by: Donet Tom Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 10 ++++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 00b5317f77f8..025625e7e800 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -208,9 +208,10 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, void *cpu_addr; uint64_t flags; int r; + const u64 GTT_MAX_PAGES = (AMDGPU_GTT_MAX_TRANSFER_SIZE >> PAGE_SHIFT); BUG_ON(adev->mman.buffer_funcs->copy_max_bytes < - AMDGPU_GTT_MAX_TRANSFER_SIZE * 8); + GTT_MAX_PAGES * AMDGPU_GPU_PAGES_IN_CPU_PAGE * 8); if (WARN_ON(mem->mem_type == AMDGPU_PL_PREEMPT)) return -EINVAL; @@ -230,7 +231,7 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, offset = mm_cur->start & ~PAGE_MASK; num_pages = PFN_UP(*size + offset); - num_pages = min_t(uint32_t, num_pages, AMDGPU_GTT_MAX_TRANSFER_SIZE); + num_pages = min_t(uint32_t, num_pages, GTT_MAX_PAGES); *size = min(*size, (uint64_t)num_pages * PAGE_SIZE - offset); @@ -2033,6 +2034,7 @@ static int amdgpu_ttm_buffer_entity_init(struct amdgpu_gtt_mgr *mgr, u32 num_gart_windows) { int i, r, num_pages; + const u64 GTT_MAX_PAGES = (AMDGPU_GTT_MAX_TRANSFER_SIZE >> PAGE_SHIFT); r = drm_sched_entity_init(&entity->base, prio, scheds, num_schedulers, NULL); if (r) @@ -2045,7 +2047,7 @@ static int amdgpu_ttm_buffer_entity_init(struct amdgpu_gtt_mgr *mgr, if (num_gart_windows == 0) return 0; - num_pages = num_gart_windows * AMDGPU_GTT_MAX_TRANSFER_SIZE; + num_pages = num_gart_windows * GTT_MAX_PAGES; r = amdgpu_gtt_mgr_alloc_entries(mgr, &entity->gart_node, num_pages, DRM_MM_INSERT_BEST); if (r) { @@ -2056,7 +2058,7 @@ static int amdgpu_ttm_buffer_entity_init(struct amdgpu_gtt_mgr *mgr, for (i = 0; i < num_gart_windows; i++) { entity->gart_window_offs[i] = amdgpu_gtt_node_to_byte_offset(&entity->gart_node) + - i * AMDGPU_GTT_MAX_TRANSFER_SIZE * PAGE_SIZE; + i * GTT_MAX_PAGES * PAGE_SIZE; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h index 00acec7226f5..ff9e2e346609 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h @@ -39,7 +39,7 @@ #define AMDGPU_PL_MMIO_REMAP (TTM_PL_PRIV + 5) #define __AMDGPU_PL_NUM (TTM_PL_PRIV + 6) -#define AMDGPU_GTT_MAX_TRANSFER_SIZE 1024 +#define AMDGPU_GTT_MAX_TRANSFER_SIZE (1ULL << 22) extern const struct attribute_group amdgpu_vram_mgr_attr_group; extern const struct attribute_group amdgpu_gtt_mgr_attr_group; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index 226e76ae0be7..7cd236c1ff75 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -128,7 +128,7 @@ svm_migrate_copy_memory_gart(struct amdgpu_device *adev, dma_addr_t *sys, enum MIGRATION_COPY_DIR direction, struct dma_fence **mfence) { - const u64 GTT_MAX_PAGES = AMDGPU_GTT_MAX_TRANSFER_SIZE; + const u64 GTT_MAX_PAGES = (AMDGPU_GTT_MAX_TRANSFER_SIZE >> PAGE_SHIFT); struct amdgpu_ring *ring; struct amdgpu_ttm_buffer_entity *entity; u64 gart_s, gart_d; -- cgit v1.2.3 From 98cad4bd1443975d972f4c7f705980da03722a22 Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Mon, 29 Jun 2026 15:58:50 -0500 Subject: drm/amd/display: Fix dangling pointer in plane reset function amdgpu_dm_plane_drm_plane_reset() frees the old state before allocating a new one. If kzalloc() fails, the function returns without updating the state pointer, leaving a dangling pointer to already freed memory. Fix this by allocating the new state first. On allocation failure, the old state remains untouched and the function safely returns. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 5d945cbcd4b1 ("drm/amd/display: Create a file dedicated to planes") Signed-off-by: Evgenii Burenchev Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260629090435.9729-3-evg28bur@yandex.ru [adjust for movement around current amd-staging-drm-next] Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 35813a39ebcb..1b564cfe2120 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 @@ -1517,17 +1517,15 @@ static const struct drm_plane_helper_funcs dm_primary_plane_helper_funcs = { static void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) { - struct dm_plane_state *amdgpu_state = NULL; - - if (plane->state) - plane->funcs->atomic_destroy_state(plane, plane->state); + struct dm_plane_state *amdgpu_state; amdgpu_state = kzalloc_obj(*amdgpu_state); - WARN_ON(amdgpu_state == NULL); - if (!amdgpu_state) return; + if (plane->state) + plane->funcs->atomic_destroy_state(plane, plane->state); + __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base); amdgpu_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; amdgpu_state->hdr_mult = AMDGPU_HDR_MULT_DEFAULT; -- cgit v1.2.3 From af1ae7d0beafb5459cfde633049a485daf76fb84 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 10:07:22 +0800 Subject: drm/amdgpu: retire legacy ACA support retire legacy ACA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/Makefile | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 4 - drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c | 450 ------------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h | 229 ---------------- drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c | 5 +- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 170 +----------- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h | 14 +- 7 files changed, 13 insertions(+), 861 deletions(-) delete mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c delete mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile index ba80542ead9d..5100e35027ec 100644 --- a/drivers/gpu/drm/amd/amdgpu/Makefile +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -70,7 +70,7 @@ amdgpu-y += amdgpu_device.o amdgpu_reg_access.o amdgpu_doorbell_mgr.o amdgpu_kms amdgpu_umc.o smu_v11_0_i2c.o amdgpu_fru_eeprom.o amdgpu_rap.o \ amdgpu_fw_attestation.o amdgpu_securedisplay.o \ amdgpu_eeprom.o amdgpu_mca.o amdgpu_psp_ta.o amdgpu_lsdma.o amdgpu_lockdep.o \ - amdgpu_ring_mux.o amdgpu_xcp.o amdgpu_seq64.o amdgpu_aca.o amdgpu_dev_coredump.o \ + amdgpu_ring_mux.o amdgpu_xcp.o amdgpu_seq64.o amdgpu_dev_coredump.o \ amdgpu_cper.o amdgpu_userq_fence.o amdgpu_eviction_fence.o amdgpu_ip.o amdgpu-$(CONFIG_PROC_FS) += amdgpu_fdinfo.o diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 4c3e933ff6d5..13d6f31344c4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -104,7 +104,6 @@ #include "amdgpu_smuio.h" #include "amdgpu_fdinfo.h" #include "amdgpu_mca.h" -#include "amdgpu_aca.h" #include "amdgpu_ras.h" #include "amdgpu_lockdep.h" #include "amdgpu_cper.h" @@ -990,9 +989,6 @@ struct amdgpu_device { /* MCA */ struct amdgpu_mca mca; - /* ACA */ - struct amdgpu_aca aca; - /* CPER */ struct amdgpu_cper cper; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c deleted file mode 100644 index 4c78de1bdb79..000000000000 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright 2023 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. - * - */ - -#include -#include "amdgpu.h" -#include "amdgpu_aca.h" -#include "amdgpu_ras.h" - -static struct aca_bank_error *new_bank_error(struct aca_error *aerr, struct aca_bank_info *info) -{ - struct aca_bank_error *bank_error; - - bank_error = kvzalloc_obj(*bank_error); - if (!bank_error) - return NULL; - - INIT_LIST_HEAD(&bank_error->node); - memcpy(&bank_error->info, info, sizeof(*info)); - - mutex_lock(&aerr->lock); - list_add_tail(&bank_error->node, &aerr->list); - aerr->nr_errors++; - mutex_unlock(&aerr->lock); - - return bank_error; -} - -static struct aca_bank_error *find_bank_error(struct aca_error *aerr, struct aca_bank_info *info) -{ - struct aca_bank_error *bank_error = NULL; - struct aca_bank_info *tmp_info; - bool found = false; - - mutex_lock(&aerr->lock); - list_for_each_entry(bank_error, &aerr->list, node) { - tmp_info = &bank_error->info; - if (tmp_info->socket_id == info->socket_id && - tmp_info->die_id == info->die_id) { - found = true; - goto out_unlock; - } - } - -out_unlock: - mutex_unlock(&aerr->lock); - - return found ? bank_error : NULL; -} - -static void aca_bank_error_remove(struct aca_error *aerr, struct aca_bank_error *bank_error) -{ - if (!aerr || !bank_error) - return; - - list_del(&bank_error->node); - aerr->nr_errors--; - - kvfree(bank_error); -} - -static struct aca_bank_error *get_bank_error(struct aca_error *aerr, struct aca_bank_info *info) -{ - struct aca_bank_error *bank_error; - - if (!aerr || !info) - return NULL; - - bank_error = find_bank_error(aerr, info); - if (bank_error) - return bank_error; - - return new_bank_error(aerr, info); -} - -int aca_error_cache_log_bank_error(struct aca_handle *handle, struct aca_bank_info *info, - enum aca_error_type type, u64 count) -{ - struct aca_error_cache *error_cache = &handle->error_cache; - struct aca_bank_error *bank_error; - struct aca_error *aerr; - - if (!handle || !info || type >= ACA_ERROR_TYPE_COUNT) - return -EINVAL; - - if (!count) - return 0; - - aerr = &error_cache->errors[type]; - bank_error = get_bank_error(aerr, info); - if (!bank_error) - return -ENOMEM; - - bank_error->count += count; - - return 0; -} - -static void aca_error_init(struct aca_error *aerr, enum aca_error_type type) -{ - mutex_init(&aerr->lock); - INIT_LIST_HEAD(&aerr->list); - aerr->type = type; - aerr->nr_errors = 0; -} - -static void aca_init_error_cache(struct aca_handle *handle) -{ - struct aca_error_cache *error_cache = &handle->error_cache; - int type; - - for (type = ACA_ERROR_TYPE_UE; type < ACA_ERROR_TYPE_COUNT; type++) - aca_error_init(&error_cache->errors[type], type); -} - -static void aca_error_fini(struct aca_error *aerr) -{ - struct aca_bank_error *bank_error, *tmp; - - mutex_lock(&aerr->lock); - if (list_empty(&aerr->list)) - goto out_unlock; - - list_for_each_entry_safe(bank_error, tmp, &aerr->list, node) - aca_bank_error_remove(aerr, bank_error); - -out_unlock: - mutex_unlock(&aerr->lock); - mutex_destroy(&aerr->lock); -} - -static void aca_fini_error_cache(struct aca_handle *handle) -{ - struct aca_error_cache *error_cache = &handle->error_cache; - int type; - - for (type = ACA_ERROR_TYPE_UE; type < ACA_ERROR_TYPE_COUNT; type++) - aca_error_fini(&error_cache->errors[type]); -} - -static int add_aca_handle(struct amdgpu_device *adev, struct aca_handle_manager *mgr, struct aca_handle *handle, - const char *name, const struct aca_info *ras_info, void *data) -{ - memset(handle, 0, sizeof(*handle)); - - handle->adev = adev; - handle->mgr = mgr; - handle->name = name; - handle->hwip = ras_info->hwip; - handle->mask = ras_info->mask; - handle->bank_ops = ras_info->bank_ops; - handle->data = data; - aca_init_error_cache(handle); - - INIT_LIST_HEAD(&handle->node); - list_add_tail(&handle->node, &mgr->list); - mgr->nr_handles++; - - return 0; -} - -static ssize_t aca_sysfs_read(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct aca_handle *handle = container_of(attr, struct aca_handle, aca_attr); - - /* NOTE: the aca cache will be auto cleared once read, - * So the driver should unify the query entry point, forward request to ras query interface directly */ - return amdgpu_ras_aca_sysfs_read(dev, attr, handle, buf, handle->data); -} - -static int add_aca_sysfs(struct amdgpu_device *adev, struct aca_handle *handle) -{ - struct device_attribute *aca_attr = &handle->aca_attr; - - snprintf(handle->attr_name, sizeof(handle->attr_name) - 1, "aca_%s", handle->name); - aca_attr->show = aca_sysfs_read; - aca_attr->attr.name = handle->attr_name; - aca_attr->attr.mode = S_IRUGO; - sysfs_attr_init(&aca_attr->attr); - - return sysfs_add_file_to_group(&adev->dev->kobj, - &aca_attr->attr, - "ras"); -} - -int amdgpu_aca_add_handle(struct amdgpu_device *adev, struct aca_handle *handle, - const char *name, const struct aca_info *ras_info, void *data) -{ - struct amdgpu_aca *aca = &adev->aca; - int ret; - - if (!amdgpu_aca_is_enabled(adev)) - return 0; - - ret = add_aca_handle(adev, &aca->mgr, handle, name, ras_info, data); - if (ret) - return ret; - - return add_aca_sysfs(adev, handle); -} - -static void remove_aca_handle(struct aca_handle *handle) -{ - struct aca_handle_manager *mgr = handle->mgr; - - aca_fini_error_cache(handle); - list_del(&handle->node); - mgr->nr_handles--; -} - -static void remove_aca_sysfs(struct aca_handle *handle) -{ - struct amdgpu_device *adev = handle->adev; - struct device_attribute *aca_attr = &handle->aca_attr; - - if (adev->dev->kobj.sd) - sysfs_remove_file_from_group(&adev->dev->kobj, - &aca_attr->attr, - "ras"); -} - -void amdgpu_aca_remove_handle(struct aca_handle *handle) -{ - if (!handle || list_empty(&handle->node)) - return; - - remove_aca_sysfs(handle); - remove_aca_handle(handle); -} - -static int aca_manager_init(struct aca_handle_manager *mgr) -{ - INIT_LIST_HEAD(&mgr->list); - mgr->nr_handles = 0; - - return 0; -} - -static void aca_manager_fini(struct aca_handle_manager *mgr) -{ - struct aca_handle *handle, *tmp; - - if (list_empty(&mgr->list)) - return; - - list_for_each_entry_safe(handle, tmp, &mgr->list, node) - amdgpu_aca_remove_handle(handle); -} - -bool amdgpu_aca_is_enabled(struct amdgpu_device *adev) -{ - return (adev->aca.is_enabled || - adev->debug_enable_ras_aca); -} - -int amdgpu_aca_init(struct amdgpu_device *adev) -{ - struct amdgpu_aca *aca = &adev->aca; - int ret; - - atomic_set(&aca->ue_update_flag, 0); - - ret = aca_manager_init(&aca->mgr); - if (ret) - return ret; - - return 0; -} - -void amdgpu_aca_fini(struct amdgpu_device *adev) -{ - struct amdgpu_aca *aca = &adev->aca; - - aca_manager_fini(&aca->mgr); - - atomic_set(&aca->ue_update_flag, 0); -} - -int amdgpu_aca_reset(struct amdgpu_device *adev) -{ - struct amdgpu_aca *aca = &adev->aca; - - atomic_set(&aca->ue_update_flag, 0); - - return 0; -} - -void amdgpu_aca_set_smu_funcs(struct amdgpu_device *adev, const struct aca_smu_funcs *smu_funcs) -{ - struct amdgpu_aca *aca = &adev->aca; - - WARN_ON(aca->smu_funcs); - aca->smu_funcs = smu_funcs; -} - -int aca_bank_info_decode(struct aca_bank *bank, struct aca_bank_info *info) -{ - u64 ipid; - u32 instidhi, instidlo; - - if (!bank || !info) - return -EINVAL; - - ipid = bank->regs[ACA_REG_IDX_IPID]; - info->hwid = ACA_REG__IPID__HARDWAREID(ipid); - info->mcatype = ACA_REG__IPID__MCATYPE(ipid); - /* - * Unfied DieID Format: SAASS. A:AID, S:Socket. - * Unfied DieID[4:4] = InstanceId[0:0] - * Unfied DieID[0:3] = InstanceIdHi[0:3] - */ - instidhi = ACA_REG__IPID__INSTANCEIDHI(ipid); - instidlo = ACA_REG__IPID__INSTANCEIDLO(ipid); - info->die_id = ((instidhi >> 2) & 0x03); - info->socket_id = ((instidlo & 0x1) << 2) | (instidhi & 0x03); - - return 0; -} - -static int aca_bank_get_error_code(struct amdgpu_device *adev, struct aca_bank *bank) -{ - struct amdgpu_aca *aca = &adev->aca; - const struct aca_smu_funcs *smu_funcs = aca->smu_funcs; - - if (!smu_funcs || !smu_funcs->parse_error_code) - return -EOPNOTSUPP; - - return smu_funcs->parse_error_code(adev, bank); -} - -int aca_bank_check_error_codes(struct amdgpu_device *adev, struct aca_bank *bank, int *err_codes, int size) -{ - int i, error_code; - - if (!bank || !err_codes) - return -EINVAL; - - error_code = aca_bank_get_error_code(adev, bank); - if (error_code < 0) - return error_code; - - for (i = 0; i < size; i++) { - if (err_codes[i] == error_code) - return 0; - } - - return -EINVAL; -} - -int amdgpu_aca_smu_set_debug_mode(struct amdgpu_device *adev, bool en) -{ - struct amdgpu_aca *aca = &adev->aca; - const struct aca_smu_funcs *smu_funcs = aca->smu_funcs; - - if (!smu_funcs || !smu_funcs->set_debug_mode) - return -EOPNOTSUPP; - - return smu_funcs->set_debug_mode(adev, en); -} - -#if defined(CONFIG_DEBUG_FS) -static int amdgpu_aca_smu_debug_mode_set(void *data, u64 val) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)data; - int ret; - - ret = amdgpu_ras_set_aca_debug_mode(adev, val ? true : false); - if (ret) - return ret; - - dev_info(adev->dev, "amdgpu set smu aca debug mode %s success\n", val ? "on" : "off"); - - return 0; -} - -static int aca_dump_show(struct seq_file *m, enum aca_smu_type type) -{ - return 0; -} - -static int aca_dump_ce_show(struct seq_file *m, void *unused) -{ - return aca_dump_show(m, ACA_SMU_TYPE_CE); -} - -static int aca_dump_ce_open(struct inode *inode, struct file *file) -{ - return single_open(file, aca_dump_ce_show, inode->i_private); -} - -static const struct file_operations aca_ce_dump_debug_fops = { - .owner = THIS_MODULE, - .open = aca_dump_ce_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int aca_dump_ue_show(struct seq_file *m, void *unused) -{ - return aca_dump_show(m, ACA_SMU_TYPE_UE); -} - -static int aca_dump_ue_open(struct inode *inode, struct file *file) -{ - return single_open(file, aca_dump_ue_show, inode->i_private); -} - -static const struct file_operations aca_ue_dump_debug_fops = { - .owner = THIS_MODULE, - .open = aca_dump_ue_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -DEFINE_DEBUGFS_ATTRIBUTE(aca_debug_mode_fops, NULL, amdgpu_aca_smu_debug_mode_set, "%llu\n"); -#endif - -void amdgpu_aca_smu_debugfs_init(struct amdgpu_device *adev, struct dentry *root) -{ -#if defined(CONFIG_DEBUG_FS) - if (!root) - return; - - debugfs_create_file("aca_debug_mode", 0200, root, adev, &aca_debug_mode_fops); - debugfs_create_file("aca_ue_dump", 0400, root, adev, &aca_ue_dump_debug_fops); - debugfs_create_file("aca_ce_dump", 0400, root, adev, &aca_ce_dump_debug_fops); -#endif -} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h deleted file mode 100644 index 93a70a350f34..000000000000 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2023 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. - * - */ - -#ifndef __AMDGPU_ACA_H__ -#define __AMDGPU_ACA_H__ - -#include - -struct ras_err_data; -struct ras_query_context; - -#define ACA_MAX_REGS_COUNT (16) - -#define ACA_REG_FIELD(x, h, l) (((x) & GENMASK_ULL(h, l)) >> l) -#define ACA_REG__STATUS__VAL(x) ACA_REG_FIELD(x, 63, 63) -#define ACA_REG__STATUS__OVERFLOW(x) ACA_REG_FIELD(x, 62, 62) -#define ACA_REG__STATUS__UC(x) ACA_REG_FIELD(x, 61, 61) -#define ACA_REG__STATUS__EN(x) ACA_REG_FIELD(x, 60, 60) -#define ACA_REG__STATUS__MISCV(x) ACA_REG_FIELD(x, 59, 59) -#define ACA_REG__STATUS__ADDRV(x) ACA_REG_FIELD(x, 58, 58) -#define ACA_REG__STATUS__PCC(x) ACA_REG_FIELD(x, 57, 57) -#define ACA_REG__STATUS__ERRCOREIDVAL(x) ACA_REG_FIELD(x, 56, 56) -#define ACA_REG__STATUS__TCC(x) ACA_REG_FIELD(x, 55, 55) -#define ACA_REG__STATUS__SYNDV(x) ACA_REG_FIELD(x, 53, 53) -#define ACA_REG__STATUS__CECC(x) ACA_REG_FIELD(x, 46, 46) -#define ACA_REG__STATUS__UECC(x) ACA_REG_FIELD(x, 45, 45) -#define ACA_REG__STATUS__DEFERRED(x) ACA_REG_FIELD(x, 44, 44) -#define ACA_REG__STATUS__POISON(x) ACA_REG_FIELD(x, 43, 43) -#define ACA_REG__STATUS__SCRUB(x) ACA_REG_FIELD(x, 40, 40) -#define ACA_REG__STATUS__ERRCOREID(x) ACA_REG_FIELD(x, 37, 32) -#define ACA_REG__STATUS__ADDRLSB(x) ACA_REG_FIELD(x, 29, 24) -#define ACA_REG__STATUS__ERRORCODEEXT(x) ACA_REG_FIELD(x, 21, 16) -#define ACA_REG__STATUS__ERRORCODE(x) ACA_REG_FIELD(x, 15, 0) - -#define ACA_REG__IPID__MCATYPE(x) ACA_REG_FIELD(x, 63, 48) -#define ACA_REG__IPID__INSTANCEIDHI(x) ACA_REG_FIELD(x, 47, 44) -#define ACA_REG__IPID__HARDWAREID(x) ACA_REG_FIELD(x, 43, 32) -#define ACA_REG__IPID__INSTANCEIDLO(x) ACA_REG_FIELD(x, 31, 0) - -#define ACA_REG__MISC0__VALID(x) ACA_REG_FIELD(x, 63, 63) -#define ACA_REG__MISC0__OVRFLW(x) ACA_REG_FIELD(x, 48, 48) -#define ACA_REG__MISC0__ERRCNT(x) ACA_REG_FIELD(x, 43, 32) - -#define ACA_REG__SYND__ERRORINFORMATION(x) ACA_REG_FIELD(x, 17, 0) - -/* NOTE: The following codes refers to the smu header file */ -#define ACA_EXTERROR_CODE_CE 0x3a -#define ACA_EXTERROR_CODE_FAULT 0x3b - -#define ACA_ERROR_UE_MASK BIT_MASK(ACA_ERROR_TYPE_UE) -#define ACA_ERROR_CE_MASK BIT_MASK(ACA_ERROR_TYPE_CE) -#define ACA_ERROR_DEFERRED_MASK BIT_MASK(ACA_ERROR_TYPE_DEFERRED) - -#define mmSMNAID_AID0_MCA_SMU 0x03b30400 /* SMN AID AID0 */ -#define mmSMNAID_XCD0_MCA_SMU 0x36430400 /* SMN AID XCD0 */ -#define mmSMNAID_XCD1_MCA_SMU 0x38430400 /* SMN AID XCD1 */ -#define mmSMNXCD_XCD0_MCA_SMU 0x40430400 /* SMN XCD XCD0 */ - -#define ACA_BANK_ERR_IS_DEFFERED(bank) \ - (ACA_REG__STATUS__POISON((bank)->regs[ACA_REG_IDX_STATUS]) || \ - ACA_REG__STATUS__DEFERRED((bank)->regs[ACA_REG_IDX_STATUS])) - -enum aca_reg_idx { - ACA_REG_IDX_CTL = 0, - ACA_REG_IDX_STATUS = 1, - ACA_REG_IDX_ADDR = 2, - ACA_REG_IDX_MISC0 = 3, - ACA_REG_IDX_CONFIG = 4, - ACA_REG_IDX_IPID = 5, - ACA_REG_IDX_SYND = 6, - ACA_REG_IDX_DESTAT = 8, - ACA_REG_IDX_DEADDR = 9, - ACA_REG_IDX_CTL_MASK = 10, - ACA_REG_IDX_COUNT = 16, -}; - -enum aca_hwip_type { - ACA_HWIP_TYPE_UNKNOW = -1, - ACA_HWIP_TYPE_PSP = 0, - ACA_HWIP_TYPE_UMC, - ACA_HWIP_TYPE_SMU, - ACA_HWIP_TYPE_PCS_XGMI, - ACA_HWIP_TYPE_COUNT, -}; - -enum aca_error_type { - ACA_ERROR_TYPE_INVALID = -1, - ACA_ERROR_TYPE_UE = 0, - ACA_ERROR_TYPE_CE, - ACA_ERROR_TYPE_DEFERRED, - ACA_ERROR_TYPE_COUNT -}; - -enum aca_smu_type { - ACA_SMU_TYPE_INVALID = -1, - ACA_SMU_TYPE_UE = 0, - ACA_SMU_TYPE_CE, - ACA_SMU_TYPE_COUNT, -}; - -struct aca_hwip { - int hwid; - int mcatype; -}; - -struct aca_bank { - enum aca_error_type aca_err_type; - enum aca_smu_type smu_err_type; - u64 regs[ACA_MAX_REGS_COUNT]; -}; - -struct aca_bank_node { - struct aca_bank bank; - struct list_head node; -}; - -struct aca_banks { - int nr_banks; - struct list_head list; -}; - -struct aca_bank_info { - int die_id; - int socket_id; - int hwid; - int mcatype; -}; - -struct aca_bank_error { - struct list_head node; - struct aca_bank_info info; - u64 count; -}; - -struct aca_error { - struct list_head list; - struct mutex lock; - enum aca_error_type type; - int nr_errors; -}; - -struct aca_handle_manager { - struct list_head list; - int nr_handles; -}; - -struct aca_error_cache { - struct aca_error errors[ACA_ERROR_TYPE_COUNT]; -}; - -struct aca_handle { - struct list_head node; - enum aca_hwip_type hwip; - struct amdgpu_device *adev; - struct aca_handle_manager *mgr; - struct aca_error_cache error_cache; - const struct aca_bank_ops *bank_ops; - struct device_attribute aca_attr; - char attr_name[64]; - const char *name; - u32 mask; - void *data; -}; - -struct aca_bank_ops { - int (*aca_bank_parser)(struct aca_handle *handle, struct aca_bank *bank, enum aca_smu_type type, void *data); - bool (*aca_bank_is_valid)(struct aca_handle *handle, struct aca_bank *bank, enum aca_smu_type type, - void *data); -}; - -struct aca_smu_funcs { - int max_ue_bank_count; - int max_ce_bank_count; - int (*set_debug_mode)(struct amdgpu_device *adev, bool enable); - int (*get_valid_aca_count)(struct amdgpu_device *adev, enum aca_smu_type type, u32 *count); - int (*get_valid_aca_bank)(struct amdgpu_device *adev, enum aca_smu_type type, int idx, struct aca_bank *bank); - int (*parse_error_code)(struct amdgpu_device *adev, struct aca_bank *bank); -}; - -struct amdgpu_aca { - struct aca_handle_manager mgr; - const struct aca_smu_funcs *smu_funcs; - atomic_t ue_update_flag; - bool is_enabled; -}; - -struct aca_info { - enum aca_hwip_type hwip; - const struct aca_bank_ops *bank_ops; - u32 mask; -}; - -int amdgpu_aca_init(struct amdgpu_device *adev); -void amdgpu_aca_fini(struct amdgpu_device *adev); -int amdgpu_aca_reset(struct amdgpu_device *adev); -void amdgpu_aca_set_smu_funcs(struct amdgpu_device *adev, const struct aca_smu_funcs *smu_funcs); -bool amdgpu_aca_is_enabled(struct amdgpu_device *adev); - -int aca_bank_info_decode(struct aca_bank *bank, struct aca_bank_info *info); -int aca_bank_check_error_codes(struct amdgpu_device *adev, struct aca_bank *bank, int *err_codes, int size); - -int amdgpu_aca_add_handle(struct amdgpu_device *adev, struct aca_handle *handle, - const char *name, const struct aca_info *aca_info, void *data); -void amdgpu_aca_remove_handle(struct aca_handle *handle); -int amdgpu_aca_smu_set_debug_mode(struct amdgpu_device *adev, bool en); -void amdgpu_aca_smu_debugfs_init(struct amdgpu_device *adev, struct dentry *root); -int aca_error_cache_log_bank_error(struct aca_handle *handle, struct aca_bank_info *info, - enum aca_error_type type, u64 count); -#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c index 34a70e479f60..6fb129025761 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c @@ -481,8 +481,7 @@ int amdgpu_cper_init(struct amdgpu_device *adev) if (amdgpu_sriov_vf(adev) && !amdgpu_sriov_ras_cper_en(adev)) return 0; - else if (!amdgpu_sriov_vf(adev) && !amdgpu_uniras_enabled(adev) && - !amdgpu_aca_is_enabled(adev)) + else if (!amdgpu_sriov_vf(adev) && !amdgpu_uniras_enabled(adev)) return 0; r = amdgpu_cper_ring_init(adev); @@ -501,7 +500,7 @@ int amdgpu_cper_init(struct amdgpu_device *adev) int amdgpu_cper_fini(struct amdgpu_device *adev) { - if (!amdgpu_aca_is_enabled(adev) && !amdgpu_sriov_ras_cper_en(adev)) + if (amdgpu_sriov_vf(adev)) return 0; adev->cper.enabled = false; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 4ab6eccb5691..afa48b8986ff 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -1375,63 +1375,6 @@ static void amdgpu_ras_mgr_virt_error_data_statistics_update(struct ras_manager obj->err_data.de_count = err_data->de_count; } -static struct ras_manager *get_ras_manager(struct amdgpu_device *adev, enum amdgpu_ras_block blk) -{ - struct ras_common_if head; - - memset(&head, 0, sizeof(head)); - head.block = blk; - - return amdgpu_ras_find_obj(adev, &head); -} - -int amdgpu_ras_bind_aca(struct amdgpu_device *adev, enum amdgpu_ras_block blk, - const struct aca_info *aca_info, void *data) -{ - struct ras_manager *obj; - - /* in resume phase, no need to create aca fs node */ - if (adev->in_suspend || amdgpu_reset_in_recovery(adev)) - return 0; - - obj = get_ras_manager(adev, blk); - if (!obj) - return -EINVAL; - - return amdgpu_aca_add_handle(adev, &obj->aca_handle, ras_block_str(blk), aca_info, data); -} - -int amdgpu_ras_unbind_aca(struct amdgpu_device *adev, enum amdgpu_ras_block blk) -{ - struct ras_manager *obj; - - obj = get_ras_manager(adev, blk); - if (!obj) - return -EINVAL; - - amdgpu_aca_remove_handle(&obj->aca_handle); - - return 0; -} - -ssize_t amdgpu_ras_aca_sysfs_read(struct device *dev, struct device_attribute *attr, - struct aca_handle *handle, char *buf, void *data) -{ - struct ras_manager *obj = container_of(handle, struct ras_manager, aca_handle); - struct ras_query_if info = { - .head = obj->head, - }; - - if (!amdgpu_ras_get_error_query_ready(obj->adev)) - return sysfs_emit(buf, "Query currently inaccessible\n"); - - if (amdgpu_ras_query_error_status(obj->adev, &info)) - return -EINVAL; - - return sysfs_emit(buf, "%s: %lu\n%s: %lu\n%s: %lu\n", "ue", info.ue_count, - "ce", info.ce_count, "de", info.de_count); -} - static int amdgpu_ras_query_error_status_helper(struct amdgpu_device *adev, struct ras_query_if *info, struct ras_err_data *err_data, @@ -1591,7 +1534,6 @@ int amdgpu_ras_reset_error_count(struct amdgpu_device *adev, { struct amdgpu_ras_block_object *block_obj = amdgpu_ras_get_ras_block(adev, block, 0); const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - const struct aca_smu_funcs *smu_funcs = adev->aca.smu_funcs; if (!block_obj || !block_obj->hw_ops) { dev_dbg_once(adev->dev, "%s doesn't config RAS function\n", @@ -1600,7 +1542,7 @@ int amdgpu_ras_reset_error_count(struct amdgpu_device *adev, } if (!amdgpu_ras_is_supported(adev, block) || - !amdgpu_ras_get_aca_debug_mode(adev)) + !amdgpu_ras_get_mca_debug_mode(adev)) return -EOPNOTSUPP; if (amdgpu_sriov_vf(adev)) @@ -1608,8 +1550,7 @@ int amdgpu_ras_reset_error_count(struct amdgpu_device *adev, /* skip ras error reset in gpu reset */ if ((amdgpu_in_reset(adev) || amdgpu_ras_in_recovery(adev)) && - ((smu_funcs && smu_funcs->set_debug_mode) || - (mca_funcs && mca_funcs->mca_set_debug_mode))) + mca_funcs && mca_funcs->mca_set_debug_mode) return -EOPNOTSUPP; if (block_obj->hw_ops->reset_ras_error_count) @@ -2056,9 +1997,6 @@ int amdgpu_ras_sysfs_create(struct amdgpu_device *adev, { struct ras_manager *obj = amdgpu_ras_find_obj(adev, head); - if (amdgpu_aca_is_enabled(adev)) - return 0; - if (!obj || obj->attr_inuse) return -EINVAL; @@ -2096,9 +2034,6 @@ int amdgpu_ras_sysfs_remove(struct amdgpu_device *adev, { struct ras_manager *obj = amdgpu_ras_find_obj(adev, head); - if (amdgpu_aca_is_enabled(adev)) - return 0; - if (!obj || !obj->attr_inuse) return -EINVAL; @@ -2211,25 +2146,6 @@ static void amdgpu_ras_debugfs_create(struct amdgpu_device *adev, obj, &amdgpu_ras_debugfs_ops); } -static bool amdgpu_ras_aca_is_supported(struct amdgpu_device *adev) -{ - bool ret; - - switch (amdgpu_ip_version(adev, MP0_HWIP, 0)) { - case IP_VERSION(13, 0, 6): - case IP_VERSION(13, 0, 12): - case IP_VERSION(13, 0, 14): - case IP_VERSION(13, 0, 15): - ret = true; - break; - default: - ret = false; - break; - } - - return ret; -} - void amdgpu_ras_debugfs_create_all(struct amdgpu_device *adev) { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); @@ -2256,13 +2172,6 @@ void amdgpu_ras_debugfs_create_all(struct amdgpu_device *adev) amdgpu_ras_debugfs_create(adev, &fs_info, dir); } } - - if (amdgpu_ras_aca_is_supported(adev)) { - if (amdgpu_aca_is_enabled(adev)) - amdgpu_aca_smu_debugfs_init(adev, dir); - else - amdgpu_mca_smu_debugfs_init(adev, dir); - } } /* debugfs end */ @@ -3883,15 +3792,6 @@ init_ras_enabled_flag: adev->ras_enabled = amdgpu_ras_enable == 0 ? 0 : adev->ras_hw_enabled & amdgpu_ras_mask; - /* aca is disabled by default except for psp v13_0_6/v13_0_12/v13_0_14 */ - if (!amdgpu_sriov_vf(adev)) { - adev->aca.is_enabled = - (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6) || - amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || - amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || - amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 15)); - } - /* bad page feature is not applicable to specific app platform */ if (adev->gmc.is_app_apu && amdgpu_ip_version(adev, UMC_HWIP, 0) == IP_VERSION(12, 0, 0)) @@ -4112,15 +4012,6 @@ int amdgpu_ras_init(struct amdgpu_device *adev) goto release_con; } - if (amdgpu_ras_aca_is_supported(adev)) { - if (amdgpu_aca_is_enabled(adev)) - r = amdgpu_aca_init(adev); - else - r = amdgpu_mca_init(adev); - if (r) - goto release_con; - } - con->init_task_pid = task_pid_nr(current); get_task_comm(con->init_task_comm, current); @@ -4348,24 +4239,6 @@ int amdgpu_ras_late_init(struct amdgpu_device *adev) amdgpu_ras_event_mgr_init(adev); - if (amdgpu_ras_aca_is_supported(adev)) { - if (amdgpu_reset_in_recovery(adev)) { - if (amdgpu_aca_is_enabled(adev)) - r = amdgpu_aca_reset(adev); - else - r = amdgpu_mca_reset(adev); - if (r) - return r; - } - - if (!amdgpu_sriov_vf(adev)) { - if (amdgpu_aca_is_enabled(adev)) - amdgpu_ras_set_aca_debug_mode(adev, false); - else - amdgpu_ras_set_mca_debug_mode(adev, false); - } - } - /* Guest side doesn't need init ras feature */ if (amdgpu_sriov_vf(adev) && !amdgpu_sriov_ras_telemetry_en(adev)) return 0; @@ -4453,13 +4326,6 @@ int amdgpu_ras_fini(struct amdgpu_device *adev) amdgpu_ras_fs_fini(adev); amdgpu_ras_interrupt_remove_all(adev); - if (amdgpu_ras_aca_is_supported(adev)) { - if (amdgpu_aca_is_enabled(adev)) - amdgpu_aca_fini(adev); - else - amdgpu_mca_fini(adev); - } - WARN(AMDGPU_RAS_GET_FEATURES(con->features), "Feature mask is not cleared"); if (AMDGPU_RAS_GET_FEATURES(con->features)) @@ -4876,41 +4742,22 @@ int amdgpu_ras_set_mca_debug_mode(struct amdgpu_device *adev, bool enable) if (con) { ret = amdgpu_mca_smu_set_debug_mode(adev, enable); if (!ret) - con->is_aca_debug_mode = enable; - } - - return ret; -} - -int amdgpu_ras_set_aca_debug_mode(struct amdgpu_device *adev, bool enable) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - int ret = 0; - - if (con) { - if (amdgpu_aca_is_enabled(adev)) - ret = amdgpu_aca_smu_set_debug_mode(adev, enable); - else - ret = amdgpu_mca_smu_set_debug_mode(adev, enable); - if (!ret) - con->is_aca_debug_mode = enable; + con->is_mca_debug_mode = enable; } return ret; } -bool amdgpu_ras_get_aca_debug_mode(struct amdgpu_device *adev) +bool amdgpu_ras_get_mca_debug_mode(struct amdgpu_device *adev) { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - const struct aca_smu_funcs *smu_funcs = adev->aca.smu_funcs; const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; if (!con) return false; - if ((amdgpu_aca_is_enabled(adev) && smu_funcs && smu_funcs->set_debug_mode) || - (!amdgpu_aca_is_enabled(adev) && mca_funcs && mca_funcs->mca_set_debug_mode)) - return con->is_aca_debug_mode; + if (mca_funcs && mca_funcs->mca_set_debug_mode) + return con->is_mca_debug_mode; else return true; } @@ -4920,7 +4767,6 @@ bool amdgpu_ras_get_error_query_mode(struct amdgpu_device *adev, { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - const struct aca_smu_funcs *smu_funcs = adev->aca.smu_funcs; if (!con) { *error_query_mode = AMDGPU_RAS_INVALID_ERROR_QUERY; @@ -4929,9 +4775,9 @@ bool amdgpu_ras_get_error_query_mode(struct amdgpu_device *adev, if (amdgpu_sriov_vf(adev)) { *error_query_mode = AMDGPU_RAS_VIRT_ERROR_COUNT_QUERY; - } else if ((smu_funcs && smu_funcs->set_debug_mode) || (mca_funcs && mca_funcs->mca_set_debug_mode)) { + } else if (mca_funcs && mca_funcs->mca_set_debug_mode) { *error_query_mode = - (con->is_aca_debug_mode) ? AMDGPU_RAS_DIRECT_ERROR_QUERY : AMDGPU_RAS_FIRMWARE_ERROR_QUERY; + (con->is_mca_debug_mode) ? AMDGPU_RAS_DIRECT_ERROR_QUERY : AMDGPU_RAS_FIRMWARE_ERROR_QUERY; } else { *error_query_mode = AMDGPU_RAS_DIRECT_ERROR_QUERY; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h index f511af205af6..255ce167d1cd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h @@ -31,7 +31,6 @@ #include "ta_ras_if.h" #include "amdgpu_ras_eeprom.h" #include "amdgpu_smuio.h" -#include "amdgpu_aca.h" struct amdgpu_iv_entry; @@ -572,7 +571,7 @@ struct amdgpu_ras { /* Indicates smu whether need update bad channel info */ bool update_channel_flag; /* Record status of smu mca debug mode */ - bool is_aca_debug_mode; + bool is_mca_debug_mode; bool is_rma; /* Record special requirements of gpu reset caller */ @@ -683,8 +682,6 @@ struct ras_manager { struct ras_ih_data ih_data; struct ras_err_data err_data; - - struct aca_handle aca_handle; }; struct ras_badpage { @@ -945,8 +942,7 @@ struct amdgpu_ras* amdgpu_ras_get_context(struct amdgpu_device *adev); int amdgpu_ras_set_context(struct amdgpu_device *adev, struct amdgpu_ras *ras_con); int amdgpu_ras_set_mca_debug_mode(struct amdgpu_device *adev, bool enable); -int amdgpu_ras_set_aca_debug_mode(struct amdgpu_device *adev, bool enable); -bool amdgpu_ras_get_aca_debug_mode(struct amdgpu_device *adev); +bool amdgpu_ras_get_mca_debug_mode(struct amdgpu_device *adev); bool amdgpu_ras_get_error_query_mode(struct amdgpu_device *adev, unsigned int *mode); @@ -987,12 +983,6 @@ int amdgpu_ras_error_statistic_de_count(struct ras_err_data *err_data, struct amdgpu_smuio_mcm_config_info *mcm_info, u64 count); void amdgpu_ras_query_boot_status(struct amdgpu_device *adev, u32 num_instances); -int amdgpu_ras_bind_aca(struct amdgpu_device *adev, enum amdgpu_ras_block blk, - const struct aca_info *aca_info, void *data); -int amdgpu_ras_unbind_aca(struct amdgpu_device *adev, enum amdgpu_ras_block blk); - -ssize_t amdgpu_ras_aca_sysfs_read(struct device *dev, struct device_attribute *attr, - struct aca_handle *handle, char *buf, void *data); void amdgpu_ras_set_fed(struct amdgpu_device *adev, bool status); bool amdgpu_ras_get_fed_status(struct amdgpu_device *adev); -- cgit v1.2.3 From 4159a0b23147646cc3c701fa290c7939744833da Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 28 Jan 2026 17:48:14 +0800 Subject: drm/amdgpu: retire MCA support retire MCA support Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mca.c | 486 -------------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_mca.h | 107 ------- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 45 +-- 3 files changed, 3 insertions(+), 635 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.c index cc6d1a4e4c3a..9a7f7d2b2767 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.c @@ -27,16 +27,6 @@ #include "umc/umc_6_7_0_offset.h" #include "umc/umc_6_7_0_sh_mask.h" -static bool amdgpu_mca_is_deferred_error(struct amdgpu_device *adev, - uint64_t mc_status) -{ - if (adev->umc.ras->check_ecc_err_status) - return adev->umc.ras->check_ecc_err_status(adev, - AMDGPU_MCA_ERROR_TYPE_DE, &mc_status); - - return false; -} - void amdgpu_mca_query_correctable_error_count(struct amdgpu_device *adev, uint64_t mc_status_addr, unsigned long *error_count) @@ -155,479 +145,3 @@ int amdgpu_mca_mpio_ras_sw_init(struct amdgpu_device *adev) return 0; } - -static void amdgpu_mca_bank_set_init(struct mca_bank_set *mca_set) -{ - if (!mca_set) - return; - - memset(mca_set, 0, sizeof(*mca_set)); - INIT_LIST_HEAD(&mca_set->list); -} - -static int amdgpu_mca_bank_set_add_entry(struct mca_bank_set *mca_set, struct mca_bank_entry *entry) -{ - struct mca_bank_node *node; - - if (!entry) - return -EINVAL; - - node = kvzalloc_obj(*node); - if (!node) - return -ENOMEM; - - memcpy(&node->entry, entry, sizeof(*entry)); - - INIT_LIST_HEAD(&node->node); - list_add_tail(&node->node, &mca_set->list); - - mca_set->nr_entries++; - - return 0; -} - -static int amdgpu_mca_bank_set_merge(struct mca_bank_set *mca_set, struct mca_bank_set *new) -{ - struct mca_bank_node *node; - - list_for_each_entry(node, &new->list, node) - amdgpu_mca_bank_set_add_entry(mca_set, &node->entry); - - return 0; -} - -static void amdgpu_mca_bank_set_remove_node(struct mca_bank_set *mca_set, struct mca_bank_node *node) -{ - if (!node) - return; - - list_del(&node->node); - kvfree(node); - - mca_set->nr_entries--; -} - -static void amdgpu_mca_bank_set_release(struct mca_bank_set *mca_set) -{ - struct mca_bank_node *node, *tmp; - - if (list_empty(&mca_set->list)) - return; - - list_for_each_entry_safe(node, tmp, &mca_set->list, node) - amdgpu_mca_bank_set_remove_node(mca_set, node); -} - -void amdgpu_mca_smu_init_funcs(struct amdgpu_device *adev, const struct amdgpu_mca_smu_funcs *mca_funcs) -{ - struct amdgpu_mca *mca = &adev->mca; - - mca->mca_funcs = mca_funcs; -} - -int amdgpu_mca_init(struct amdgpu_device *adev) -{ - struct amdgpu_mca *mca = &adev->mca; - struct mca_bank_cache *mca_cache; - int i; - - atomic_set(&mca->ue_update_flag, 0); - - for (i = 0; i < ARRAY_SIZE(mca->mca_caches); i++) { - mca_cache = &mca->mca_caches[i]; - mutex_init(&mca_cache->lock); - amdgpu_mca_bank_set_init(&mca_cache->mca_set); - } - - return 0; -} - -void amdgpu_mca_fini(struct amdgpu_device *adev) -{ - struct amdgpu_mca *mca = &adev->mca; - struct mca_bank_cache *mca_cache; - int i; - - atomic_set(&mca->ue_update_flag, 0); - - for (i = 0; i < ARRAY_SIZE(mca->mca_caches); i++) { - mca_cache = &mca->mca_caches[i]; - amdgpu_mca_bank_set_release(&mca_cache->mca_set); - mutex_destroy(&mca_cache->lock); - } -} - -int amdgpu_mca_reset(struct amdgpu_device *adev) -{ - amdgpu_mca_fini(adev); - - return amdgpu_mca_init(adev); -} - -int amdgpu_mca_smu_set_debug_mode(struct amdgpu_device *adev, bool enable) -{ - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - - if (mca_funcs && mca_funcs->mca_set_debug_mode) - return mca_funcs->mca_set_debug_mode(adev, enable); - - return -EOPNOTSUPP; -} - -static void amdgpu_mca_smu_mca_bank_dump(struct amdgpu_device *adev, int idx, struct mca_bank_entry *entry, - struct ras_query_context *qctx) -{ - u64 event_id = qctx ? qctx->evid.event_id : RAS_EVENT_INVALID_ID; - - RAS_EVENT_LOG(adev, event_id, HW_ERR "Accelerator Check Architecture events logged\n"); - RAS_EVENT_LOG(adev, event_id, HW_ERR "aca entry[%02d].STATUS=0x%016llx\n", - idx, entry->regs[MCA_REG_IDX_STATUS]); - RAS_EVENT_LOG(adev, event_id, HW_ERR "aca entry[%02d].ADDR=0x%016llx\n", - idx, entry->regs[MCA_REG_IDX_ADDR]); - RAS_EVENT_LOG(adev, event_id, HW_ERR "aca entry[%02d].MISC0=0x%016llx\n", - idx, entry->regs[MCA_REG_IDX_MISC0]); - RAS_EVENT_LOG(adev, event_id, HW_ERR "aca entry[%02d].IPID=0x%016llx\n", - idx, entry->regs[MCA_REG_IDX_IPID]); - RAS_EVENT_LOG(adev, event_id, HW_ERR "aca entry[%02d].SYND=0x%016llx\n", - idx, entry->regs[MCA_REG_IDX_SYND]); -} - -static int amdgpu_mca_smu_get_valid_mca_count(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, uint32_t *count) -{ - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - - if (!count) - return -EINVAL; - - if (mca_funcs && mca_funcs->mca_get_valid_mca_count) - return mca_funcs->mca_get_valid_mca_count(adev, type, count); - - return -EOPNOTSUPP; -} - -static int amdgpu_mca_smu_get_mca_entry(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, - int idx, struct mca_bank_entry *entry) -{ - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - int count; - - if (!mca_funcs || !mca_funcs->mca_get_mca_entry) - return -EOPNOTSUPP; - - switch (type) { - case AMDGPU_MCA_ERROR_TYPE_UE: - count = mca_funcs->max_ue_count; - break; - case AMDGPU_MCA_ERROR_TYPE_CE: - count = mca_funcs->max_ce_count; - break; - default: - return -EINVAL; - } - - if (idx >= count) - return -EINVAL; - - return mca_funcs->mca_get_mca_entry(adev, type, idx, entry); -} - -static bool amdgpu_mca_bank_should_update(struct amdgpu_device *adev, enum amdgpu_mca_error_type type) -{ - struct amdgpu_mca *mca = &adev->mca; - bool ret = true; - - /* - * Because the UE Valid MCA count will only be cleared after reset, - * in order to avoid repeated counting of the error count, - * the aca bank is only updated once during the gpu recovery stage. - */ - if (type == AMDGPU_MCA_ERROR_TYPE_UE) { - if (amdgpu_ras_intr_triggered()) - ret = atomic_cmpxchg(&mca->ue_update_flag, 0, 1) == 0; - else - atomic_set(&mca->ue_update_flag, 0); - } - - return ret; -} - -static bool amdgpu_mca_bank_should_dump(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, - struct mca_bank_entry *entry) -{ - bool ret; - - switch (type) { - case AMDGPU_MCA_ERROR_TYPE_CE: - ret = amdgpu_mca_is_deferred_error(adev, entry->regs[MCA_REG_IDX_STATUS]); - break; - case AMDGPU_MCA_ERROR_TYPE_UE: - default: - ret = true; - break; - } - - return ret; -} - -static int amdgpu_mca_smu_get_mca_set(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, struct mca_bank_set *mca_set, - struct ras_query_context *qctx) -{ - struct mca_bank_entry entry; - uint32_t count = 0, i; - int ret; - - if (!mca_set) - return -EINVAL; - - if (!amdgpu_mca_bank_should_update(adev, type)) - return 0; - - ret = amdgpu_mca_smu_get_valid_mca_count(adev, type, &count); - if (ret) - return ret; - - for (i = 0; i < count; i++) { - memset(&entry, 0, sizeof(entry)); - ret = amdgpu_mca_smu_get_mca_entry(adev, type, i, &entry); - if (ret) - return ret; - - amdgpu_mca_bank_set_add_entry(mca_set, &entry); - - if (amdgpu_mca_bank_should_dump(adev, type, &entry)) - amdgpu_mca_smu_mca_bank_dump(adev, i, &entry, qctx); - } - - return 0; -} - -static int amdgpu_mca_smu_parse_mca_error_count(struct amdgpu_device *adev, enum amdgpu_ras_block blk, - enum amdgpu_mca_error_type type, struct mca_bank_entry *entry, uint32_t *count) -{ - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - - if (!count || !entry) - return -EINVAL; - - if (!mca_funcs || !mca_funcs->mca_parse_mca_error_count) - return -EOPNOTSUPP; - - return mca_funcs->mca_parse_mca_error_count(adev, blk, type, entry, count); -} - -static int amdgpu_mca_dispatch_mca_set(struct amdgpu_device *adev, enum amdgpu_ras_block blk, enum amdgpu_mca_error_type type, - struct mca_bank_set *mca_set, struct ras_err_data *err_data) -{ - struct amdgpu_smuio_mcm_config_info mcm_info; - struct mca_bank_node *node, *tmp; - struct mca_bank_entry *entry; - uint32_t count; - int ret; - - if (!mca_set) - return -EINVAL; - - if (!mca_set->nr_entries) - return 0; - - list_for_each_entry_safe(node, tmp, &mca_set->list, node) { - entry = &node->entry; - - count = 0; - ret = amdgpu_mca_smu_parse_mca_error_count(adev, blk, type, entry, &count); - if (ret && ret != -EOPNOTSUPP) - return ret; - - if (!count) - continue; - - memset(&mcm_info, 0, sizeof(mcm_info)); - - mcm_info.socket_id = entry->info.socket_id; - mcm_info.die_id = entry->info.aid; - - if (type == AMDGPU_MCA_ERROR_TYPE_UE) { - amdgpu_ras_error_statistic_ue_count(err_data, - &mcm_info, (uint64_t)count); - } else { - if (amdgpu_mca_is_deferred_error(adev, entry->regs[MCA_REG_IDX_STATUS])) - amdgpu_ras_error_statistic_de_count(err_data, - &mcm_info, (uint64_t)count); - else - amdgpu_ras_error_statistic_ce_count(err_data, - &mcm_info, (uint64_t)count); - } - - amdgpu_mca_bank_set_remove_node(mca_set, node); - } - - return 0; -} - -static int amdgpu_mca_add_mca_set_to_cache(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, struct mca_bank_set *new) -{ - struct mca_bank_cache *mca_cache = &adev->mca.mca_caches[type]; - int ret; - - mutex_lock(&mca_cache->lock); - ret = amdgpu_mca_bank_set_merge(&mca_cache->mca_set, new); - mutex_unlock(&mca_cache->lock); - - return ret; -} - -int amdgpu_mca_smu_log_ras_error(struct amdgpu_device *adev, enum amdgpu_ras_block blk, enum amdgpu_mca_error_type type, - struct ras_err_data *err_data, struct ras_query_context *qctx) -{ - struct mca_bank_set mca_set; - struct mca_bank_cache *mca_cache = &adev->mca.mca_caches[type]; - int ret; - - amdgpu_mca_bank_set_init(&mca_set); - - ret = amdgpu_mca_smu_get_mca_set(adev, type, &mca_set, qctx); - if (ret) - goto out_mca_release; - - ret = amdgpu_mca_dispatch_mca_set(adev, blk, type, &mca_set, err_data); - if (ret) - goto out_mca_release; - - /* add remain mca bank to mca cache */ - if (mca_set.nr_entries) { - ret = amdgpu_mca_add_mca_set_to_cache(adev, type, &mca_set); - if (ret) - goto out_mca_release; - } - - /* dispatch mca set again if mca cache has valid data */ - mutex_lock(&mca_cache->lock); - if (mca_cache->mca_set.nr_entries) - ret = amdgpu_mca_dispatch_mca_set(adev, blk, type, &mca_cache->mca_set, err_data); - mutex_unlock(&mca_cache->lock); - -out_mca_release: - amdgpu_mca_bank_set_release(&mca_set); - - return ret; -} - -#if defined(CONFIG_DEBUG_FS) -static int amdgpu_mca_smu_debug_mode_set(void *data, u64 val) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)data; - int ret; - - ret = amdgpu_ras_set_mca_debug_mode(adev, val ? true : false); - if (ret) - return ret; - - dev_info(adev->dev, "amdgpu set smu mca debug mode %s success\n", val ? "on" : "off"); - - return 0; -} - -static void mca_dump_entry(struct seq_file *m, struct mca_bank_entry *entry) -{ - int i, idx = entry->idx; - int reg_idx_array[] = { - MCA_REG_IDX_STATUS, - MCA_REG_IDX_ADDR, - MCA_REG_IDX_MISC0, - MCA_REG_IDX_IPID, - MCA_REG_IDX_SYND, - }; - - seq_printf(m, "mca entry[%d].type: %s\n", idx, entry->type == AMDGPU_MCA_ERROR_TYPE_UE ? "UE" : "CE"); - seq_printf(m, "mca entry[%d].ip: %d\n", idx, entry->ip); - seq_printf(m, "mca entry[%d].info: socketid:%d aid:%d hwid:0x%03x mcatype:0x%04x\n", - idx, entry->info.socket_id, entry->info.aid, entry->info.hwid, entry->info.mcatype); - - for (i = 0; i < ARRAY_SIZE(reg_idx_array); i++) - seq_printf(m, "mca entry[%d].regs[%d]: 0x%016llx\n", idx, reg_idx_array[i], entry->regs[reg_idx_array[i]]); -} - -static int mca_dump_show(struct seq_file *m, enum amdgpu_mca_error_type type) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)m->private; - struct mca_bank_node *node; - struct mca_bank_set mca_set; - struct ras_query_context qctx; - int ret; - - amdgpu_mca_bank_set_init(&mca_set); - - qctx.evid.event_id = RAS_EVENT_INVALID_ID; - ret = amdgpu_mca_smu_get_mca_set(adev, type, &mca_set, &qctx); - if (ret) - goto err_free_mca_set; - - seq_printf(m, "amdgpu smu %s valid mca count: %d\n", - type == AMDGPU_MCA_ERROR_TYPE_UE ? "UE" : "CE", mca_set.nr_entries); - - if (!mca_set.nr_entries) - goto err_free_mca_set; - - list_for_each_entry(node, &mca_set.list, node) - mca_dump_entry(m, &node->entry); - - /* add mca bank to mca bank cache */ - ret = amdgpu_mca_add_mca_set_to_cache(adev, type, &mca_set); - -err_free_mca_set: - amdgpu_mca_bank_set_release(&mca_set); - - return ret; -} - -static int mca_dump_ce_show(struct seq_file *m, void *unused) -{ - return mca_dump_show(m, AMDGPU_MCA_ERROR_TYPE_CE); -} - -static int mca_dump_ce_open(struct inode *inode, struct file *file) -{ - return single_open(file, mca_dump_ce_show, inode->i_private); -} - -static const struct file_operations mca_ce_dump_debug_fops = { - .owner = THIS_MODULE, - .open = mca_dump_ce_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int mca_dump_ue_show(struct seq_file *m, void *unused) -{ - return mca_dump_show(m, AMDGPU_MCA_ERROR_TYPE_UE); -} - -static int mca_dump_ue_open(struct inode *inode, struct file *file) -{ - return single_open(file, mca_dump_ue_show, inode->i_private); -} - -static const struct file_operations mca_ue_dump_debug_fops = { - .owner = THIS_MODULE, - .open = mca_dump_ue_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -DEFINE_DEBUGFS_ATTRIBUTE(mca_debug_mode_fops, NULL, amdgpu_mca_smu_debug_mode_set, "%llu\n"); -#endif - -void amdgpu_mca_smu_debugfs_init(struct amdgpu_device *adev, struct dentry *root) -{ -#if defined(CONFIG_DEBUG_FS) - if (!root) - return; - - debugfs_create_file("mca_debug_mode", 0200, root, adev, &mca_debug_mode_fops); - debugfs_create_file("mca_ue_dump", 0400, root, adev, &mca_ue_dump_debug_fops); - debugfs_create_file("mca_ce_dump", 0400, root, adev, &mca_ce_dump_debug_fops); -#endif -} - diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.h index e80323ff90c1..6d12f8a516d5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mca.h @@ -23,45 +23,6 @@ #include "amdgpu_ras.h" -#define MCA_MAX_REGS_COUNT (16) - -#define MCA_REG_FIELD(x, h, l) (((x) & GENMASK_ULL(h, l)) >> l) -#define MCA_REG__STATUS__VAL(x) MCA_REG_FIELD(x, 63, 63) -#define MCA_REG__STATUS__OVERFLOW(x) MCA_REG_FIELD(x, 62, 62) -#define MCA_REG__STATUS__UC(x) MCA_REG_FIELD(x, 61, 61) -#define MCA_REG__STATUS__EN(x) MCA_REG_FIELD(x, 60, 60) -#define MCA_REG__STATUS__MISCV(x) MCA_REG_FIELD(x, 59, 59) -#define MCA_REG__STATUS__ADDRV(x) MCA_REG_FIELD(x, 58, 58) -#define MCA_REG__STATUS__PCC(x) MCA_REG_FIELD(x, 57, 57) -#define MCA_REG__STATUS__ERRCOREIDVAL(x) MCA_REG_FIELD(x, 56, 56) -#define MCA_REG__STATUS__TCC(x) MCA_REG_FIELD(x, 55, 55) -#define MCA_REG__STATUS__SYNDV(x) MCA_REG_FIELD(x, 53, 53) -#define MCA_REG__STATUS__CECC(x) MCA_REG_FIELD(x, 46, 46) -#define MCA_REG__STATUS__UECC(x) MCA_REG_FIELD(x, 45, 45) -#define MCA_REG__STATUS__DEFERRED(x) MCA_REG_FIELD(x, 44, 44) -#define MCA_REG__STATUS__POISON(x) MCA_REG_FIELD(x, 43, 43) -#define MCA_REG__STATUS__SCRUB(x) MCA_REG_FIELD(x, 40, 40) -#define MCA_REG__STATUS__ERRCOREID(x) MCA_REG_FIELD(x, 37, 32) -#define MCA_REG__STATUS__ADDRLSB(x) MCA_REG_FIELD(x, 29, 24) -#define MCA_REG__STATUS__ERRORCODEEXT(x) MCA_REG_FIELD(x, 21, 16) -#define MCA_REG__STATUS__ERRORCODE(x) MCA_REG_FIELD(x, 15, 0) - -#define MCA_REG__MISC0__ERRCNT(x) MCA_REG_FIELD(x, 43, 32) - -#define MCA_REG__SYND__ERRORINFORMATION(x) MCA_REG_FIELD(x, 17, 0) - -enum amdgpu_mca_ip { - AMDGPU_MCA_IP_UNKNOW = -1, - AMDGPU_MCA_IP_PSP = 0, - AMDGPU_MCA_IP_SDMA, - AMDGPU_MCA_IP_GC, - AMDGPU_MCA_IP_SMU, - AMDGPU_MCA_IP_MP5, - AMDGPU_MCA_IP_UMC, - AMDGPU_MCA_IP_PCS_XGMI, - AMDGPU_MCA_IP_COUNT, -}; - enum amdgpu_mca_error_type { AMDGPU_MCA_ERROR_TYPE_UE = 0, AMDGPU_MCA_ERROR_TYPE_CE, @@ -77,77 +38,20 @@ struct amdgpu_mca_ras { struct amdgpu_mca_ras_block *ras; }; -struct mca_bank_set { - int nr_entries; - struct list_head list; -}; - -struct mca_bank_cache { - struct mca_bank_set mca_set; - struct mutex lock; -}; - struct amdgpu_mca { struct amdgpu_mca_ras mp0; struct amdgpu_mca_ras mp1; struct amdgpu_mca_ras mpio; - const struct amdgpu_mca_smu_funcs *mca_funcs; - struct mca_bank_cache mca_caches[AMDGPU_MCA_ERROR_TYPE_DE]; - atomic_t ue_update_flag; -}; - -enum mca_reg_idx { - MCA_REG_IDX_STATUS = 1, - MCA_REG_IDX_ADDR = 2, - MCA_REG_IDX_MISC0 = 3, - MCA_REG_IDX_IPID = 5, - MCA_REG_IDX_SYND = 6, - MCA_REG_IDX_COUNT = 16, -}; - -struct mca_bank_info { - int socket_id; - int aid; - int hwid; - int mcatype; -}; - -struct mca_bank_entry { - int idx; - enum amdgpu_mca_error_type type; - enum amdgpu_mca_ip ip; - struct mca_bank_info info; - uint64_t regs[MCA_MAX_REGS_COUNT]; -}; - -struct mca_bank_node { - struct mca_bank_entry entry; - struct list_head node; -}; - -struct amdgpu_mca_smu_funcs { - int max_ue_count; - int max_ce_count; - int (*mca_set_debug_mode)(struct amdgpu_device *adev, bool enable); - int (*mca_parse_mca_error_count)(struct amdgpu_device *adev, enum amdgpu_ras_block blk, enum amdgpu_mca_error_type type, - struct mca_bank_entry *entry, uint32_t *count); - int (*mca_get_valid_mca_count)(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, - uint32_t *count); - int (*mca_get_mca_entry)(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, - int idx, struct mca_bank_entry *entry); }; void amdgpu_mca_query_correctable_error_count(struct amdgpu_device *adev, uint64_t mc_status_addr, unsigned long *error_count); - void amdgpu_mca_query_uncorrectable_error_count(struct amdgpu_device *adev, uint64_t mc_status_addr, unsigned long *error_count); - void amdgpu_mca_reset_error_count(struct amdgpu_device *adev, uint64_t mc_status_addr); - void amdgpu_mca_query_ras_error_count(struct amdgpu_device *adev, uint64_t mc_status_addr, void *ras_error_status); @@ -155,15 +59,4 @@ int amdgpu_mca_mp0_ras_sw_init(struct amdgpu_device *adev); int amdgpu_mca_mp1_ras_sw_init(struct amdgpu_device *adev); int amdgpu_mca_mpio_ras_sw_init(struct amdgpu_device *adev); -void amdgpu_mca_smu_init_funcs(struct amdgpu_device *adev, const struct amdgpu_mca_smu_funcs *mca_funcs); -int amdgpu_mca_init(struct amdgpu_device *adev); -void amdgpu_mca_fini(struct amdgpu_device *adev); -int amdgpu_mca_reset(struct amdgpu_device *adev); -int amdgpu_mca_smu_set_debug_mode(struct amdgpu_device *adev, bool enable); -int amdgpu_mca_smu_get_mca_set_error_count(struct amdgpu_device *adev, enum amdgpu_ras_block blk, - enum amdgpu_mca_error_type type, uint32_t *total); -void amdgpu_mca_smu_debugfs_init(struct amdgpu_device *adev, struct dentry *root); -int amdgpu_mca_smu_log_ras_error(struct amdgpu_device *adev, enum amdgpu_ras_block blk, enum amdgpu_mca_error_type type, - struct ras_err_data *err_data, struct ras_query_context *qctx); - #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index afa48b8986ff..3a55cc95422d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -1392,7 +1392,7 @@ static int amdgpu_ras_query_error_status_helper(struct amdgpu_device *adev, if (error_query_mode == AMDGPU_RAS_VIRT_ERROR_COUNT_QUERY) { return amdgpu_virt_req_ras_err_count(adev, blk, err_data); - } else if (error_query_mode == AMDGPU_RAS_DIRECT_ERROR_QUERY) { + } else { if (info->head.block == AMDGPU_RAS_BLOCK__UMC) { amdgpu_ras_get_ecc_info(adev, err_data); } else { @@ -1413,10 +1413,6 @@ static int amdgpu_ras_query_error_status_helper(struct amdgpu_device *adev, block_obj->hw_ops->query_ras_error_status(adev); } } - } else { - /* FIXME: add code to check return value later */ - amdgpu_mca_smu_log_ras_error(adev, blk, AMDGPU_MCA_ERROR_TYPE_UE, err_data, qctx); - amdgpu_mca_smu_log_ras_error(adev, blk, AMDGPU_MCA_ERROR_TYPE_CE, err_data, qctx); } return 0; @@ -1533,7 +1529,6 @@ int amdgpu_ras_reset_error_count(struct amdgpu_device *adev, enum amdgpu_ras_block block) { struct amdgpu_ras_block_object *block_obj = amdgpu_ras_get_ras_block(adev, block, 0); - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; if (!block_obj || !block_obj->hw_ops) { dev_dbg_once(adev->dev, "%s doesn't config RAS function\n", @@ -1541,16 +1536,14 @@ int amdgpu_ras_reset_error_count(struct amdgpu_device *adev, return -EOPNOTSUPP; } - if (!amdgpu_ras_is_supported(adev, block) || - !amdgpu_ras_get_mca_debug_mode(adev)) + if (!amdgpu_ras_is_supported(adev, block)) return -EOPNOTSUPP; if (amdgpu_sriov_vf(adev)) return -EOPNOTSUPP; /* skip ras error reset in gpu reset */ - if ((amdgpu_in_reset(adev) || amdgpu_ras_in_recovery(adev)) && - mca_funcs && mca_funcs->mca_set_debug_mode) + if (amdgpu_in_reset(adev) || amdgpu_ras_in_recovery(adev)) return -EOPNOTSUPP; if (block_obj->hw_ops->reset_ras_error_count) @@ -4734,39 +4727,10 @@ int amdgpu_ras_reset_gpu(struct amdgpu_device *adev) return 0; } -int amdgpu_ras_set_mca_debug_mode(struct amdgpu_device *adev, bool enable) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - int ret = 0; - - if (con) { - ret = amdgpu_mca_smu_set_debug_mode(adev, enable); - if (!ret) - con->is_mca_debug_mode = enable; - } - - return ret; -} - -bool amdgpu_ras_get_mca_debug_mode(struct amdgpu_device *adev) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; - - if (!con) - return false; - - if (mca_funcs && mca_funcs->mca_set_debug_mode) - return con->is_mca_debug_mode; - else - return true; -} - bool amdgpu_ras_get_error_query_mode(struct amdgpu_device *adev, unsigned int *error_query_mode) { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - const struct amdgpu_mca_smu_funcs *mca_funcs = adev->mca.mca_funcs; if (!con) { *error_query_mode = AMDGPU_RAS_INVALID_ERROR_QUERY; @@ -4775,9 +4739,6 @@ bool amdgpu_ras_get_error_query_mode(struct amdgpu_device *adev, if (amdgpu_sriov_vf(adev)) { *error_query_mode = AMDGPU_RAS_VIRT_ERROR_COUNT_QUERY; - } else if (mca_funcs && mca_funcs->mca_set_debug_mode) { - *error_query_mode = - (con->is_mca_debug_mode) ? AMDGPU_RAS_DIRECT_ERROR_QUERY : AMDGPU_RAS_FIRMWARE_ERROR_QUERY; } else { *error_query_mode = AMDGPU_RAS_DIRECT_ERROR_QUERY; } -- cgit v1.2.3 From 2d222780579fad6c46532d147c795a55d4604bfd Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 29 Jan 2026 16:00:18 +0800 Subject: drm/amdgpu: retire RAS error count query/reset for gfx_v9_4_3 retire RAS error count query/reset for gfx_v9_4_3 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 4 +- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 885 +------------------------------- 2 files changed, 3 insertions(+), 886 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 3a55cc95422d..78c2d4394708 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -4102,9 +4102,9 @@ int amdgpu_ras_block_late_init(struct amdgpu_device *adev, goto cleanup; } - if (ras_obj->hw_ops && + if (amdgpu_uniras_enabled(adev) || (ras_obj->hw_ops && (ras_obj->hw_ops->query_ras_error_count || - ras_obj->hw_ops->query_ras_error_status)) { + ras_obj->hw_ops->query_ras_error_status))) { r = amdgpu_ras_sysfs_create(adev, ras_block); if (r) goto interrupt; 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 d67ac6f96481..b89cbc2df951 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -3726,872 +3726,6 @@ pipe_reset: return amdgpu_ring_reset_helper_end(ring, timedout_fence); } -enum amdgpu_gfx_cp_ras_mem_id { - AMDGPU_GFX_CP_MEM1 = 1, - AMDGPU_GFX_CP_MEM2, - AMDGPU_GFX_CP_MEM3, - AMDGPU_GFX_CP_MEM4, - AMDGPU_GFX_CP_MEM5, -}; - -enum amdgpu_gfx_gcea_ras_mem_id { - AMDGPU_GFX_GCEA_IOWR_CMDMEM = 4, - AMDGPU_GFX_GCEA_IORD_CMDMEM, - AMDGPU_GFX_GCEA_GMIWR_CMDMEM, - AMDGPU_GFX_GCEA_GMIRD_CMDMEM, - AMDGPU_GFX_GCEA_DRAMWR_CMDMEM, - AMDGPU_GFX_GCEA_DRAMRD_CMDMEM, - AMDGPU_GFX_GCEA_MAM_DMEM0, - AMDGPU_GFX_GCEA_MAM_DMEM1, - AMDGPU_GFX_GCEA_MAM_DMEM2, - AMDGPU_GFX_GCEA_MAM_DMEM3, - AMDGPU_GFX_GCEA_MAM_AMEM0, - AMDGPU_GFX_GCEA_MAM_AMEM1, - AMDGPU_GFX_GCEA_MAM_AMEM2, - AMDGPU_GFX_GCEA_MAM_AMEM3, - AMDGPU_GFX_GCEA_MAM_AFLUSH_BUFFER, - AMDGPU_GFX_GCEA_WRET_TAGMEM, - AMDGPU_GFX_GCEA_RRET_TAGMEM, - AMDGPU_GFX_GCEA_IOWR_DATAMEM, - AMDGPU_GFX_GCEA_GMIWR_DATAMEM, - AMDGPU_GFX_GCEA_DRAM_DATAMEM, -}; - -enum amdgpu_gfx_gc_cane_ras_mem_id { - AMDGPU_GFX_GC_CANE_MEM0 = 0, -}; - -enum amdgpu_gfx_gcutcl2_ras_mem_id { - AMDGPU_GFX_GCUTCL2_MEM2P512X95 = 160, -}; - -enum amdgpu_gfx_gds_ras_mem_id { - AMDGPU_GFX_GDS_MEM0 = 0, -}; - -enum amdgpu_gfx_lds_ras_mem_id { - AMDGPU_GFX_LDS_BANK0 = 0, - AMDGPU_GFX_LDS_BANK1, - AMDGPU_GFX_LDS_BANK2, - AMDGPU_GFX_LDS_BANK3, - AMDGPU_GFX_LDS_BANK4, - AMDGPU_GFX_LDS_BANK5, - AMDGPU_GFX_LDS_BANK6, - AMDGPU_GFX_LDS_BANK7, - AMDGPU_GFX_LDS_BANK8, - AMDGPU_GFX_LDS_BANK9, - AMDGPU_GFX_LDS_BANK10, - AMDGPU_GFX_LDS_BANK11, - AMDGPU_GFX_LDS_BANK12, - AMDGPU_GFX_LDS_BANK13, - AMDGPU_GFX_LDS_BANK14, - AMDGPU_GFX_LDS_BANK15, - AMDGPU_GFX_LDS_BANK16, - AMDGPU_GFX_LDS_BANK17, - AMDGPU_GFX_LDS_BANK18, - AMDGPU_GFX_LDS_BANK19, - AMDGPU_GFX_LDS_BANK20, - AMDGPU_GFX_LDS_BANK21, - AMDGPU_GFX_LDS_BANK22, - AMDGPU_GFX_LDS_BANK23, - AMDGPU_GFX_LDS_BANK24, - AMDGPU_GFX_LDS_BANK25, - AMDGPU_GFX_LDS_BANK26, - AMDGPU_GFX_LDS_BANK27, - AMDGPU_GFX_LDS_BANK28, - AMDGPU_GFX_LDS_BANK29, - AMDGPU_GFX_LDS_BANK30, - AMDGPU_GFX_LDS_BANK31, - AMDGPU_GFX_LDS_SP_BUFFER_A, - AMDGPU_GFX_LDS_SP_BUFFER_B, -}; - -enum amdgpu_gfx_rlc_ras_mem_id { - AMDGPU_GFX_RLC_GPMF32 = 1, - AMDGPU_GFX_RLC_RLCVF32, - AMDGPU_GFX_RLC_SCRATCH, - AMDGPU_GFX_RLC_SRM_ARAM, - AMDGPU_GFX_RLC_SRM_DRAM, - AMDGPU_GFX_RLC_TCTAG, - AMDGPU_GFX_RLC_SPM_SE, - AMDGPU_GFX_RLC_SPM_GRBMT, -}; - -enum amdgpu_gfx_sp_ras_mem_id { - AMDGPU_GFX_SP_SIMDID0 = 0, -}; - -enum amdgpu_gfx_spi_ras_mem_id { - AMDGPU_GFX_SPI_MEM0 = 0, - AMDGPU_GFX_SPI_MEM1, - AMDGPU_GFX_SPI_MEM2, - AMDGPU_GFX_SPI_MEM3, -}; - -enum amdgpu_gfx_sqc_ras_mem_id { - AMDGPU_GFX_SQC_INST_CACHE_A = 100, - AMDGPU_GFX_SQC_INST_CACHE_B = 101, - AMDGPU_GFX_SQC_INST_CACHE_TAG_A = 102, - AMDGPU_GFX_SQC_INST_CACHE_TAG_B = 103, - AMDGPU_GFX_SQC_INST_CACHE_MISS_FIFO_A = 104, - AMDGPU_GFX_SQC_INST_CACHE_MISS_FIFO_B = 105, - AMDGPU_GFX_SQC_INST_CACHE_GATCL1_MISS_FIFO_A = 106, - AMDGPU_GFX_SQC_INST_CACHE_GATCL1_MISS_FIFO_B = 107, - AMDGPU_GFX_SQC_DATA_CACHE_A = 200, - AMDGPU_GFX_SQC_DATA_CACHE_B = 201, - AMDGPU_GFX_SQC_DATA_CACHE_TAG_A = 202, - AMDGPU_GFX_SQC_DATA_CACHE_TAG_B = 203, - AMDGPU_GFX_SQC_DATA_CACHE_MISS_FIFO_A = 204, - AMDGPU_GFX_SQC_DATA_CACHE_MISS_FIFO_B = 205, - AMDGPU_GFX_SQC_DATA_CACHE_HIT_FIFO_A = 206, - AMDGPU_GFX_SQC_DATA_CACHE_HIT_FIFO_B = 207, - AMDGPU_GFX_SQC_DIRTY_BIT_A = 208, - AMDGPU_GFX_SQC_DIRTY_BIT_B = 209, - AMDGPU_GFX_SQC_WRITE_DATA_BUFFER_CU0 = 210, - AMDGPU_GFX_SQC_WRITE_DATA_BUFFER_CU1 = 211, - AMDGPU_GFX_SQC_UTCL1_MISS_LFIFO_DATA_CACHE_A = 212, - AMDGPU_GFX_SQC_UTCL1_MISS_LFIFO_DATA_CACHE_B = 213, - AMDGPU_GFX_SQC_UTCL1_MISS_LFIFO_INST_CACHE = 108, -}; - -enum amdgpu_gfx_sq_ras_mem_id { - AMDGPU_GFX_SQ_SGPR_MEM0 = 0, - AMDGPU_GFX_SQ_SGPR_MEM1, - AMDGPU_GFX_SQ_SGPR_MEM2, - AMDGPU_GFX_SQ_SGPR_MEM3, -}; - -enum amdgpu_gfx_ta_ras_mem_id { - AMDGPU_GFX_TA_FS_AFIFO_RAM_LO = 1, - AMDGPU_GFX_TA_FS_AFIFO_RAM_HI, - AMDGPU_GFX_TA_FS_CFIFO_RAM, - AMDGPU_GFX_TA_FSX_LFIFO, - AMDGPU_GFX_TA_FS_DFIFO_RAM, -}; - -enum amdgpu_gfx_tcc_ras_mem_id { - AMDGPU_GFX_TCC_MEM1 = 1, -}; - -enum amdgpu_gfx_tca_ras_mem_id { - AMDGPU_GFX_TCA_MEM1 = 1, -}; - -enum amdgpu_gfx_tci_ras_mem_id { - AMDGPU_GFX_TCIW_MEM = 1, -}; - -enum amdgpu_gfx_tcp_ras_mem_id { - AMDGPU_GFX_TCP_LFIFO0 = 1, - AMDGPU_GFX_TCP_SET0BANK0_RAM, - AMDGPU_GFX_TCP_SET0BANK1_RAM, - AMDGPU_GFX_TCP_SET0BANK2_RAM, - AMDGPU_GFX_TCP_SET0BANK3_RAM, - AMDGPU_GFX_TCP_SET1BANK0_RAM, - AMDGPU_GFX_TCP_SET1BANK1_RAM, - AMDGPU_GFX_TCP_SET1BANK2_RAM, - AMDGPU_GFX_TCP_SET1BANK3_RAM, - AMDGPU_GFX_TCP_SET2BANK0_RAM, - AMDGPU_GFX_TCP_SET2BANK1_RAM, - AMDGPU_GFX_TCP_SET2BANK2_RAM, - AMDGPU_GFX_TCP_SET2BANK3_RAM, - AMDGPU_GFX_TCP_SET3BANK0_RAM, - AMDGPU_GFX_TCP_SET3BANK1_RAM, - AMDGPU_GFX_TCP_SET3BANK2_RAM, - AMDGPU_GFX_TCP_SET3BANK3_RAM, - AMDGPU_GFX_TCP_VM_FIFO, - AMDGPU_GFX_TCP_DB_TAGRAM0, - AMDGPU_GFX_TCP_DB_TAGRAM1, - AMDGPU_GFX_TCP_DB_TAGRAM2, - AMDGPU_GFX_TCP_DB_TAGRAM3, - AMDGPU_GFX_TCP_UTCL1_LFIFO_PROBE0, - AMDGPU_GFX_TCP_UTCL1_LFIFO_PROBE1, - AMDGPU_GFX_TCP_CMD_FIFO, -}; - -enum amdgpu_gfx_td_ras_mem_id { - AMDGPU_GFX_TD_UTD_CS_FIFO_MEM = 1, - AMDGPU_GFX_TD_UTD_SS_FIFO_LO_MEM, - AMDGPU_GFX_TD_UTD_SS_FIFO_HI_MEM, -}; - -enum amdgpu_gfx_tcx_ras_mem_id { - AMDGPU_GFX_TCX_FIFOD0 = 0, - AMDGPU_GFX_TCX_FIFOD1, - AMDGPU_GFX_TCX_FIFOD2, - AMDGPU_GFX_TCX_FIFOD3, - AMDGPU_GFX_TCX_FIFOD4, - AMDGPU_GFX_TCX_FIFOD5, - AMDGPU_GFX_TCX_FIFOD6, - AMDGPU_GFX_TCX_FIFOD7, - AMDGPU_GFX_TCX_FIFOB0, - AMDGPU_GFX_TCX_FIFOB1, - AMDGPU_GFX_TCX_FIFOB2, - AMDGPU_GFX_TCX_FIFOB3, - AMDGPU_GFX_TCX_FIFOB4, - AMDGPU_GFX_TCX_FIFOB5, - AMDGPU_GFX_TCX_FIFOB6, - AMDGPU_GFX_TCX_FIFOB7, - AMDGPU_GFX_TCX_FIFOA0, - AMDGPU_GFX_TCX_FIFOA1, - AMDGPU_GFX_TCX_FIFOA2, - AMDGPU_GFX_TCX_FIFOA3, - AMDGPU_GFX_TCX_FIFOA4, - AMDGPU_GFX_TCX_FIFOA5, - AMDGPU_GFX_TCX_FIFOA6, - AMDGPU_GFX_TCX_FIFOA7, - AMDGPU_GFX_TCX_CFIFO0, - AMDGPU_GFX_TCX_CFIFO1, - AMDGPU_GFX_TCX_CFIFO2, - AMDGPU_GFX_TCX_CFIFO3, - AMDGPU_GFX_TCX_CFIFO4, - AMDGPU_GFX_TCX_CFIFO5, - AMDGPU_GFX_TCX_CFIFO6, - AMDGPU_GFX_TCX_CFIFO7, - AMDGPU_GFX_TCX_FIFO_ACKB0, - AMDGPU_GFX_TCX_FIFO_ACKB1, - AMDGPU_GFX_TCX_FIFO_ACKB2, - AMDGPU_GFX_TCX_FIFO_ACKB3, - AMDGPU_GFX_TCX_FIFO_ACKB4, - AMDGPU_GFX_TCX_FIFO_ACKB5, - AMDGPU_GFX_TCX_FIFO_ACKB6, - AMDGPU_GFX_TCX_FIFO_ACKB7, - AMDGPU_GFX_TCX_FIFO_ACKD0, - AMDGPU_GFX_TCX_FIFO_ACKD1, - AMDGPU_GFX_TCX_FIFO_ACKD2, - AMDGPU_GFX_TCX_FIFO_ACKD3, - AMDGPU_GFX_TCX_FIFO_ACKD4, - AMDGPU_GFX_TCX_FIFO_ACKD5, - AMDGPU_GFX_TCX_FIFO_ACKD6, - AMDGPU_GFX_TCX_FIFO_ACKD7, - AMDGPU_GFX_TCX_DST_FIFOA0, - AMDGPU_GFX_TCX_DST_FIFOA1, - AMDGPU_GFX_TCX_DST_FIFOA2, - AMDGPU_GFX_TCX_DST_FIFOA3, - AMDGPU_GFX_TCX_DST_FIFOA4, - AMDGPU_GFX_TCX_DST_FIFOA5, - AMDGPU_GFX_TCX_DST_FIFOA6, - AMDGPU_GFX_TCX_DST_FIFOA7, - AMDGPU_GFX_TCX_DST_FIFOB0, - AMDGPU_GFX_TCX_DST_FIFOB1, - AMDGPU_GFX_TCX_DST_FIFOB2, - AMDGPU_GFX_TCX_DST_FIFOB3, - AMDGPU_GFX_TCX_DST_FIFOB4, - AMDGPU_GFX_TCX_DST_FIFOB5, - AMDGPU_GFX_TCX_DST_FIFOB6, - AMDGPU_GFX_TCX_DST_FIFOB7, - AMDGPU_GFX_TCX_DST_FIFOD0, - AMDGPU_GFX_TCX_DST_FIFOD1, - AMDGPU_GFX_TCX_DST_FIFOD2, - AMDGPU_GFX_TCX_DST_FIFOD3, - AMDGPU_GFX_TCX_DST_FIFOD4, - AMDGPU_GFX_TCX_DST_FIFOD5, - AMDGPU_GFX_TCX_DST_FIFOD6, - AMDGPU_GFX_TCX_DST_FIFOD7, - AMDGPU_GFX_TCX_DST_FIFO_ACKB0, - AMDGPU_GFX_TCX_DST_FIFO_ACKB1, - AMDGPU_GFX_TCX_DST_FIFO_ACKB2, - AMDGPU_GFX_TCX_DST_FIFO_ACKB3, - AMDGPU_GFX_TCX_DST_FIFO_ACKB4, - AMDGPU_GFX_TCX_DST_FIFO_ACKB5, - AMDGPU_GFX_TCX_DST_FIFO_ACKB6, - AMDGPU_GFX_TCX_DST_FIFO_ACKB7, - AMDGPU_GFX_TCX_DST_FIFO_ACKD0, - AMDGPU_GFX_TCX_DST_FIFO_ACKD1, - AMDGPU_GFX_TCX_DST_FIFO_ACKD2, - AMDGPU_GFX_TCX_DST_FIFO_ACKD3, - AMDGPU_GFX_TCX_DST_FIFO_ACKD4, - AMDGPU_GFX_TCX_DST_FIFO_ACKD5, - AMDGPU_GFX_TCX_DST_FIFO_ACKD6, - AMDGPU_GFX_TCX_DST_FIFO_ACKD7, -}; - -enum amdgpu_gfx_atc_l2_ras_mem_id { - AMDGPU_GFX_ATC_L2_MEM0 = 0, -}; - -enum amdgpu_gfx_utcl2_ras_mem_id { - AMDGPU_GFX_UTCL2_MEM0 = 0, -}; - -enum amdgpu_gfx_vml2_ras_mem_id { - AMDGPU_GFX_VML2_MEM0 = 0, -}; - -enum amdgpu_gfx_vml2_walker_ras_mem_id { - AMDGPU_GFX_VML2_WALKER_MEM0 = 0, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_cp_mem_list[] = { - {AMDGPU_GFX_CP_MEM1, "CP_MEM1"}, - {AMDGPU_GFX_CP_MEM2, "CP_MEM2"}, - {AMDGPU_GFX_CP_MEM3, "CP_MEM3"}, - {AMDGPU_GFX_CP_MEM4, "CP_MEM4"}, - {AMDGPU_GFX_CP_MEM5, "CP_MEM5"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_gcea_mem_list[] = { - {AMDGPU_GFX_GCEA_IOWR_CMDMEM, "GCEA_IOWR_CMDMEM"}, - {AMDGPU_GFX_GCEA_IORD_CMDMEM, "GCEA_IORD_CMDMEM"}, - {AMDGPU_GFX_GCEA_GMIWR_CMDMEM, "GCEA_GMIWR_CMDMEM"}, - {AMDGPU_GFX_GCEA_GMIRD_CMDMEM, "GCEA_GMIRD_CMDMEM"}, - {AMDGPU_GFX_GCEA_DRAMWR_CMDMEM, "GCEA_DRAMWR_CMDMEM"}, - {AMDGPU_GFX_GCEA_DRAMRD_CMDMEM, "GCEA_DRAMRD_CMDMEM"}, - {AMDGPU_GFX_GCEA_MAM_DMEM0, "GCEA_MAM_DMEM0"}, - {AMDGPU_GFX_GCEA_MAM_DMEM1, "GCEA_MAM_DMEM1"}, - {AMDGPU_GFX_GCEA_MAM_DMEM2, "GCEA_MAM_DMEM2"}, - {AMDGPU_GFX_GCEA_MAM_DMEM3, "GCEA_MAM_DMEM3"}, - {AMDGPU_GFX_GCEA_MAM_AMEM0, "GCEA_MAM_AMEM0"}, - {AMDGPU_GFX_GCEA_MAM_AMEM1, "GCEA_MAM_AMEM1"}, - {AMDGPU_GFX_GCEA_MAM_AMEM2, "GCEA_MAM_AMEM2"}, - {AMDGPU_GFX_GCEA_MAM_AMEM3, "GCEA_MAM_AMEM3"}, - {AMDGPU_GFX_GCEA_MAM_AFLUSH_BUFFER, "GCEA_MAM_AFLUSH_BUFFER"}, - {AMDGPU_GFX_GCEA_WRET_TAGMEM, "GCEA_WRET_TAGMEM"}, - {AMDGPU_GFX_GCEA_RRET_TAGMEM, "GCEA_RRET_TAGMEM"}, - {AMDGPU_GFX_GCEA_IOWR_DATAMEM, "GCEA_IOWR_DATAMEM"}, - {AMDGPU_GFX_GCEA_GMIWR_DATAMEM, "GCEA_GMIWR_DATAMEM"}, - {AMDGPU_GFX_GCEA_DRAM_DATAMEM, "GCEA_DRAM_DATAMEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_gc_cane_mem_list[] = { - {AMDGPU_GFX_GC_CANE_MEM0, "GC_CANE_MEM0"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_gcutcl2_mem_list[] = { - {AMDGPU_GFX_GCUTCL2_MEM2P512X95, "GCUTCL2_MEM2P512X95"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_gds_mem_list[] = { - {AMDGPU_GFX_GDS_MEM0, "GDS_MEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_lds_mem_list[] = { - {AMDGPU_GFX_LDS_BANK0, "LDS_BANK0"}, - {AMDGPU_GFX_LDS_BANK1, "LDS_BANK1"}, - {AMDGPU_GFX_LDS_BANK2, "LDS_BANK2"}, - {AMDGPU_GFX_LDS_BANK3, "LDS_BANK3"}, - {AMDGPU_GFX_LDS_BANK4, "LDS_BANK4"}, - {AMDGPU_GFX_LDS_BANK5, "LDS_BANK5"}, - {AMDGPU_GFX_LDS_BANK6, "LDS_BANK6"}, - {AMDGPU_GFX_LDS_BANK7, "LDS_BANK7"}, - {AMDGPU_GFX_LDS_BANK8, "LDS_BANK8"}, - {AMDGPU_GFX_LDS_BANK9, "LDS_BANK9"}, - {AMDGPU_GFX_LDS_BANK10, "LDS_BANK10"}, - {AMDGPU_GFX_LDS_BANK11, "LDS_BANK11"}, - {AMDGPU_GFX_LDS_BANK12, "LDS_BANK12"}, - {AMDGPU_GFX_LDS_BANK13, "LDS_BANK13"}, - {AMDGPU_GFX_LDS_BANK14, "LDS_BANK14"}, - {AMDGPU_GFX_LDS_BANK15, "LDS_BANK15"}, - {AMDGPU_GFX_LDS_BANK16, "LDS_BANK16"}, - {AMDGPU_GFX_LDS_BANK17, "LDS_BANK17"}, - {AMDGPU_GFX_LDS_BANK18, "LDS_BANK18"}, - {AMDGPU_GFX_LDS_BANK19, "LDS_BANK19"}, - {AMDGPU_GFX_LDS_BANK20, "LDS_BANK20"}, - {AMDGPU_GFX_LDS_BANK21, "LDS_BANK21"}, - {AMDGPU_GFX_LDS_BANK22, "LDS_BANK22"}, - {AMDGPU_GFX_LDS_BANK23, "LDS_BANK23"}, - {AMDGPU_GFX_LDS_BANK24, "LDS_BANK24"}, - {AMDGPU_GFX_LDS_BANK25, "LDS_BANK25"}, - {AMDGPU_GFX_LDS_BANK26, "LDS_BANK26"}, - {AMDGPU_GFX_LDS_BANK27, "LDS_BANK27"}, - {AMDGPU_GFX_LDS_BANK28, "LDS_BANK28"}, - {AMDGPU_GFX_LDS_BANK29, "LDS_BANK29"}, - {AMDGPU_GFX_LDS_BANK30, "LDS_BANK30"}, - {AMDGPU_GFX_LDS_BANK31, "LDS_BANK31"}, - {AMDGPU_GFX_LDS_SP_BUFFER_A, "LDS_SP_BUFFER_A"}, - {AMDGPU_GFX_LDS_SP_BUFFER_B, "LDS_SP_BUFFER_B"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_rlc_mem_list[] = { - {AMDGPU_GFX_RLC_GPMF32, "RLC_GPMF32"}, - {AMDGPU_GFX_RLC_RLCVF32, "RLC_RLCVF32"}, - {AMDGPU_GFX_RLC_SCRATCH, "RLC_SCRATCH"}, - {AMDGPU_GFX_RLC_SRM_ARAM, "RLC_SRM_ARAM"}, - {AMDGPU_GFX_RLC_SRM_DRAM, "RLC_SRM_DRAM"}, - {AMDGPU_GFX_RLC_TCTAG, "RLC_TCTAG"}, - {AMDGPU_GFX_RLC_SPM_SE, "RLC_SPM_SE"}, - {AMDGPU_GFX_RLC_SPM_GRBMT, "RLC_SPM_GRBMT"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_sp_mem_list[] = { - {AMDGPU_GFX_SP_SIMDID0, "SP_SIMDID0"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_spi_mem_list[] = { - {AMDGPU_GFX_SPI_MEM0, "SPI_MEM0"}, - {AMDGPU_GFX_SPI_MEM1, "SPI_MEM1"}, - {AMDGPU_GFX_SPI_MEM2, "SPI_MEM2"}, - {AMDGPU_GFX_SPI_MEM3, "SPI_MEM3"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_sqc_mem_list[] = { - {AMDGPU_GFX_SQC_INST_CACHE_A, "SQC_INST_CACHE_A"}, - {AMDGPU_GFX_SQC_INST_CACHE_B, "SQC_INST_CACHE_B"}, - {AMDGPU_GFX_SQC_INST_CACHE_TAG_A, "SQC_INST_CACHE_TAG_A"}, - {AMDGPU_GFX_SQC_INST_CACHE_TAG_B, "SQC_INST_CACHE_TAG_B"}, - {AMDGPU_GFX_SQC_INST_CACHE_MISS_FIFO_A, "SQC_INST_CACHE_MISS_FIFO_A"}, - {AMDGPU_GFX_SQC_INST_CACHE_MISS_FIFO_B, "SQC_INST_CACHE_MISS_FIFO_B"}, - {AMDGPU_GFX_SQC_INST_CACHE_GATCL1_MISS_FIFO_A, "SQC_INST_CACHE_GATCL1_MISS_FIFO_A"}, - {AMDGPU_GFX_SQC_INST_CACHE_GATCL1_MISS_FIFO_B, "SQC_INST_CACHE_GATCL1_MISS_FIFO_B"}, - {AMDGPU_GFX_SQC_DATA_CACHE_A, "SQC_DATA_CACHE_A"}, - {AMDGPU_GFX_SQC_DATA_CACHE_B, "SQC_DATA_CACHE_B"}, - {AMDGPU_GFX_SQC_DATA_CACHE_TAG_A, "SQC_DATA_CACHE_TAG_A"}, - {AMDGPU_GFX_SQC_DATA_CACHE_TAG_B, "SQC_DATA_CACHE_TAG_B"}, - {AMDGPU_GFX_SQC_DATA_CACHE_MISS_FIFO_A, "SQC_DATA_CACHE_MISS_FIFO_A"}, - {AMDGPU_GFX_SQC_DATA_CACHE_MISS_FIFO_B, "SQC_DATA_CACHE_MISS_FIFO_B"}, - {AMDGPU_GFX_SQC_DATA_CACHE_HIT_FIFO_A, "SQC_DATA_CACHE_HIT_FIFO_A"}, - {AMDGPU_GFX_SQC_DATA_CACHE_HIT_FIFO_B, "SQC_DATA_CACHE_HIT_FIFO_B"}, - {AMDGPU_GFX_SQC_DIRTY_BIT_A, "SQC_DIRTY_BIT_A"}, - {AMDGPU_GFX_SQC_DIRTY_BIT_B, "SQC_DIRTY_BIT_B"}, - {AMDGPU_GFX_SQC_WRITE_DATA_BUFFER_CU0, "SQC_WRITE_DATA_BUFFER_CU0"}, - {AMDGPU_GFX_SQC_WRITE_DATA_BUFFER_CU1, "SQC_WRITE_DATA_BUFFER_CU1"}, - {AMDGPU_GFX_SQC_UTCL1_MISS_LFIFO_DATA_CACHE_A, "SQC_UTCL1_MISS_LFIFO_DATA_CACHE_A"}, - {AMDGPU_GFX_SQC_UTCL1_MISS_LFIFO_DATA_CACHE_B, "SQC_UTCL1_MISS_LFIFO_DATA_CACHE_B"}, - {AMDGPU_GFX_SQC_UTCL1_MISS_LFIFO_INST_CACHE, "SQC_UTCL1_MISS_LFIFO_INST_CACHE"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_sq_mem_list[] = { - {AMDGPU_GFX_SQ_SGPR_MEM0, "SQ_SGPR_MEM0"}, - {AMDGPU_GFX_SQ_SGPR_MEM1, "SQ_SGPR_MEM1"}, - {AMDGPU_GFX_SQ_SGPR_MEM2, "SQ_SGPR_MEM2"}, - {AMDGPU_GFX_SQ_SGPR_MEM3, "SQ_SGPR_MEM3"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_ta_mem_list[] = { - {AMDGPU_GFX_TA_FS_AFIFO_RAM_LO, "TA_FS_AFIFO_RAM_LO"}, - {AMDGPU_GFX_TA_FS_AFIFO_RAM_HI, "TA_FS_AFIFO_RAM_HI"}, - {AMDGPU_GFX_TA_FS_CFIFO_RAM, "TA_FS_CFIFO_RAM"}, - {AMDGPU_GFX_TA_FSX_LFIFO, "TA_FSX_LFIFO"}, - {AMDGPU_GFX_TA_FS_DFIFO_RAM, "TA_FS_DFIFO_RAM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_tcc_mem_list[] = { - {AMDGPU_GFX_TCC_MEM1, "TCC_MEM1"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_tca_mem_list[] = { - {AMDGPU_GFX_TCA_MEM1, "TCA_MEM1"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_tci_mem_list[] = { - {AMDGPU_GFX_TCIW_MEM, "TCIW_MEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_tcp_mem_list[] = { - {AMDGPU_GFX_TCP_LFIFO0, "TCP_LFIFO0"}, - {AMDGPU_GFX_TCP_SET0BANK0_RAM, "TCP_SET0BANK0_RAM"}, - {AMDGPU_GFX_TCP_SET0BANK1_RAM, "TCP_SET0BANK1_RAM"}, - {AMDGPU_GFX_TCP_SET0BANK2_RAM, "TCP_SET0BANK2_RAM"}, - {AMDGPU_GFX_TCP_SET0BANK3_RAM, "TCP_SET0BANK3_RAM"}, - {AMDGPU_GFX_TCP_SET1BANK0_RAM, "TCP_SET1BANK0_RAM"}, - {AMDGPU_GFX_TCP_SET1BANK1_RAM, "TCP_SET1BANK1_RAM"}, - {AMDGPU_GFX_TCP_SET1BANK2_RAM, "TCP_SET1BANK2_RAM"}, - {AMDGPU_GFX_TCP_SET1BANK3_RAM, "TCP_SET1BANK3_RAM"}, - {AMDGPU_GFX_TCP_SET2BANK0_RAM, "TCP_SET2BANK0_RAM"}, - {AMDGPU_GFX_TCP_SET2BANK1_RAM, "TCP_SET2BANK1_RAM"}, - {AMDGPU_GFX_TCP_SET2BANK2_RAM, "TCP_SET2BANK2_RAM"}, - {AMDGPU_GFX_TCP_SET2BANK3_RAM, "TCP_SET2BANK3_RAM"}, - {AMDGPU_GFX_TCP_SET3BANK0_RAM, "TCP_SET3BANK0_RAM"}, - {AMDGPU_GFX_TCP_SET3BANK1_RAM, "TCP_SET3BANK1_RAM"}, - {AMDGPU_GFX_TCP_SET3BANK2_RAM, "TCP_SET3BANK2_RAM"}, - {AMDGPU_GFX_TCP_SET3BANK3_RAM, "TCP_SET3BANK3_RAM"}, - {AMDGPU_GFX_TCP_VM_FIFO, "TCP_VM_FIFO"}, - {AMDGPU_GFX_TCP_DB_TAGRAM0, "TCP_DB_TAGRAM0"}, - {AMDGPU_GFX_TCP_DB_TAGRAM1, "TCP_DB_TAGRAM1"}, - {AMDGPU_GFX_TCP_DB_TAGRAM2, "TCP_DB_TAGRAM2"}, - {AMDGPU_GFX_TCP_DB_TAGRAM3, "TCP_DB_TAGRAM3"}, - {AMDGPU_GFX_TCP_UTCL1_LFIFO_PROBE0, "TCP_UTCL1_LFIFO_PROBE0"}, - {AMDGPU_GFX_TCP_UTCL1_LFIFO_PROBE1, "TCP_UTCL1_LFIFO_PROBE1"}, - {AMDGPU_GFX_TCP_CMD_FIFO, "TCP_CMD_FIFO"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_td_mem_list[] = { - {AMDGPU_GFX_TD_UTD_CS_FIFO_MEM, "TD_UTD_CS_FIFO_MEM"}, - {AMDGPU_GFX_TD_UTD_SS_FIFO_LO_MEM, "TD_UTD_SS_FIFO_LO_MEM"}, - {AMDGPU_GFX_TD_UTD_SS_FIFO_HI_MEM, "TD_UTD_SS_FIFO_HI_MEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_tcx_mem_list[] = { - {AMDGPU_GFX_TCX_FIFOD0, "TCX_FIFOD0"}, - {AMDGPU_GFX_TCX_FIFOD1, "TCX_FIFOD1"}, - {AMDGPU_GFX_TCX_FIFOD2, "TCX_FIFOD2"}, - {AMDGPU_GFX_TCX_FIFOD3, "TCX_FIFOD3"}, - {AMDGPU_GFX_TCX_FIFOD4, "TCX_FIFOD4"}, - {AMDGPU_GFX_TCX_FIFOD5, "TCX_FIFOD5"}, - {AMDGPU_GFX_TCX_FIFOD6, "TCX_FIFOD6"}, - {AMDGPU_GFX_TCX_FIFOD7, "TCX_FIFOD7"}, - {AMDGPU_GFX_TCX_FIFOB0, "TCX_FIFOB0"}, - {AMDGPU_GFX_TCX_FIFOB1, "TCX_FIFOB1"}, - {AMDGPU_GFX_TCX_FIFOB2, "TCX_FIFOB2"}, - {AMDGPU_GFX_TCX_FIFOB3, "TCX_FIFOB3"}, - {AMDGPU_GFX_TCX_FIFOB4, "TCX_FIFOB4"}, - {AMDGPU_GFX_TCX_FIFOB5, "TCX_FIFOB5"}, - {AMDGPU_GFX_TCX_FIFOB6, "TCX_FIFOB6"}, - {AMDGPU_GFX_TCX_FIFOB7, "TCX_FIFOB7"}, - {AMDGPU_GFX_TCX_FIFOA0, "TCX_FIFOA0"}, - {AMDGPU_GFX_TCX_FIFOA1, "TCX_FIFOA1"}, - {AMDGPU_GFX_TCX_FIFOA2, "TCX_FIFOA2"}, - {AMDGPU_GFX_TCX_FIFOA3, "TCX_FIFOA3"}, - {AMDGPU_GFX_TCX_FIFOA4, "TCX_FIFOA4"}, - {AMDGPU_GFX_TCX_FIFOA5, "TCX_FIFOA5"}, - {AMDGPU_GFX_TCX_FIFOA6, "TCX_FIFOA6"}, - {AMDGPU_GFX_TCX_FIFOA7, "TCX_FIFOA7"}, - {AMDGPU_GFX_TCX_CFIFO0, "TCX_CFIFO0"}, - {AMDGPU_GFX_TCX_CFIFO1, "TCX_CFIFO1"}, - {AMDGPU_GFX_TCX_CFIFO2, "TCX_CFIFO2"}, - {AMDGPU_GFX_TCX_CFIFO3, "TCX_CFIFO3"}, - {AMDGPU_GFX_TCX_CFIFO4, "TCX_CFIFO4"}, - {AMDGPU_GFX_TCX_CFIFO5, "TCX_CFIFO5"}, - {AMDGPU_GFX_TCX_CFIFO6, "TCX_CFIFO6"}, - {AMDGPU_GFX_TCX_CFIFO7, "TCX_CFIFO7"}, - {AMDGPU_GFX_TCX_FIFO_ACKB0, "TCX_FIFO_ACKB0"}, - {AMDGPU_GFX_TCX_FIFO_ACKB1, "TCX_FIFO_ACKB1"}, - {AMDGPU_GFX_TCX_FIFO_ACKB2, "TCX_FIFO_ACKB2"}, - {AMDGPU_GFX_TCX_FIFO_ACKB3, "TCX_FIFO_ACKB3"}, - {AMDGPU_GFX_TCX_FIFO_ACKB4, "TCX_FIFO_ACKB4"}, - {AMDGPU_GFX_TCX_FIFO_ACKB5, "TCX_FIFO_ACKB5"}, - {AMDGPU_GFX_TCX_FIFO_ACKB6, "TCX_FIFO_ACKB6"}, - {AMDGPU_GFX_TCX_FIFO_ACKB7, "TCX_FIFO_ACKB7"}, - {AMDGPU_GFX_TCX_FIFO_ACKD0, "TCX_FIFO_ACKD0"}, - {AMDGPU_GFX_TCX_FIFO_ACKD1, "TCX_FIFO_ACKD1"}, - {AMDGPU_GFX_TCX_FIFO_ACKD2, "TCX_FIFO_ACKD2"}, - {AMDGPU_GFX_TCX_FIFO_ACKD3, "TCX_FIFO_ACKD3"}, - {AMDGPU_GFX_TCX_FIFO_ACKD4, "TCX_FIFO_ACKD4"}, - {AMDGPU_GFX_TCX_FIFO_ACKD5, "TCX_FIFO_ACKD5"}, - {AMDGPU_GFX_TCX_FIFO_ACKD6, "TCX_FIFO_ACKD6"}, - {AMDGPU_GFX_TCX_FIFO_ACKD7, "TCX_FIFO_ACKD7"}, - {AMDGPU_GFX_TCX_DST_FIFOA0, "TCX_DST_FIFOA0"}, - {AMDGPU_GFX_TCX_DST_FIFOA1, "TCX_DST_FIFOA1"}, - {AMDGPU_GFX_TCX_DST_FIFOA2, "TCX_DST_FIFOA2"}, - {AMDGPU_GFX_TCX_DST_FIFOA3, "TCX_DST_FIFOA3"}, - {AMDGPU_GFX_TCX_DST_FIFOA4, "TCX_DST_FIFOA4"}, - {AMDGPU_GFX_TCX_DST_FIFOA5, "TCX_DST_FIFOA5"}, - {AMDGPU_GFX_TCX_DST_FIFOA6, "TCX_DST_FIFOA6"}, - {AMDGPU_GFX_TCX_DST_FIFOA7, "TCX_DST_FIFOA7"}, - {AMDGPU_GFX_TCX_DST_FIFOB0, "TCX_DST_FIFOB0"}, - {AMDGPU_GFX_TCX_DST_FIFOB1, "TCX_DST_FIFOB1"}, - {AMDGPU_GFX_TCX_DST_FIFOB2, "TCX_DST_FIFOB2"}, - {AMDGPU_GFX_TCX_DST_FIFOB3, "TCX_DST_FIFOB3"}, - {AMDGPU_GFX_TCX_DST_FIFOB4, "TCX_DST_FIFOB4"}, - {AMDGPU_GFX_TCX_DST_FIFOB5, "TCX_DST_FIFOB5"}, - {AMDGPU_GFX_TCX_DST_FIFOB6, "TCX_DST_FIFOB6"}, - {AMDGPU_GFX_TCX_DST_FIFOB7, "TCX_DST_FIFOB7"}, - {AMDGPU_GFX_TCX_DST_FIFOD0, "TCX_DST_FIFOD0"}, - {AMDGPU_GFX_TCX_DST_FIFOD1, "TCX_DST_FIFOD1"}, - {AMDGPU_GFX_TCX_DST_FIFOD2, "TCX_DST_FIFOD2"}, - {AMDGPU_GFX_TCX_DST_FIFOD3, "TCX_DST_FIFOD3"}, - {AMDGPU_GFX_TCX_DST_FIFOD4, "TCX_DST_FIFOD4"}, - {AMDGPU_GFX_TCX_DST_FIFOD5, "TCX_DST_FIFOD5"}, - {AMDGPU_GFX_TCX_DST_FIFOD6, "TCX_DST_FIFOD6"}, - {AMDGPU_GFX_TCX_DST_FIFOD7, "TCX_DST_FIFOD7"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB0, "TCX_DST_FIFO_ACKB0"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB1, "TCX_DST_FIFO_ACKB1"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB2, "TCX_DST_FIFO_ACKB2"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB3, "TCX_DST_FIFO_ACKB3"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB4, "TCX_DST_FIFO_ACKB4"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB5, "TCX_DST_FIFO_ACKB5"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB6, "TCX_DST_FIFO_ACKB6"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKB7, "TCX_DST_FIFO_ACKB7"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD0, "TCX_DST_FIFO_ACKD0"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD1, "TCX_DST_FIFO_ACKD1"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD2, "TCX_DST_FIFO_ACKD2"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD3, "TCX_DST_FIFO_ACKD3"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD4, "TCX_DST_FIFO_ACKD4"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD5, "TCX_DST_FIFO_ACKD5"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD6, "TCX_DST_FIFO_ACKD6"}, - {AMDGPU_GFX_TCX_DST_FIFO_ACKD7, "TCX_DST_FIFO_ACKD7"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_atc_l2_mem_list[] = { - {AMDGPU_GFX_ATC_L2_MEM, "ATC_L2_MEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_utcl2_mem_list[] = { - {AMDGPU_GFX_UTCL2_MEM, "UTCL2_MEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_vml2_mem_list[] = { - {AMDGPU_GFX_VML2_MEM, "VML2_MEM"}, -}; - -static const struct amdgpu_ras_memory_id_entry gfx_v9_4_3_ras_vml2_walker_mem_list[] = { - {AMDGPU_GFX_VML2_WALKER_MEM, "VML2_WALKER_MEM"}, -}; - -static const struct amdgpu_gfx_ras_mem_id_entry gfx_v9_4_3_ras_mem_list_array[AMDGPU_GFX_MEM_TYPE_NUM] = { - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_cp_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_gcea_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_gc_cane_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_gcutcl2_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_gds_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_lds_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_rlc_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_sp_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_spi_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_sqc_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_sq_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_ta_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_tcc_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_tca_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_tci_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_tcp_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_td_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_tcx_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_atc_l2_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_utcl2_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_vml2_mem_list) - AMDGPU_GFX_MEMID_ENT(gfx_v9_4_3_ras_vml2_walker_mem_list) -}; - -static const struct amdgpu_gfx_ras_reg_entry gfx_v9_4_3_ce_reg_list[] = { - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regRLC_CE_ERR_STATUS_LOW, regRLC_CE_ERR_STATUS_HIGH), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "RLC"}, - AMDGPU_GFX_RLC_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regCPC_CE_ERR_STATUS_LO, regCPC_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CPC"}, - AMDGPU_GFX_CP_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regCPF_CE_ERR_STATUS_LO, regCPF_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CPF"}, - AMDGPU_GFX_CP_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regCPG_CE_ERR_STATUS_LO, regCPG_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CPG"}, - AMDGPU_GFX_CP_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regGDS_CE_ERR_STATUS_LO, regGDS_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "GDS"}, - AMDGPU_GFX_GDS_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regGC_CANE_CE_ERR_STATUS_LO, regGC_CANE_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CANE"}, - AMDGPU_GFX_GC_CANE_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSPI_CE_ERR_STATUS_LO, regSPI_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SPI"}, - AMDGPU_GFX_SPI_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSP0_CE_ERR_STATUS_LO, regSP0_CE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SP0"}, - AMDGPU_GFX_SP_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSP1_CE_ERR_STATUS_LO, regSP1_CE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SP1"}, - AMDGPU_GFX_SP_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSQ_CE_ERR_STATUS_LO, regSQ_CE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SQ"}, - AMDGPU_GFX_SQ_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSQC_CE_EDC_LO, regSQC_CE_EDC_HI), - 5, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SQC"}, - AMDGPU_GFX_SQC_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCX_CE_ERR_STATUS_LO, regTCX_CE_ERR_STATUS_HI), - 2, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCX"}, - AMDGPU_GFX_TCX_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCC_CE_ERR_STATUS_LO, regTCC_CE_ERR_STATUS_HI), - 16, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCC"}, - AMDGPU_GFX_TCC_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTA_CE_EDC_LO, regTA_CE_EDC_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TA"}, - AMDGPU_GFX_TA_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCI_CE_EDC_LO_REG, regTCI_CE_EDC_HI_REG), - 27, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCI"}, - AMDGPU_GFX_TCI_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCP_CE_EDC_LO_REG, regTCP_CE_EDC_HI_REG), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCP"}, - AMDGPU_GFX_TCP_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTD_CE_EDC_LO, regTD_CE_EDC_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TD"}, - AMDGPU_GFX_TD_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regGCEA_CE_ERR_STATUS_LO, regGCEA_CE_ERR_STATUS_HI), - 16, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "GCEA"}, - AMDGPU_GFX_GCEA_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regLDS_CE_ERR_STATUS_LO, regLDS_CE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "LDS"}, - AMDGPU_GFX_LDS_MEM, 4}, -}; - -static const struct amdgpu_gfx_ras_reg_entry gfx_v9_4_3_ue_reg_list[] = { - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regRLC_UE_ERR_STATUS_LOW, regRLC_UE_ERR_STATUS_HIGH), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "RLC"}, - AMDGPU_GFX_RLC_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regCPC_UE_ERR_STATUS_LO, regCPC_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CPC"}, - AMDGPU_GFX_CP_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regCPF_UE_ERR_STATUS_LO, regCPF_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CPF"}, - AMDGPU_GFX_CP_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regCPG_UE_ERR_STATUS_LO, regCPG_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CPG"}, - AMDGPU_GFX_CP_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regGDS_UE_ERR_STATUS_LO, regGDS_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "GDS"}, - AMDGPU_GFX_GDS_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regGC_CANE_UE_ERR_STATUS_LO, regGC_CANE_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "CANE"}, - AMDGPU_GFX_GC_CANE_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSPI_UE_ERR_STATUS_LO, regSPI_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SPI"}, - AMDGPU_GFX_SPI_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSP0_UE_ERR_STATUS_LO, regSP0_UE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SP0"}, - AMDGPU_GFX_SP_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSP1_UE_ERR_STATUS_LO, regSP1_UE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SP1"}, - AMDGPU_GFX_SP_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSQ_UE_ERR_STATUS_LO, regSQ_UE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SQ"}, - AMDGPU_GFX_SQ_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regSQC_UE_EDC_LO, regSQC_UE_EDC_HI), - 5, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "SQC"}, - AMDGPU_GFX_SQC_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCX_UE_ERR_STATUS_LO, regTCX_UE_ERR_STATUS_HI), - 2, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCX"}, - AMDGPU_GFX_TCX_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCC_UE_ERR_STATUS_LO, regTCC_UE_ERR_STATUS_HI), - 16, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCC"}, - AMDGPU_GFX_TCC_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTA_UE_EDC_LO, regTA_UE_EDC_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TA"}, - AMDGPU_GFX_TA_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCI_UE_EDC_LO_REG, regTCI_UE_EDC_HI_REG), - 27, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCI"}, - AMDGPU_GFX_TCI_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCP_UE_EDC_LO_REG, regTCP_UE_EDC_HI_REG), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCP"}, - AMDGPU_GFX_TCP_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTD_UE_EDC_LO, regTD_UE_EDC_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TD"}, - AMDGPU_GFX_TD_MEM, 4}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regTCA_UE_ERR_STATUS_LO, regTCA_UE_ERR_STATUS_HI), - 2, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "TCA"}, - AMDGPU_GFX_TCA_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regGCEA_UE_ERR_STATUS_LO, regGCEA_UE_ERR_STATUS_HI), - 16, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "GCEA"}, - AMDGPU_GFX_GCEA_MEM, 1}, - {{AMDGPU_RAS_REG_ENTRY(GC, 0, regLDS_UE_ERR_STATUS_LO, regLDS_UE_ERR_STATUS_HI), - 10, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "LDS"}, - AMDGPU_GFX_LDS_MEM, 4}, -}; - -static void gfx_v9_4_3_inst_query_ras_err_count(struct amdgpu_device *adev, - void *ras_error_status, int xcc_id) -{ - struct ras_err_data *err_data = (struct ras_err_data *)ras_error_status; - unsigned long ce_count = 0, ue_count = 0; - uint32_t i, j, k; - - /* NOTE: convert xcc_id to physical XCD ID (XCD0 or XCD1) */ - struct amdgpu_smuio_mcm_config_info mcm_info = { - .socket_id = adev->smuio.funcs->get_socket_id(adev), - .die_id = xcc_id & 0x01 ? 1 : 0, - }; - - mutex_lock(&adev->grbm_idx_mutex); - - for (i = 0; i < ARRAY_SIZE(gfx_v9_4_3_ce_reg_list); i++) { - for (j = 0; j < gfx_v9_4_3_ce_reg_list[i].se_num; j++) { - for (k = 0; k < gfx_v9_4_3_ce_reg_list[i].reg_entry.reg_inst; k++) { - /* no need to select if instance number is 1 */ - if (gfx_v9_4_3_ce_reg_list[i].se_num > 1 || - gfx_v9_4_3_ce_reg_list[i].reg_entry.reg_inst > 1) - gfx_v9_4_3_xcc_select_se_sh(adev, j, 0, k, xcc_id); - - amdgpu_ras_inst_query_ras_error_count(adev, - &(gfx_v9_4_3_ce_reg_list[i].reg_entry), - 1, - gfx_v9_4_3_ras_mem_list_array[gfx_v9_4_3_ce_reg_list[i].mem_id_type].mem_id_ent, - gfx_v9_4_3_ras_mem_list_array[gfx_v9_4_3_ce_reg_list[i].mem_id_type].size, - GET_INST(GC, xcc_id), - AMDGPU_RAS_ERROR__SINGLE_CORRECTABLE, - &ce_count); - - amdgpu_ras_inst_query_ras_error_count(adev, - &(gfx_v9_4_3_ue_reg_list[i].reg_entry), - 1, - gfx_v9_4_3_ras_mem_list_array[gfx_v9_4_3_ue_reg_list[i].mem_id_type].mem_id_ent, - gfx_v9_4_3_ras_mem_list_array[gfx_v9_4_3_ue_reg_list[i].mem_id_type].size, - GET_INST(GC, xcc_id), - AMDGPU_RAS_ERROR__MULTI_UNCORRECTABLE, - &ue_count); - } - } - } - - /* handle extra register entries of UE */ - for (; i < ARRAY_SIZE(gfx_v9_4_3_ue_reg_list); i++) { - for (j = 0; j < gfx_v9_4_3_ue_reg_list[i].se_num; j++) { - for (k = 0; k < gfx_v9_4_3_ue_reg_list[i].reg_entry.reg_inst; k++) { - /* no need to select if instance number is 1 */ - if (gfx_v9_4_3_ue_reg_list[i].se_num > 1 || - gfx_v9_4_3_ue_reg_list[i].reg_entry.reg_inst > 1) - gfx_v9_4_3_xcc_select_se_sh(adev, j, 0, k, xcc_id); - - amdgpu_ras_inst_query_ras_error_count(adev, - &(gfx_v9_4_3_ue_reg_list[i].reg_entry), - 1, - gfx_v9_4_3_ras_mem_list_array[gfx_v9_4_3_ue_reg_list[i].mem_id_type].mem_id_ent, - gfx_v9_4_3_ras_mem_list_array[gfx_v9_4_3_ue_reg_list[i].mem_id_type].size, - GET_INST(GC, xcc_id), - AMDGPU_RAS_ERROR__MULTI_UNCORRECTABLE, - &ue_count); - } - } - } - - gfx_v9_4_3_xcc_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, - xcc_id); - mutex_unlock(&adev->grbm_idx_mutex); - - /* the caller should make sure initialize value of - * err_data->ue_count and err_data->ce_count - */ - amdgpu_ras_error_statistic_ue_count(err_data, &mcm_info, ue_count); - amdgpu_ras_error_statistic_ce_count(err_data, &mcm_info, ce_count); -} - -static void gfx_v9_4_3_inst_reset_ras_err_count(struct amdgpu_device *adev, - void *ras_error_status, int xcc_id) -{ - uint32_t i, j, k; - - mutex_lock(&adev->grbm_idx_mutex); - - for (i = 0; i < ARRAY_SIZE(gfx_v9_4_3_ce_reg_list); i++) { - for (j = 0; j < gfx_v9_4_3_ce_reg_list[i].se_num; j++) { - for (k = 0; k < gfx_v9_4_3_ce_reg_list[i].reg_entry.reg_inst; k++) { - /* no need to select if instance number is 1 */ - if (gfx_v9_4_3_ce_reg_list[i].se_num > 1 || - gfx_v9_4_3_ce_reg_list[i].reg_entry.reg_inst > 1) - gfx_v9_4_3_xcc_select_se_sh(adev, j, 0, k, xcc_id); - - amdgpu_ras_inst_reset_ras_error_count(adev, - &(gfx_v9_4_3_ce_reg_list[i].reg_entry), - 1, - GET_INST(GC, xcc_id)); - - amdgpu_ras_inst_reset_ras_error_count(adev, - &(gfx_v9_4_3_ue_reg_list[i].reg_entry), - 1, - GET_INST(GC, xcc_id)); - } - } - } - - /* handle extra register entries of UE */ - for (; i < ARRAY_SIZE(gfx_v9_4_3_ue_reg_list); i++) { - for (j = 0; j < gfx_v9_4_3_ue_reg_list[i].se_num; j++) { - for (k = 0; k < gfx_v9_4_3_ue_reg_list[i].reg_entry.reg_inst; k++) { - /* no need to select if instance number is 1 */ - if (gfx_v9_4_3_ue_reg_list[i].se_num > 1 || - gfx_v9_4_3_ue_reg_list[i].reg_entry.reg_inst > 1) - gfx_v9_4_3_xcc_select_se_sh(adev, j, 0, k, xcc_id); - - amdgpu_ras_inst_reset_ras_error_count(adev, - &(gfx_v9_4_3_ue_reg_list[i].reg_entry), - 1, - GET_INST(GC, xcc_id)); - } - } - } - - gfx_v9_4_3_xcc_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, - xcc_id); - mutex_unlock(&adev->grbm_idx_mutex); -} - static void gfx_v9_4_3_inst_enable_watchdog_timer(struct amdgpu_device *adev, void *ras_error_status, int xcc_id) { @@ -4624,18 +3758,6 @@ static void gfx_v9_4_3_inst_enable_watchdog_timer(struct amdgpu_device *adev, mutex_unlock(&adev->grbm_idx_mutex); } -static void gfx_v9_4_3_query_ras_error_count(struct amdgpu_device *adev, - void *ras_error_status) -{ - amdgpu_gfx_ras_error_func(adev, ras_error_status, - gfx_v9_4_3_inst_query_ras_err_count); -} - -static void gfx_v9_4_3_reset_ras_error_count(struct amdgpu_device *adev) -{ - amdgpu_gfx_ras_error_func(adev, NULL, gfx_v9_4_3_inst_reset_ras_err_count); -} - static void gfx_v9_4_3_enable_watchdog_timer(struct amdgpu_device *adev) { amdgpu_gfx_ras_error_func(adev, NULL, gfx_v9_4_3_inst_enable_watchdog_timer); @@ -5116,14 +4238,9 @@ struct amdgpu_xcp_ip_funcs gfx_v9_4_3_xcp_funcs = { .resume = &gfx_v9_4_3_xcp_resume }; -struct amdgpu_ras_block_hw_ops gfx_v9_4_3_ras_ops = { - .query_ras_error_count = &gfx_v9_4_3_query_ras_error_count, - .reset_ras_error_count = &gfx_v9_4_3_reset_ras_error_count, -}; - struct amdgpu_gfx_ras gfx_v9_4_3_ras = { .ras_block = { - .hw_ops = &gfx_v9_4_3_ras_ops, + .hw_ops = NULL, }, .enable_watchdog_timer = &gfx_v9_4_3_enable_watchdog_timer, }; -- cgit v1.2.3 From 2bf6867e5953a140fc91c3159987656e44ed29fb Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Tue, 23 Jun 2026 10:59:18 +0800 Subject: drm/amd/pm: Guard VBIOS AC timing table walk Reject AC timing blocks with a stride smaller than a dword before walking VBIOS data. A zero stride can otherwise keep reg_data pinned on a nonmatching MEM_ID forever. Also bound the data-block and END marker reads by the returned VRAM_Info table size so malformed index/data sizes do not push the timing walk past the table. Signed-off-by: Xiang Liu Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c | 28 +++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c index 1fff7567bca2..4b796d60b03d 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c @@ -46,16 +46,22 @@ union voltage_object_info { static int atomctrl_retrieve_ac_timing( uint8_t index, ATOM_INIT_REG_BLOCK *reg_block, + u8 *table_end, pp_atomctrl_mc_reg_table *table) { uint32_t i, j; + u16 stride = le16_to_cpu(reg_block->usRegDataBlkSize); uint8_t tmem_id; ATOM_MEMORY_SETTING_DATA_BLOCK *reg_data = (ATOM_MEMORY_SETTING_DATA_BLOCK *) ((uint8_t *)reg_block + (2 * sizeof(uint16_t)) + le16_to_cpu(reg_block->usRegIndexTblSize)); uint8_t num_ranges = 0; - while (*(uint32_t *)reg_data != END_OF_REG_DATA_BLOCK && + if (stride < sizeof(uint32_t)) + return -EINVAL; + + while ((uint8_t *)reg_data + sizeof(uint32_t) <= table_end && + *(uint32_t *)reg_data != END_OF_REG_DATA_BLOCK && num_ranges < VBIOS_MAX_AC_TIMING_ENTRIES) { tmem_id = (uint8_t)((*(uint32_t *)reg_data & MEM_ID_MASK) >> MEM_ID_SHIFT); @@ -67,6 +73,10 @@ static int atomctrl_retrieve_ac_timing( for (i = 0, j = 1; i < table->last; i++) { if ((table->mc_reg_address[i].uc_pre_reg_data & LOW_NIBBLE_MASK) == DATA_FROM_TABLE) { + if ((uint8_t *)reg_data + + (j + 1) * sizeof(uint32_t) > table_end) + return -EINVAL; + table->mc_reg_table_entry[num_ranges].mc_data[i] = (uint32_t)*((uint32_t *)reg_data + j); j++; @@ -81,11 +91,13 @@ static int atomctrl_retrieve_ac_timing( } reg_data = (ATOM_MEMORY_SETTING_DATA_BLOCK *) - ((uint8_t *)reg_data + le16_to_cpu(reg_block->usRegDataBlkSize)) ; + ((uint8_t *)reg_data + stride); } - PP_ASSERT_WITH_CODE((*(uint32_t *)reg_data == END_OF_REG_DATA_BLOCK), - "Invalid VramInfo table.", return -1); + if ((uint8_t *)reg_data + sizeof(uint32_t) > table_end || + *(uint32_t *)reg_data != END_OF_REG_DATA_BLOCK) + return -EINVAL; + table->num_entries = num_ranges; return 0; @@ -136,6 +148,7 @@ int atomctrl_initialize_mc_reg_table( { ATOM_VRAM_INFO_HEADER_V2_1 *vram_info; ATOM_INIT_REG_BLOCK *reg_block; + u8 *table_end; int result = 0; u8 frev, crev; u16 size; @@ -157,6 +170,7 @@ int atomctrl_initialize_mc_reg_table( } if (0 == result) { + table_end = (uint8_t *)vram_info + size; reg_block = (ATOM_INIT_REG_BLOCK *) ((uint8_t *)vram_info + le16_to_cpu(vram_info->usMemClkPatchTblOffset)); result = atomctrl_set_mc_reg_address_table(reg_block, table); @@ -164,7 +178,7 @@ int atomctrl_initialize_mc_reg_table( if (0 == result) { result = atomctrl_retrieve_ac_timing(module_index, - reg_block, table); + reg_block, table_end, table); } return result; @@ -177,6 +191,7 @@ int atomctrl_initialize_mc_reg_table_v2_2( { ATOM_VRAM_INFO_HEADER_V2_2 *vram_info; ATOM_INIT_REG_BLOCK *reg_block; + u8 *table_end; int result = 0; u8 frev, crev; u16 size; @@ -198,6 +213,7 @@ int atomctrl_initialize_mc_reg_table_v2_2( } if (0 == result) { + table_end = (uint8_t *)vram_info + size; reg_block = (ATOM_INIT_REG_BLOCK *) ((uint8_t *)vram_info + le16_to_cpu(vram_info->usMemClkPatchTblOffset)); result = atomctrl_set_mc_reg_address_table(reg_block, table); @@ -205,7 +221,7 @@ int atomctrl_initialize_mc_reg_table_v2_2( if (0 == result) { result = atomctrl_retrieve_ac_timing(module_index, - reg_block, table); + reg_block, table_end, table); } return result; -- cgit v1.2.3 From 680adf5faeeabb4585f7aeb53681719e2d6c2f41 Mon Sep 17 00:00:00 2001 From: Boyuan Zhang Date: Wed, 24 Jun 2026 09:50:01 -0400 Subject: drm/amdgpu/jpeg: fix jpeg_v5_0_1_is_idle detection jpeg_v5_0_1_is_idle() initializes ret to false and then accumulates ring idle status using &=. Since false & condition always remains false, the function can never report the JPEG block as idle. Initialize ret to true so the function returns true only when all JPEG rings report RB_JOB_DONE. Signed-off-by: Boyuan Zhang Reviewed-by: David (Ming Qiang) Wu Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index 324d5899bd80..8846cb3ed12b 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -674,7 +674,7 @@ static void jpeg_v5_0_1_dec_ring_set_wptr(struct amdgpu_ring *ring) static bool jpeg_v5_0_1_is_idle(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; - bool ret = false; + bool ret = true; int i, j; for (i = 0; i < adev->jpeg.num_jpeg_inst; ++i) { -- cgit v1.2.3 From e9df8e9d04e0593d17ddb069f3b7958991cd18c9 Mon Sep 17 00:00:00 2001 From: Boyuan Zhang Date: Fri, 26 Jun 2026 10:39:26 -0400 Subject: drm/amdgpu/jpeg: fix jpeg_v4_0_3_is_idle detection jpeg_v4_0_3_is_idle() initializes ret to false and then accumulates ring idle status using &=. Since false & condition always remains false, the function can never report the JPEG block as idle. Initialize ret to true so the function returns true only when all JPEG rings report RB_JOB_DONE. Signed-off-by: Boyuan Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c index 4c57871b810a..0fdc32b3ae91 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c @@ -1027,7 +1027,7 @@ void jpeg_v4_0_3_dec_ring_nop(struct amdgpu_ring *ring, uint32_t count) static bool jpeg_v4_0_3_is_idle(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; - bool ret = false; + bool ret = true; int i, j; for (i = 0; i < adev->jpeg.num_jpeg_inst; ++i) { -- cgit v1.2.3 From b5fb1891663afc741698555eb8d04ff644272445 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 29 Jan 2026 16:44:39 +0800 Subject: drm/amdgpu: retire legacy RAS reset/query operations for XGMI v6_4 retire legacy RAS reset/query operations for XGMI v6_4 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 49 ++------------------------------ 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index 4ccc1bb6b22f..d2c5bb50d94a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -106,16 +106,6 @@ static const int walf_pcs_err_noncorrectable_mask_reg_aldebaran[] = { smnPCS_GOPX1_PCS_ERROR_NONCORRECTABLE_MASK + 0x100000 }; -static const int xgmi3x16_pcs_err_status_reg_v6_4[] = { - smnPCS_XGMI3X16_PCS_ERROR_STATUS, - smnPCS_XGMI3X16_PCS_ERROR_STATUS + 0x100000 -}; - -static const int xgmi3x16_pcs_err_noncorrectable_mask_reg_v6_4[] = { - smnPCS_XGMI3X16_PCS_ERROR_NONCORRECTABLE_MASK, - smnPCS_XGMI3X16_PCS_ERROR_NONCORRECTABLE_MASK + 0x100000 -}; - static const struct amdgpu_pcs_ras_field xgmi_pcs_ras_fields[] = { {"XGMI PCS DataLossErr", SOC15_REG_FIELD(XGMI0_PCS_GOPX16_PCS_ERROR_STATUS, DataLossErr)}, @@ -1165,17 +1155,6 @@ static void amdgpu_xgmi_reset_ras_error_count(struct amdgpu_device *adev) default: break; } - - switch (amdgpu_ip_version(adev, XGMI_HWIP, 0)) { - case IP_VERSION(6, 4, 0): - case IP_VERSION(6, 4, 1): - for (i = 0; i < ARRAY_SIZE(xgmi3x16_pcs_err_status_reg_v6_4); i++) - pcs_clear_status(adev, - xgmi3x16_pcs_err_status_reg_v6_4[i]); - break; - default: - break; - } } static int amdgpu_xgmi_query_pcs_error_status(struct amdgpu_device *adev, @@ -1193,11 +1172,7 @@ static int amdgpu_xgmi_query_pcs_error_status(struct amdgpu_device *adev, if (is_xgmi_pcs) { if (amdgpu_ip_version(adev, XGMI_HWIP, 0) == - IP_VERSION(6, 1, 0) || - amdgpu_ip_version(adev, XGMI_HWIP, 0) == - IP_VERSION(6, 4, 0) || - amdgpu_ip_version(adev, XGMI_HWIP, 0) == - IP_VERSION(6, 4, 1)) { + IP_VERSION(6, 1, 0)) { pcs_ras_fields = &xgmi3x16_pcs_ras_fields[0]; field_array_size = ARRAY_SIZE(xgmi3x16_pcs_ras_fields); } else { @@ -1235,7 +1210,7 @@ static void amdgpu_xgmi_query_ras_error_count(struct amdgpu_device *adev, void *ras_error_status) { struct ras_err_data *err_data = (struct ras_err_data *)ras_error_status; - int i, supported = 1; + int i; uint32_t data, mask_data = 0; uint32_t ue_cnt = 0, ce_cnt = 0; @@ -1299,26 +1274,6 @@ static void amdgpu_xgmi_query_ras_error_count(struct amdgpu_device *adev, } break; default: - supported = 0; - break; - } - - switch (amdgpu_ip_version(adev, XGMI_HWIP, 0)) { - case IP_VERSION(6, 4, 0): - case IP_VERSION(6, 4, 1): - /* check xgmi3x16 pcs error */ - for (i = 0; i < ARRAY_SIZE(xgmi3x16_pcs_err_status_reg_v6_4); i++) { - data = RREG32_PCIE(xgmi3x16_pcs_err_status_reg_v6_4[i]); - mask_data = - RREG32_PCIE(xgmi3x16_pcs_err_noncorrectable_mask_reg_v6_4[i]); - if (data) - amdgpu_xgmi_query_pcs_error_status(adev, data, - mask_data, &ue_cnt, &ce_cnt, true, true); - } - break; - default: - if (!supported) - dev_warn(adev->dev, "XGMI RAS error query not supported"); break; } -- cgit v1.2.3 From bc434335ab3c096a33a9e88c7951b4ac574db458 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Wed, 17 Jun 2026 14:20:16 +0800 Subject: drm/amdgpu: add the doorbell index input for suspending userq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It requires inputing the doorbell offset for MES firmware preempts the userq, and adding the doorbell offset also keep aliging with the union MESAPI__SUSPEND in MES firmware. Signed-off-by: Prike Liang Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index dbedb1e47c3f..f25cffad8efe 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -306,12 +306,14 @@ struct mes_suspend_gang_input { uint64_t gang_context_addr; uint64_t suspend_fence_addr; uint32_t suspend_fence_value; + uint32_t doorbell_offset; }; struct mes_resume_gang_input { uint32_t xcc_id; bool resume_all_gangs; uint64_t gang_context_addr; + uint32_t doorbell_offset; }; struct mes_reset_queue_input { -- cgit v1.2.3 From b1390963678d95510b90a6f7ade3568d32f0fb65 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Tue, 24 Feb 2026 10:05:20 +0800 Subject: drm/amdgpu: retire legacy RAS reset/query operations for mmhub v1_8 retire legacy RAS reset/query operations for mmhub v1_8 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mmhub.h | 23 ----- drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c | 139 +----------------------------- 2 files changed, 1 insertion(+), 161 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mmhub.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mmhub.h index 6b8214650e5d..c5120ba51e24 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mmhub.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mmhub.h @@ -21,29 +21,6 @@ #ifndef __AMDGPU_MMHUB_H__ #define __AMDGPU_MMHUB_H__ -enum amdgpu_mmhub_ras_memory_id { - AMDGPU_MMHUB_WGMI_PAGEMEM = 0, - AMDGPU_MMHUB_RGMI_PAGEMEM = 1, - AMDGPU_MMHUB_WDRAM_PAGEMEM = 2, - AMDGPU_MMHUB_RDRAM_PAGEMEM = 3, - AMDGPU_MMHUB_WIO_CMDMEM = 4, - AMDGPU_MMHUB_RIO_CMDMEM = 5, - AMDGPU_MMHUB_WGMI_CMDMEM = 6, - AMDGPU_MMHUB_RGMI_CMDMEM = 7, - AMDGPU_MMHUB_WDRAM_CMDMEM = 8, - AMDGPU_MMHUB_RDRAM_CMDMEM = 9, - AMDGPU_MMHUB_MAM_DMEM0 = 10, - AMDGPU_MMHUB_MAM_DMEM1 = 11, - AMDGPU_MMHUB_MAM_DMEM2 = 12, - AMDGPU_MMHUB_MAM_DMEM3 = 13, - AMDGPU_MMHUB_WRET_TAGMEM = 19, - AMDGPU_MMHUB_RRET_TAGMEM = 20, - AMDGPU_MMHUB_WIO_DATAMEM = 21, - AMDGPU_MMHUB_WGMI_DATAMEM = 22, - AMDGPU_MMHUB_WDRAM_DATAMEM = 23, - AMDGPU_MMHUB_MEMORY_BLOCK_LAST, -}; - struct amdgpu_mmhub_ras { struct amdgpu_ras_block_object ras_block; }; diff --git a/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c b/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c index 2a6a5ac4f374..47d07cd25fc4 100644 --- a/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c +++ b/drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c @@ -29,7 +29,6 @@ #include "soc15_common.h" #include "soc15.h" -#include "amdgpu_ras.h" #include "amdgpu_psp.h" #define regVM_L2_CNTL3_DEFAULT 0x80100007 @@ -636,144 +635,8 @@ const struct amdgpu_mmhub_funcs mmhub_v1_8_funcs = { .get_clockgating = mmhub_v1_8_get_clockgating, }; -static const struct amdgpu_ras_err_status_reg_entry mmhub_v1_8_ce_reg_list[] = { - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA0_CE_ERR_STATUS_LO, regMMEA0_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA0"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA1_CE_ERR_STATUS_LO, regMMEA1_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA1"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA2_CE_ERR_STATUS_LO, regMMEA2_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA2"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA3_CE_ERR_STATUS_LO, regMMEA3_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA3"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA4_CE_ERR_STATUS_LO, regMMEA4_CE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA4"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMM_CANE_CE_ERR_STATUS_LO, regMM_CANE_CE_ERR_STATUS_HI), - 1, 0, "MM_CANE"}, -}; - -static const struct amdgpu_ras_err_status_reg_entry mmhub_v1_8_ue_reg_list[] = { - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA0_UE_ERR_STATUS_LO, regMMEA0_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA0"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA1_UE_ERR_STATUS_LO, regMMEA1_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA1"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA2_UE_ERR_STATUS_LO, regMMEA2_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA2"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA3_UE_ERR_STATUS_LO, regMMEA3_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA3"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMMEA4_UE_ERR_STATUS_LO, regMMEA4_UE_ERR_STATUS_HI), - 1, (AMDGPU_RAS_ERR_INFO_VALID | AMDGPU_RAS_ERR_STATUS_VALID), "MMEA4"}, - {AMDGPU_RAS_REG_ENTRY(MMHUB, 0, regMM_CANE_UE_ERR_STATUS_LO, regMM_CANE_UE_ERR_STATUS_HI), - 1, 0, "MM_CANE"}, -}; - -static const struct amdgpu_ras_memory_id_entry mmhub_v1_8_ras_memory_list[] = { - {AMDGPU_MMHUB_WGMI_PAGEMEM, "MMEA_WGMI_PAGEMEM"}, - {AMDGPU_MMHUB_RGMI_PAGEMEM, "MMEA_RGMI_PAGEMEM"}, - {AMDGPU_MMHUB_WDRAM_PAGEMEM, "MMEA_WDRAM_PAGEMEM"}, - {AMDGPU_MMHUB_RDRAM_PAGEMEM, "MMEA_RDRAM_PAGEMEM"}, - {AMDGPU_MMHUB_WIO_CMDMEM, "MMEA_WIO_CMDMEM"}, - {AMDGPU_MMHUB_RIO_CMDMEM, "MMEA_RIO_CMDMEM"}, - {AMDGPU_MMHUB_WGMI_CMDMEM, "MMEA_WGMI_CMDMEM"}, - {AMDGPU_MMHUB_RGMI_CMDMEM, "MMEA_RGMI_CMDMEM"}, - {AMDGPU_MMHUB_WDRAM_CMDMEM, "MMEA_WDRAM_CMDMEM"}, - {AMDGPU_MMHUB_RDRAM_CMDMEM, "MMEA_RDRAM_CMDMEM"}, - {AMDGPU_MMHUB_MAM_DMEM0, "MMEA_MAM_DMEM0"}, - {AMDGPU_MMHUB_MAM_DMEM1, "MMEA_MAM_DMEM1"}, - {AMDGPU_MMHUB_MAM_DMEM2, "MMEA_MAM_DMEM2"}, - {AMDGPU_MMHUB_MAM_DMEM3, "MMEA_MAM_DMEM3"}, - {AMDGPU_MMHUB_WRET_TAGMEM, "MMEA_WRET_TAGMEM"}, - {AMDGPU_MMHUB_RRET_TAGMEM, "MMEA_RRET_TAGMEM"}, - {AMDGPU_MMHUB_WIO_DATAMEM, "MMEA_WIO_DATAMEM"}, - {AMDGPU_MMHUB_WGMI_DATAMEM, "MMEA_WGMI_DATAMEM"}, - {AMDGPU_MMHUB_WDRAM_DATAMEM, "MMEA_WDRAM_DATAMEM"}, -}; - -static void mmhub_v1_8_inst_query_ras_error_count(struct amdgpu_device *adev, - uint32_t mmhub_inst, - void *ras_err_status) -{ - struct ras_err_data *err_data = (struct ras_err_data *)ras_err_status; - unsigned long ue_count = 0, ce_count = 0; - - /* NOTE: mmhub is converted by aid_mask and the range is 0-3, - * which can be used as die ID directly */ - struct amdgpu_smuio_mcm_config_info mcm_info = { - .socket_id = adev->smuio.funcs->get_socket_id(adev), - .die_id = mmhub_inst, - }; - - amdgpu_ras_inst_query_ras_error_count(adev, - mmhub_v1_8_ce_reg_list, - ARRAY_SIZE(mmhub_v1_8_ce_reg_list), - mmhub_v1_8_ras_memory_list, - ARRAY_SIZE(mmhub_v1_8_ras_memory_list), - mmhub_inst, - AMDGPU_RAS_ERROR__SINGLE_CORRECTABLE, - &ce_count); - amdgpu_ras_inst_query_ras_error_count(adev, - mmhub_v1_8_ue_reg_list, - ARRAY_SIZE(mmhub_v1_8_ue_reg_list), - mmhub_v1_8_ras_memory_list, - ARRAY_SIZE(mmhub_v1_8_ras_memory_list), - mmhub_inst, - AMDGPU_RAS_ERROR__MULTI_UNCORRECTABLE, - &ue_count); - - amdgpu_ras_error_statistic_ce_count(err_data, &mcm_info, ce_count); - amdgpu_ras_error_statistic_ue_count(err_data, &mcm_info, ue_count); -} - -static void mmhub_v1_8_query_ras_error_count(struct amdgpu_device *adev, - void *ras_err_status) -{ - uint32_t inst_mask; - uint32_t i; - - if (!amdgpu_ras_is_supported(adev, AMDGPU_RAS_BLOCK__MMHUB)) { - dev_warn(adev->dev, "MMHUB RAS is not supported\n"); - return; - } - - inst_mask = adev->aid_mask; - for_each_inst(i, inst_mask) - mmhub_v1_8_inst_query_ras_error_count(adev, i, ras_err_status); -} - -static void mmhub_v1_8_inst_reset_ras_error_count(struct amdgpu_device *adev, - uint32_t mmhub_inst) -{ - amdgpu_ras_inst_reset_ras_error_count(adev, - mmhub_v1_8_ce_reg_list, - ARRAY_SIZE(mmhub_v1_8_ce_reg_list), - mmhub_inst); - amdgpu_ras_inst_reset_ras_error_count(adev, - mmhub_v1_8_ue_reg_list, - ARRAY_SIZE(mmhub_v1_8_ue_reg_list), - mmhub_inst); -} - -static void mmhub_v1_8_reset_ras_error_count(struct amdgpu_device *adev) -{ - uint32_t inst_mask; - uint32_t i; - - if (!amdgpu_ras_is_supported(adev, AMDGPU_RAS_BLOCK__MMHUB)) { - dev_warn(adev->dev, "MMHUB RAS is not supported\n"); - return; - } - - inst_mask = adev->aid_mask; - for_each_inst(i, inst_mask) - mmhub_v1_8_inst_reset_ras_error_count(adev, i); -} - -static const struct amdgpu_ras_block_hw_ops mmhub_v1_8_ras_hw_ops = { - .query_ras_error_count = mmhub_v1_8_query_ras_error_count, - .reset_ras_error_count = mmhub_v1_8_reset_ras_error_count, -}; - struct amdgpu_mmhub_ras mmhub_v1_8_ras = { .ras_block = { - .hw_ops = &mmhub_v1_8_ras_hw_ops, + .hw_ops = NULL, }, }; -- cgit v1.2.3 From fab755988b87e0574221b318cc114dde6b3b7ed8 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 26 Feb 2026 15:16:34 +0800 Subject: drm/amdgpu: add the macro definition of UMC_V12_0_PER_CHANNEL_OFFSET Add the macro definition of UMC_V12_0_PER_CHANNEL_OFFSET for subsequent use. Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.h index 8a35ad856165..650b5f1f22f7 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.h @@ -290,6 +290,8 @@ /* R13 bit shift should be considered, double the number */ #define UMC_V12_0_BAD_PAGE_NUM_PER_CHANNEL (UMC_V12_0_NA_MAP_PA_NUM * 2) +/* UMC register per channel offset */ +#define UMC_V12_0_PER_CHANNEL_OFFSET 0x400 /* C2, C3, C4, R13, four MCA bits are looped in page retirement */ #define UMC_V12_0_RETIRE_LOOP_BITS 4 -- cgit v1.2.3 From adf423513b1f55264ffd6dc457e41ff4b5da4517 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 26 Feb 2026 15:00:17 +0800 Subject: drm/amdgpu: retire legacy RAS reset/query operations for umc v12.0 retire legacy RAS reset/query operations for umc v12.0 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c | 3 +- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 214 +-------------------------------- drivers/gpu/drm/amd/amdgpu/umc_v12_0.h | 25 ---- 3 files changed, 3 insertions(+), 239 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c index 5166055c6692..1fcc0594fd0a 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c @@ -57,6 +57,7 @@ #include "umc_v6_0.h" #include "umc_v6_7.h" #include "umc_v12_0.h" +#include "ras_umc_v12_0.h" #include "hdp_v4_0.h" #include "mca_v3_0.h" @@ -1382,7 +1383,7 @@ static void gmc_v9_0_set_umc_funcs(struct amdgpu_device *adev) case IP_VERSION(12, 0, 0): case IP_VERSION(12, 5, 0): adev->umc.max_ras_err_cnt_per_query = - UMC_V12_0_TOTAL_CHANNEL_NUM(adev) * UMC_V12_0_BAD_PAGE_NUM_PER_CHANNEL; + UMC_V12_0_TOTAL_CHANNEL_NUM * UMC_V12_0_BAD_PAGE_NUM_PER_CHANNEL; adev->umc.channel_inst_num = UMC_V12_0_CHANNEL_INSTANCE_NUM; adev->umc.umc_inst_num = UMC_V12_0_UMC_INSTANCE_NUM; adev->umc.node_inst_num /= UMC_V12_0_UMC_INSTANCE_NUM; diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index e441270a91ec..d39e74f6ce03 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -31,45 +31,6 @@ #define MAX_ECC_NUM_PER_RETIREMENT 32 #define DELAYED_TIME_FOR_GPU_RESET 1000 //ms -static inline uint64_t get_umc_v12_0_reg_offset(struct amdgpu_device *adev, - uint32_t node_inst, - uint32_t umc_inst, - uint32_t ch_inst) -{ - uint32_t index = umc_inst * adev->umc.channel_inst_num + ch_inst; - uint64_t cross_node_offset = (node_inst == 0) ? 0 : UMC_V12_0_CROSS_NODE_OFFSET; - - umc_inst = index / 4; - ch_inst = index % 4; - - return adev->umc.channel_offs * ch_inst + UMC_V12_0_INST_DIST * umc_inst + - UMC_V12_0_NODE_DIST * node_inst + cross_node_offset; -} - -static int umc_v12_0_reset_error_count_per_channel(struct amdgpu_device *adev, - uint32_t node_inst, uint32_t umc_inst, - uint32_t ch_inst, void *data) -{ - uint64_t odecc_err_cnt_addr; - uint64_t umc_reg_offset = - get_umc_v12_0_reg_offset(adev, node_inst, umc_inst, ch_inst); - - odecc_err_cnt_addr = - SOC15_REG_OFFSET(UMC, 0, regUMCCH0_OdEccErrCnt); - - /* clear error count */ - WREG32_PCIE_EXT((odecc_err_cnt_addr + umc_reg_offset) * 4, - UMC_V12_0_CE_CNT_INIT); - - return 0; -} - -static void umc_v12_0_reset_error_count(struct amdgpu_device *adev) -{ - amdgpu_umc_loop_channels(adev, - umc_v12_0_reset_error_count_per_channel, NULL); -} - bool umc_v12_0_is_deferred_error(struct amdgpu_device *adev, uint64_t mc_umc_status) { dev_dbg(adev->dev, @@ -115,65 +76,6 @@ bool umc_v12_0_is_correctable_error(struct amdgpu_device *adev, uint64_t mc_umc_ !(umc_v12_0_is_uncorrectable_error(adev, mc_umc_status))))); } -static void umc_v12_0_query_error_count_per_type(struct amdgpu_device *adev, - uint64_t umc_reg_offset, - unsigned long *error_count, - check_error_type_func error_type_func) -{ - uint64_t mc_umc_status; - uint64_t mc_umc_status_addr; - - mc_umc_status_addr = - SOC15_REG_OFFSET(UMC, 0, regMCA_UMC_UMC0_MCUMC_STATUST0); - - /* Check MCUMC_STATUS */ - mc_umc_status = - RREG64_PCIE_EXT((mc_umc_status_addr + umc_reg_offset) * 4); - - if (error_type_func(adev, mc_umc_status)) - *error_count += 1; -} - -static int umc_v12_0_query_error_count(struct amdgpu_device *adev, - uint32_t node_inst, uint32_t umc_inst, - uint32_t ch_inst, void *data) -{ - struct ras_err_data *err_data = (struct ras_err_data *)data; - unsigned long ue_count = 0, ce_count = 0, de_count = 0; - - /* NOTE: node_inst is converted by adev->umc.active_mask and the range is [0-3], - * which can be used as die ID directly */ - struct amdgpu_smuio_mcm_config_info mcm_info = { - .socket_id = adev->smuio.funcs->get_socket_id(adev), - .die_id = node_inst, - }; - - uint64_t umc_reg_offset = - get_umc_v12_0_reg_offset(adev, node_inst, umc_inst, ch_inst); - - umc_v12_0_query_error_count_per_type(adev, umc_reg_offset, - &ce_count, umc_v12_0_is_correctable_error); - umc_v12_0_query_error_count_per_type(adev, umc_reg_offset, - &ue_count, umc_v12_0_is_uncorrectable_error); - umc_v12_0_query_error_count_per_type(adev, umc_reg_offset, - &de_count, umc_v12_0_is_deferred_error); - - amdgpu_ras_error_statistic_ue_count(err_data, &mcm_info, ue_count); - amdgpu_ras_error_statistic_ce_count(err_data, &mcm_info, ce_count); - amdgpu_ras_error_statistic_de_count(err_data, &mcm_info, de_count); - - return 0; -} - -static void umc_v12_0_query_ras_error_count(struct amdgpu_device *adev, - void *ras_error_status) -{ - amdgpu_umc_loop_channels(adev, - umc_v12_0_query_error_count, ras_error_status); - - umc_v12_0_reset_error_count(adev); -} - static void umc_v12_0_get_retire_flip_bits(struct amdgpu_device *adev) { enum amdgpu_memory_partition nps = AMDGPU_NPS1_PARTITION_MODE; @@ -371,98 +273,6 @@ out: return ret; } -static int umc_v12_0_query_error_address(struct amdgpu_device *adev, - uint32_t node_inst, uint32_t umc_inst, - uint32_t ch_inst, void *data) -{ - struct ras_err_data *err_data = (struct ras_err_data *)data; - struct ta_ras_query_address_input addr_in; - uint64_t mc_umc_status_addr; - uint64_t mc_umc_status, err_addr; - uint64_t mc_umc_addrt0; - uint64_t umc_reg_offset = - get_umc_v12_0_reg_offset(adev, node_inst, umc_inst, ch_inst); - - mc_umc_status_addr = - SOC15_REG_OFFSET(UMC, 0, regMCA_UMC_UMC0_MCUMC_STATUST0); - - mc_umc_status = RREG64_PCIE_EXT((mc_umc_status_addr + umc_reg_offset) * 4); - - if (mc_umc_status == 0) - return 0; - - if (!err_data->err_addr) { - /* clear umc status */ - WREG64_PCIE_EXT((mc_umc_status_addr + umc_reg_offset) * 4, 0x0ULL); - - return 0; - } - - /* calculate error address if ue error is detected */ - if (umc_v12_0_is_uncorrectable_error(adev, mc_umc_status) || - umc_v12_0_is_deferred_error(adev, mc_umc_status)) { - mc_umc_addrt0 = - SOC15_REG_OFFSET(UMC, 0, regMCA_UMC_UMC0_MCUMC_ADDRT0); - - err_addr = RREG64_PCIE_EXT((mc_umc_addrt0 + umc_reg_offset) * 4); - - err_addr = REG_GET_FIELD(err_addr, MCA_UMC_UMC0_MCUMC_ADDRT0, ErrorAddr); - - if (!adev->aid_mask && - adev->smuio.funcs && - adev->smuio.funcs->get_socket_id) - addr_in.ma.socket_id = adev->smuio.funcs->get_socket_id(adev); - else - addr_in.ma.socket_id = 0; - - addr_in.ma.err_addr = err_addr; - addr_in.ma.ch_inst = ch_inst; - addr_in.ma.umc_inst = umc_inst; - addr_in.ma.node_inst = node_inst; - - umc_v12_0_convert_error_address(adev, err_data, &addr_in, NULL, true); - } - - /* clear umc status */ - WREG64_PCIE_EXT((mc_umc_status_addr + umc_reg_offset) * 4, 0x0ULL); - - return 0; -} - -static void umc_v12_0_query_ras_error_address(struct amdgpu_device *adev, - void *ras_error_status) -{ - amdgpu_umc_loop_channels(adev, - umc_v12_0_query_error_address, ras_error_status); -} - -static int umc_v12_0_err_cnt_init_per_channel(struct amdgpu_device *adev, - uint32_t node_inst, uint32_t umc_inst, - uint32_t ch_inst, void *data) -{ - uint32_t odecc_cnt_sel; - uint64_t odecc_cnt_sel_addr, odecc_err_cnt_addr; - uint64_t umc_reg_offset = - get_umc_v12_0_reg_offset(adev, node_inst, umc_inst, ch_inst); - - odecc_cnt_sel_addr = - SOC15_REG_OFFSET(UMC, 0, regUMCCH0_OdEccCntSel); - odecc_err_cnt_addr = - SOC15_REG_OFFSET(UMC, 0, regUMCCH0_OdEccErrCnt); - - odecc_cnt_sel = RREG32_PCIE_EXT((odecc_cnt_sel_addr + umc_reg_offset) * 4); - - /* set ce error interrupt type to APIC based interrupt */ - odecc_cnt_sel = REG_SET_FIELD(odecc_cnt_sel, UMCCH0_OdEccCntSel, - OdEccErrInt, 0x1); - WREG32_PCIE_EXT((odecc_cnt_sel_addr + umc_reg_offset) * 4, odecc_cnt_sel); - - /* set error count to initial value */ - WREG32_PCIE_EXT((odecc_err_cnt_addr + umc_reg_offset) * 4, UMC_V12_0_CE_CNT_INIT); - - return 0; -} - static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, void *ras_error_status) { @@ -482,26 +292,6 @@ static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, return false; } -static void umc_v12_0_err_cnt_init(struct amdgpu_device *adev) -{ - amdgpu_umc_loop_channels(adev, - umc_v12_0_err_cnt_init_per_channel, NULL); -} - -static bool umc_v12_0_query_ras_poison_mode(struct amdgpu_device *adev) -{ - /* - * Force return true, because regUMCCH0_EccCtrl - * is not accessible from host side - */ - return true; -} - -const struct amdgpu_ras_block_hw_ops umc_v12_0_ras_hw_ops = { - .query_ras_error_count = umc_v12_0_query_ras_error_count, - .query_ras_error_address = umc_v12_0_query_ras_error_address, -}; - static int umc_v12_0_update_ecc_status(struct amdgpu_device *adev, uint64_t status, uint64_t ipid, uint64_t addr) { @@ -690,10 +480,8 @@ static void umc_v12_0_mca_ipid_parse(struct amdgpu_device *adev, uint64_t ipid, struct amdgpu_umc_ras umc_v12_0_ras = { .ras_block = { - .hw_ops = &umc_v12_0_ras_hw_ops, + .hw_ops = NULL, }, - .err_cnt_init = umc_v12_0_err_cnt_init, - .query_ras_poison_mode = umc_v12_0_query_ras_poison_mode, .ecc_info_query_ras_error_address = umc_v12_0_query_ras_ecc_err_addr, .check_ecc_err_status = umc_v12_0_check_ecc_err_status, .update_ecc_status = umc_v12_0_update_ecc_status, diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h index 63b7e7254526..d470775be308 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h @@ -26,31 +26,6 @@ #include "soc15_common.h" #include "amdgpu.h" -#define UMC_V12_0_NODE_DIST 0x40000000 -#define UMC_V12_0_INST_DIST 0x40000 - -/* UMC register per channel offset */ -#define UMC_V12_0_PER_CHANNEL_OFFSET 0x400 - -/* UMC cross node offset */ -#define UMC_V12_0_CROSS_NODE_OFFSET 0x100000000 - -/* OdEccErrCnt max value */ -#define UMC_V12_0_CE_CNT_MAX 0xffff -/* umc ce interrupt threshold */ -#define UMC_V12_0_CE_INT_THRESHOLD 0xffff -/* umc ce count initial value */ -#define UMC_V12_0_CE_CNT_INIT (UMC_V12_0_CE_CNT_MAX - UMC_V12_0_CE_INT_THRESHOLD) - -/* number of umc channel instance with memory map register access */ -#define UMC_V12_0_CHANNEL_INSTANCE_NUM 8 -/* number of umc instance with memory map register access */ -#define UMC_V12_0_UMC_INSTANCE_NUM 4 - -/* Total channel instances for all available umc nodes */ -#define UMC_V12_0_TOTAL_CHANNEL_NUM(adev) \ - (UMC_V12_0_CHANNEL_INSTANCE_NUM * (adev)->gmc.num_umc) - /* one piece of normalized address is mapped to 8 pieces of physical address */ #define UMC_V12_0_NA_MAP_PA_NUM 8 /* R13 bit shift should be considered, double the number */ -- cgit v1.2.3 From 991d67e8456a65c627fc42e52cdd845f7c7b2919 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 10:21:37 +0800 Subject: drm/amdgpu: remove interface for updating umc v12_0 ecc Retire the interface to update umc v12_0 ecc status and its related code,since this interface is no longer needed. Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 32 ------------ drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h | 2 - drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 55 -------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h | 11 ---- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 92 --------------------------------- drivers/gpu/drm/amd/amdgpu/umc_v12_0.h | 3 -- 6 files changed, 195 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 78c2d4394708..5c28244f1b34 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -3410,35 +3410,6 @@ static void amdgpu_ras_validate_threshold(struct amdgpu_device *adev, } } -static void amdgpu_ras_ecc_log_init(struct ras_ecc_log_info *ecc_log) -{ - mutex_init(&ecc_log->lock); - - INIT_RADIX_TREE(&ecc_log->de_page_tree, GFP_KERNEL); - ecc_log->de_queried_count = 0; - ecc_log->consumption_q_count = 0; -} - -static void amdgpu_ras_ecc_log_fini(struct ras_ecc_log_info *ecc_log) -{ - struct radix_tree_iter iter; - void __rcu **slot; - struct ras_ecc_err *ecc_err; - - mutex_lock(&ecc_log->lock); - radix_tree_for_each_slot(slot, &ecc_log->de_page_tree, &iter, 0) { - ecc_err = radix_tree_deref_slot(slot); - kfree(ecc_err->err_pages.pfn); - kfree(ecc_err); - radix_tree_iter_delete(&ecc_log->de_page_tree, &iter, slot); - } - mutex_unlock(&ecc_log->lock); - - mutex_destroy(&ecc_log->lock); - ecc_log->de_queried_count = 0; - ecc_log->consumption_q_count = 0; -} - int amdgpu_ras_init_badpage_info(struct amdgpu_device *adev) { struct amdgpu_ras *con = amdgpu_ras_get_context(adev); @@ -3542,7 +3513,6 @@ int amdgpu_ras_recovery_init(struct amdgpu_device *adev, bool init_bp_info) mutex_init(&con->page_rsv_lock); mutex_init(&con->page_retirement_lock); - amdgpu_ras_ecc_log_init(&con->umc_ecc_log); #ifdef CONFIG_X86_MCE_AMD if ((adev->asic_type == CHIP_ALDEBARAN) && (adev->gmc.xgmi.connected_to_cpu)) @@ -3582,8 +3552,6 @@ static int amdgpu_ras_recovery_fini(struct amdgpu_device *adev) cancel_work_sync(&con->recovery_work); - amdgpu_ras_ecc_log_fini(&con->umc_ecc_log); - mutex_lock(&con->recovery_lock); con->eh_data = NULL; kfree(data->bps); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h index 255ce167d1cd..a44aed7f169e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h @@ -483,8 +483,6 @@ struct ras_ecc_err { struct ras_ecc_log_info { struct mutex lock; struct radix_tree_root de_page_tree; - uint64_t de_queried_count; - uint64_t consumption_q_count; }; struct ras_critical_region { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index 254aacc7138b..e760dc0fc5e6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -502,34 +502,6 @@ int amdgpu_umc_loop_channels(struct amdgpu_device *adev, return 0; } -int amdgpu_umc_update_ecc_status(struct amdgpu_device *adev, - uint64_t status, uint64_t ipid, uint64_t addr) -{ - if (adev->umc.ras->update_ecc_status) - return adev->umc.ras->update_ecc_status(adev, - status, ipid, addr); - return 0; -} - -int amdgpu_umc_logs_ecc_err(struct amdgpu_device *adev, - struct radix_tree_root *ecc_tree, struct ras_ecc_err *ecc_err) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - struct ras_ecc_log_info *ecc_log; - int ret; - - ecc_log = &con->umc_ecc_log; - - mutex_lock(&ecc_log->lock); - ret = radix_tree_insert(ecc_tree, ecc_err->pa_pfn, ecc_err); - if (!ret) - radix_tree_tag_set(ecc_tree, - ecc_err->pa_pfn, UMC_ECC_NEW_DETECTED_TAG); - mutex_unlock(&ecc_log->lock); - - return ret; -} - int amdgpu_umc_pages_in_a_row(struct amdgpu_device *adev, struct ras_err_data *err_data, uint64_t pa_addr) { @@ -578,33 +550,6 @@ out: return ret; } -int amdgpu_umc_mca_to_addr(struct amdgpu_device *adev, - uint64_t err_addr, uint32_t ch, uint32_t umc, - uint32_t node, uint32_t socket, - struct ta_ras_query_address_output *addr_out, bool dump_addr) -{ - struct ta_ras_query_address_input addr_in; - int ret; - - memset(&addr_in, 0, sizeof(addr_in)); - addr_in.ma.err_addr = err_addr; - addr_in.ma.ch_inst = ch; - addr_in.ma.umc_inst = umc; - addr_in.ma.node_inst = node; - addr_in.ma.socket_id = socket; - - if (adev->umc.ras && adev->umc.ras->convert_ras_err_addr) { - ret = adev->umc.ras->convert_ras_err_addr(adev, NULL, &addr_in, - addr_out, dump_addr); - if (ret) - return ret; - } else { - return 0; - } - - return 0; -} - int amdgpu_umc_pa2mca(struct amdgpu_device *adev, uint64_t pa, uint64_t *mca, enum amdgpu_memory_partition nps) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h index 8494a55ebf76..f65f3e082c64 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h @@ -103,8 +103,6 @@ struct amdgpu_umc_ras { void *ras_error_status); bool (*check_ecc_err_status)(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, void *ras_error_status); - int (*update_ecc_status)(struct amdgpu_device *adev, - uint64_t status, uint64_t ipid, uint64_t addr); int (*convert_ras_err_addr)(struct amdgpu_device *adev, struct ras_err_data *err_data, struct ta_ras_query_address_input *addr_in, @@ -179,21 +177,12 @@ int amdgpu_umc_page_retirement_mca(struct amdgpu_device *adev, int amdgpu_umc_loop_channels(struct amdgpu_device *adev, umc_func func, void *data); -int amdgpu_umc_update_ecc_status(struct amdgpu_device *adev, - uint64_t status, uint64_t ipid, uint64_t addr); -int amdgpu_umc_logs_ecc_err(struct amdgpu_device *adev, - struct radix_tree_root *ecc_tree, struct ras_ecc_err *ecc_err); - void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, void *ras_error_status); int amdgpu_umc_pages_in_a_row(struct amdgpu_device *adev, struct ras_err_data *err_data, uint64_t pa_addr); int amdgpu_umc_lookup_bad_pages_in_a_row(struct amdgpu_device *adev, uint64_t pa_addr, uint64_t *pfns, int len); -int amdgpu_umc_mca_to_addr(struct amdgpu_device *adev, - uint64_t err_addr, uint32_t ch, uint32_t umc, - uint32_t node, uint32_t socket, - struct ta_ras_query_address_output *addr_out, bool dump_addr); int amdgpu_umc_pa2mca(struct amdgpu_device *adev, uint64_t pa, uint64_t *mca, enum amdgpu_memory_partition nps); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index d39e74f6ce03..ebceb933481e 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -29,7 +29,6 @@ #include "mp/mp_13_0_6_sh_mask.h" #define MAX_ECC_NUM_PER_RETIREMENT 32 -#define DELAYED_TIME_FOR_GPU_RESET 1000 //ms bool umc_v12_0_is_deferred_error(struct amdgpu_device *adev, uint64_t mc_umc_status) { @@ -292,96 +291,6 @@ static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, return false; } -static int umc_v12_0_update_ecc_status(struct amdgpu_device *adev, - uint64_t status, uint64_t ipid, uint64_t addr) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - uint16_t hwid, mcatype; - uint64_t page_pfn[UMC_V12_0_BAD_PAGE_NUM_PER_CHANNEL]; - uint64_t err_addr, pa_addr = 0; - struct ras_ecc_err *ecc_err; - struct ta_ras_query_address_output addr_out; - uint32_t shift_bit = adev->umc.flip_bits.flip_bits_in_pa[2]; - int count, ret, i; - - hwid = REG_GET_FIELD(ipid, MCMP1_IPIDT0, HardwareID); - mcatype = REG_GET_FIELD(ipid, MCMP1_IPIDT0, McaType); - - /* The IP block decode of consumption is SMU */ - if (hwid != MCA_UMC_HWID_V12_0 || mcatype != MCA_UMC_MCATYPE_V12_0) { - con->umc_ecc_log.consumption_q_count++; - return 0; - } - - if (!status) - return 0; - - if (!umc_v12_0_is_deferred_error(adev, status)) - return 0; - - err_addr = REG_GET_FIELD(addr, - MCA_UMC_UMC0_MCUMC_ADDRT0, ErrorAddr); - - dev_dbg(adev->dev, - "UMC:IPID:0x%llx, socket:%llu, aid:%llu, inst:%llu, ch:%llu, err_addr:0x%llx\n", - ipid, - MCA_IPID_2_SOCKET_ID(ipid), - MCA_IPID_2_DIE_ID(ipid), - MCA_IPID_2_UMC_INST(ipid), - MCA_IPID_2_UMC_CH(ipid), - err_addr); - - ret = amdgpu_umc_mca_to_addr(adev, - err_addr, MCA_IPID_2_UMC_CH(ipid), - MCA_IPID_2_UMC_INST(ipid), MCA_IPID_2_DIE_ID(ipid), - MCA_IPID_2_SOCKET_ID(ipid), &addr_out, true); - if (ret) - return ret; - - ecc_err = kzalloc_obj(*ecc_err); - if (!ecc_err) - return -ENOMEM; - - pa_addr = addr_out.pa.pa; - ecc_err->status = status; - ecc_err->ipid = ipid; - ecc_err->addr = addr; - ecc_err->pa_pfn = pa_addr >> AMDGPU_GPU_PAGE_SHIFT; - ecc_err->channel_idx = addr_out.pa.channel_idx; - - /* If converted pa_pfn is 0, use pa C4 pfn. */ - if (!ecc_err->pa_pfn) - ecc_err->pa_pfn = BIT_ULL(shift_bit) >> AMDGPU_GPU_PAGE_SHIFT; - - ret = amdgpu_umc_logs_ecc_err(adev, &con->umc_ecc_log.de_page_tree, ecc_err); - if (ret) { - if (ret == -EEXIST) - con->umc_ecc_log.de_queried_count++; - else - dev_err(adev->dev, "Fail to log ecc error! ret:%d\n", ret); - - kfree(ecc_err); - return ret; - } - - con->umc_ecc_log.de_queried_count++; - - memset(page_pfn, 0, sizeof(page_pfn)); - count = amdgpu_umc_lookup_bad_pages_in_a_row(adev, - pa_addr, - page_pfn, ARRAY_SIZE(page_pfn)); - if (count <= 0) { - dev_warn(adev->dev, "Fail to convert error address! count:%d\n", count); - return 0; - } - - /* Reserve memory */ - for (i = 0; i < count; i++) - amdgpu_ras_reserve_page(adev, page_pfn[i]); - - return 0; -} - static int umc_v12_0_fill_error_record(struct amdgpu_device *adev, struct ras_ecc_err *ecc_err, void *ras_error_status) { @@ -484,7 +393,6 @@ struct amdgpu_umc_ras umc_v12_0_ras = { }, .ecc_info_query_ras_error_address = umc_v12_0_query_ras_ecc_err_addr, .check_ecc_err_status = umc_v12_0_check_ecc_err_status, - .update_ecc_status = umc_v12_0_update_ecc_status, .convert_ras_err_addr = umc_v12_0_convert_error_address, .get_die_id_from_pa = umc_v12_0_get_die_id, .get_retire_flip_bits = umc_v12_0_get_retire_flip_bits, diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h index d470775be308..9d9e84d8d3bb 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.h @@ -50,9 +50,6 @@ /* row bits in MCA address */ #define UMC_V12_0_MA_R0_BIT 10 -#define MCA_UMC_HWID_V12_0 0x96 -#define MCA_UMC_MCATYPE_V12_0 0x0 - #define MCA_IPID_LO_2_UMC_CH(_ipid_lo) (((((_ipid_lo) >> 20) & 0x1) * 4) + \ (((_ipid_lo) >> 12) & 0xF)) #define MCA_IPID_LO_2_UMC_INST(_ipid_lo) (((_ipid_lo) >> 21) & 0x7) -- cgit v1.2.3 From 8a8793f006786fef8ada7e5a6edd13ff4ef0ab50 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 10:32:30 +0800 Subject: drm/amdgpu: Remove the legacy bad page retirement Remove the legacy bad page retirement handling for UMC v12_0 Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 16 ++++----- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 31 ----------------- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 61 --------------------------------- 3 files changed, 6 insertions(+), 102 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 5c28244f1b34..14808a474b2c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -243,16 +243,12 @@ static int amdgpu_check_address_validity(struct amdgpu_device *adev, (address >= RAS_UMC_INJECT_ADDR_LIMIT)) return -EFAULT; - if (amdgpu_uniras_enabled(adev)) { - if (amdgpu_sriov_vf(adev)) - count = amdgpu_virt_ras_convert_retired_address(adev, address, - page_pfns, ARRAY_SIZE(page_pfns)); - else - count = amdgpu_ras_mgr_lookup_bad_pages_in_a_row(adev, address, - page_pfns, ARRAY_SIZE(page_pfns)); - } else - count = amdgpu_umc_lookup_bad_pages_in_a_row(adev, - address, page_pfns, ARRAY_SIZE(page_pfns)); + if (amdgpu_sriov_vf(adev)) + count = amdgpu_virt_ras_convert_retired_address(adev, address, + page_pfns, ARRAY_SIZE(page_pfns)); + else + count = amdgpu_ras_mgr_lookup_bad_pages_in_a_row(adev, address, + page_pfns, ARRAY_SIZE(page_pfns)); if (count <= 0) return -EPERM; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index e760dc0fc5e6..26c39437dc8c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -519,37 +519,6 @@ int amdgpu_umc_pages_in_a_row(struct amdgpu_device *adev, return -EINVAL; } -int amdgpu_umc_lookup_bad_pages_in_a_row(struct amdgpu_device *adev, - uint64_t pa_addr, uint64_t *pfns, int len) -{ - int i, ret; - struct ras_err_data err_data; - - err_data.err_addr = kzalloc_objs(struct eeprom_table_record, - adev->umc.retire_unit); - if (!err_data.err_addr) { - dev_warn(adev->dev, "Failed to alloc memory in bad page lookup!\n"); - return 0; - } - - ret = amdgpu_umc_pages_in_a_row(adev, &err_data, pa_addr); - if (ret) - goto out; - - for (i = 0; i < adev->umc.retire_unit; i++) { - if (i >= len) - goto out; - - pfns[i] = err_data.err_addr[i].retired_page; - } - ret = i; - adev->umc.err_addr_cnt = err_data.err_addr_cnt; - -out: - kfree(err_data.err_addr); - return ret; -} - int amdgpu_umc_pa2mca(struct amdgpu_device *adev, uint64_t pa, uint64_t *mca, enum amdgpu_memory_partition nps) { diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index ebceb933481e..e1d900818a81 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -28,8 +28,6 @@ #include "umc/umc_12_0_0_sh_mask.h" #include "mp/mp_13_0_6_sh_mask.h" -#define MAX_ECC_NUM_PER_RETIREMENT 32 - bool umc_v12_0_is_deferred_error(struct amdgpu_device *adev, uint64_t mc_umc_status) { dev_dbg(adev->dev, @@ -291,64 +289,6 @@ static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, return false; } -static int umc_v12_0_fill_error_record(struct amdgpu_device *adev, - struct ras_ecc_err *ecc_err, void *ras_error_status) -{ - struct ras_err_data *err_data = (struct ras_err_data *)ras_error_status; - uint64_t page_pfn[UMC_V12_0_BAD_PAGE_NUM_PER_CHANNEL]; - int ret, i, count; - - if (!err_data || !ecc_err) - return -EINVAL; - - memset(page_pfn, 0, sizeof(page_pfn)); - count = amdgpu_umc_lookup_bad_pages_in_a_row(adev, - ecc_err->pa_pfn << AMDGPU_GPU_PAGE_SHIFT, - page_pfn, ARRAY_SIZE(page_pfn)); - - for (i = 0; i < count; i++) { - ret = amdgpu_umc_fill_error_record(err_data, - ecc_err->addr, - page_pfn[i] << AMDGPU_GPU_PAGE_SHIFT, - ecc_err->channel_idx, - MCA_IPID_2_UMC_INST(ecc_err->ipid)); - if (ret) - break; - } - - err_data->de_count++; - - return ret; -} - -static void umc_v12_0_query_ras_ecc_err_addr(struct amdgpu_device *adev, - void *ras_error_status) -{ - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - struct ras_ecc_err *entries[MAX_ECC_NUM_PER_RETIREMENT]; - struct radix_tree_root *ecc_tree; - int new_detected, ret, i; - - ecc_tree = &con->umc_ecc_log.de_page_tree; - - mutex_lock(&con->umc_ecc_log.lock); - new_detected = radix_tree_gang_lookup_tag(ecc_tree, (void **)entries, - 0, ARRAY_SIZE(entries), UMC_ECC_NEW_DETECTED_TAG); - for (i = 0; i < new_detected; i++) { - if (!entries[i]) - continue; - - ret = umc_v12_0_fill_error_record(adev, entries[i], ras_error_status); - if (ret) { - dev_err(adev->dev, "Fail to fill umc error record, ret:%d\n", ret); - break; - } - radix_tree_tag_clear(ecc_tree, - entries[i]->pa_pfn, UMC_ECC_NEW_DETECTED_TAG); - } - mutex_unlock(&con->umc_ecc_log.lock); -} - static uint32_t umc_v12_0_get_die_id(struct amdgpu_device *adev, uint64_t mca_addr, uint64_t retired_page) { @@ -391,7 +331,6 @@ struct amdgpu_umc_ras umc_v12_0_ras = { .ras_block = { .hw_ops = NULL, }, - .ecc_info_query_ras_error_address = umc_v12_0_query_ras_ecc_err_addr, .check_ecc_err_status = umc_v12_0_check_ecc_err_status, .convert_ras_err_addr = umc_v12_0_convert_error_address, .get_die_id_from_pa = umc_v12_0_get_die_id, -- cgit v1.2.3 From 404665cf00288ba4c63f67e744ec05d5b61e1a26 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 3 Jun 2026 15:07:57 +0800 Subject: drm/amdgpu: remove legacy UMC v12_0 error address remove legacy UMC v12_0 error address conversion Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 93 ---------------------------------- 1 file changed, 93 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index e1d900818a81..4d6197c0efb1 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -178,98 +178,6 @@ static void umc_v12_0_get_retire_flip_bits(struct amdgpu_device *adev) adev->umc.retire_unit = 0x1 << flip_bits->bit_num; } -static int umc_v12_0_convert_error_address(struct amdgpu_device *adev, - struct ras_err_data *err_data, - struct ta_ras_query_address_input *addr_in, - struct ta_ras_query_address_output *addr_out, - bool dump_addr) -{ - uint32_t row = 0, row_lower = 0, row_high = 0; - uint32_t col = 0, col_lower = 0, bank = 0; - uint32_t channel_index = 0, umc_inst = 0; - uint32_t i, bit_num, retire_unit, *flip_bits; - uint64_t soc_pa, column, err_addr; - struct ta_ras_query_address_output addr_out_tmp; - struct ta_ras_query_address_output *paddr_out; - int ret = 0; - - if (!addr_out) - paddr_out = &addr_out_tmp; - else - paddr_out = addr_out; - - err_addr = bank = 0; - if (addr_in) { - err_addr = addr_in->ma.err_addr; - addr_in->addr_type = TA_RAS_MCA_TO_PA; - ret = psp_ras_query_address(&adev->psp, addr_in, paddr_out); - if (ret) { - dev_warn(adev->dev, "Failed to query RAS physical address for 0x%llx", - err_addr); - - goto out; - } - - bank = paddr_out->pa.bank; - /* no need to care about umc inst if addr_in is NULL */ - umc_inst = addr_in->ma.umc_inst; - } - - flip_bits = adev->umc.flip_bits.flip_bits_in_pa; - bit_num = adev->umc.flip_bits.bit_num; - retire_unit = adev->umc.retire_unit; - - soc_pa = paddr_out->pa.pa; - channel_index = paddr_out->pa.channel_idx; - /* clear loop bits in soc physical address */ - for (i = 0; i < bit_num; i++) - soc_pa &= ~BIT_ULL(flip_bits[i]); - - paddr_out->pa.pa = soc_pa; - /* get column bit 0 and 1 in mca address */ - col_lower = (err_addr >> 1) & 0x3ULL; - /* extra row bit will be handled later */ - row_lower = (err_addr >> UMC_V12_0_MA_R0_BIT) & 0x1fffULL; - row_lower &= ~BIT_ULL(adev->umc.flip_bits.flip_row_bit); - - if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(9, 5, 0)) { - row_high = (soc_pa >> adev->umc.flip_bits.r13_in_pa) & 0x3ULL; - /* it's 2.25GB in each channel, from MCA address to PA - * [R14 R13] is converted if the two bits value are 0x3, - * get them from PA instead of MCA address. - */ - row_lower |= (row_high << 13); - } - - if (!err_data && !dump_addr) - goto out; - - /* loop for all possibilities of retired bits */ - for (column = 0; column < retire_unit; column++) { - soc_pa = paddr_out->pa.pa; - for (i = 0; i < bit_num; i++) - soc_pa |= (((column >> i) & 0x1ULL) << flip_bits[i]); - - col = ((column & 0x7) << 2) | col_lower; - /* handle extra row bit */ - if (bit_num == RETIRE_FLIP_BITS_NUM) - row = ((column >> 3) << adev->umc.flip_bits.flip_row_bit) | - row_lower; - - if (dump_addr) - dev_info(adev->dev, - "Error Address(PA):0x%-10llx Row:0x%-4x Col:0x%-2x Bank:0x%x Channel:0x%x\n", - soc_pa, row, col, bank, channel_index); - - if (err_data) - amdgpu_umc_fill_error_record(err_data, err_addr, - soc_pa, channel_index, umc_inst); - } - -out: - return ret; -} - static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, void *ras_error_status) { @@ -332,7 +240,6 @@ struct amdgpu_umc_ras umc_v12_0_ras = { .hw_ops = NULL, }, .check_ecc_err_status = umc_v12_0_check_ecc_err_status, - .convert_ras_err_addr = umc_v12_0_convert_error_address, .get_die_id_from_pa = umc_v12_0_get_die_id, .get_retire_flip_bits = umc_v12_0_get_retire_flip_bits, .mca_ipid_parse = umc_v12_0_mca_ipid_parse, -- cgit v1.2.3 From 631849ff5d603841e74f19f4a5e30fe1f7d7cf30 Mon Sep 17 00:00:00 2001 From: Christian König Date: Wed, 24 Jun 2026 16:00:41 +0200 Subject: drm/amdgpu: fix check in amdgpu_hmm_invalidate_gfx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For a short moment during alloc/free the userptr BO is not part of his VM, so bo->vm_bo can be NULL. Keep a reference to the VM root PD as parent of the userptr BO so that we can always use that to wait for all submissions of the VM instead of only the one involving the userptr BO. Signed-off-by: Christian König Fixes: 91250893cbaa ("drm/amdgpu: fix waiting for all submissions for userptrs") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5399 Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 76da3f932f24..6a0699746fbc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -535,6 +535,7 @@ int amdgpu_gem_userptr_ioctl(struct drm_device *dev, void *data, bo = gem_to_amdgpu_bo(gobj); bo->preferred_domains = AMDGPU_GEM_DOMAIN_GTT; bo->allowed_domains = AMDGPU_GEM_DOMAIN_GTT; + bo->parent = amdgpu_bo_ref(fpriv->vm.root.bo); r = amdgpu_ttm_tt_set_userptr(&bo->tbo, args->addr, args->flags); if (r) goto release_object; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c index 99bc9ad67d5b..a7d13e337d84 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c @@ -67,7 +67,6 @@ static bool amdgpu_hmm_invalidate_gfx(struct mmu_interval_notifier *mni, { struct amdgpu_bo *bo = container_of(mni, struct amdgpu_bo, notifier); struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); - struct amdgpu_bo *vm_root = bo->vm_bo->vm->root.bo; long r; if (!mmu_notifier_range_blockable(range)) @@ -78,7 +77,7 @@ static bool amdgpu_hmm_invalidate_gfx(struct mmu_interval_notifier *mni, mmu_interval_set_seq(mni, cur_seq); amdgpu_vm_bo_invalidate(bo, false); - r = dma_resv_wait_timeout(vm_root->tbo.base.resv, + r = dma_resv_wait_timeout(bo->parent->tbo.base.resv, DMA_RESV_USAGE_BOOKKEEP, false, MAX_SCHEDULE_TIMEOUT); mutex_unlock(&adev->notifier_lock); -- cgit v1.2.3 From 0aeed866cb938943908c3ba46422128e49d2d080 Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Mon, 29 Jun 2026 15:58:56 -0500 Subject: drm/amd/display: Fix dangling pointer in CRTC reset function amdgpu_dm_crtc_reset_state() frees the old state before allocating a new one. If kzalloc() fails, the function returns without updating the state pointer, leaving a dangling pointer to already freed memory. Fix this by allocating the new state first. On allocation failure, the old state remains untouched and the function safely returns. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: e7b07ceef2a6 ("drm/amd/display: Merge amdgpu_dm_crtc and dm_crtc_state") Signed-off-by: Evgenii Burenchev Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260629090435.9729-4-evg28bur@yandex.ru [adjust for movement around current amd-staging-drm-next] Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 f7fcce6e76bb..0ad7704800d9 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 @@ -444,13 +444,13 @@ static void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc) { struct dm_crtc_state *state; - if (crtc->state) - amdgpu_dm_crtc_destroy_state(crtc, crtc->state); - state = kzalloc_obj(*state); - if (WARN_ON(!state)) + if (!state) return; + if (crtc->state) + amdgpu_dm_crtc_destroy_state(crtc, crtc->state); + __drm_atomic_helper_crtc_reset(crtc, &state->base); } -- cgit v1.2.3 From 3b1f4d5e47b361002490d2297b344ce34dae3d55 Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Mon, 29 Jun 2026 15:59:01 -0500 Subject: drm/amd/display: Fix dangling pointer in connector reset function amdgpu_dm_connector_funcs_reset() frees the old state before allocating a new one. If kzalloc() fails, the function returns without updating the state pointer, leaving a dangling pointer to already freed memory. Fix this by allocating the new state first. On allocation failure, the old state remains untouched and the function safely returns. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: e7b07ceef2a6 ("drm/amd/display: Merge amdgpu_dm_crtc and dm_crtc_state") Signed-off-by: Evgenii Burenchev Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260629090435.9729-5-evg28bur@yandex.ru [adjust for movement around current amd-staging-drm-next] Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 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 d4720c5576ce..40688d35bde6 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 @@ -1786,33 +1786,34 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector) void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) { - struct dm_connector_state *state = + struct dm_connector_state *old_state = to_dm_connector_state(connector->state); + struct dm_connector_state *state; + + state = kzalloc_obj(*state); + if (!state) + return; if (connector->state) __drm_atomic_helper_connector_destroy_state(connector->state); - kfree(state); + kfree(old_state); - state = kzalloc_obj(*state); + __drm_atomic_helper_connector_reset(connector, &state->base); - if (state) { - state->scaling = RMX_OFF; - state->underscan_enable = false; - state->underscan_hborder = 0; - state->underscan_vborder = 0; - state->base.max_requested_bpc = 8; - state->vcpi_slots = 0; - state->pbn = 0; - - if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { - if (amdgpu_dm_abm_level <= 0) - state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; - else - state->abm_level = amdgpu_dm_abm_level; - } + state->scaling = RMX_OFF; + state->underscan_enable = false; + state->underscan_hborder = 0; + state->underscan_vborder = 0; + state->base.max_requested_bpc = 8; + state->vcpi_slots = 0; + state->pbn = 0; - __drm_atomic_helper_connector_reset(connector, &state->base); + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + if (amdgpu_dm_abm_level <= 0) + state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + else + state->abm_level = amdgpu_dm_abm_level; } } EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_reset); -- cgit v1.2.3 From 2b3877b00aae569cf52e0a190031b4f2826cba1b Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 3 Jun 2026 15:30:44 +0800 Subject: drm/amdgpu: remove operations related to legacy address Remove operations related to legacy address conversion Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 248 ++----------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 6 +- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 40 ---- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h | 13 -- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 26 --- 5 files changed, 12 insertions(+), 321 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 14808a474b2c..bb83b7396881 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -2882,77 +2882,6 @@ static int amdgpu_ras_realloc_eh_data_space(struct amdgpu_device *adev, return 0; } -static int amdgpu_ras_mca2pa_by_idx(struct amdgpu_device *adev, - struct eeprom_table_record *bps, - struct ras_err_data *err_data) -{ - struct ta_ras_query_address_input addr_in; - uint32_t socket = 0; - int ret = 0; - - if (adev->smuio.funcs && adev->smuio.funcs->get_socket_id) - socket = adev->smuio.funcs->get_socket_id(adev); - - /* reinit err_data */ - err_data->err_addr_cnt = 0; - err_data->err_addr_len = adev->umc.retire_unit; - - memset(&addr_in, 0, sizeof(addr_in)); - addr_in.ma.err_addr = bps->address; - addr_in.ma.socket_id = socket; - addr_in.ma.ch_inst = bps->mem_channel; - if (!amdgpu_ras_smu_eeprom_supported(adev)) { - /* tell RAS TA the node instance is not used */ - addr_in.ma.node_inst = TA_RAS_INV_NODE; - } else { - addr_in.ma.umc_inst = bps->mcumc_id; - addr_in.ma.node_inst = bps->cu; - } - - if (adev->umc.ras && adev->umc.ras->convert_ras_err_addr) - ret = adev->umc.ras->convert_ras_err_addr(adev, err_data, - &addr_in, NULL, false); - - return ret; -} - -static int amdgpu_ras_mca2pa(struct amdgpu_device *adev, - struct eeprom_table_record *bps, - struct ras_err_data *err_data) -{ - struct ta_ras_query_address_input addr_in; - uint32_t die_id, socket = 0; - - if (adev->smuio.funcs && adev->smuio.funcs->get_socket_id) - socket = adev->smuio.funcs->get_socket_id(adev); - - /* although die id is gotten from PA in nps1 mode, the id is - * fitable for any nps mode - */ - if (adev->umc.ras && adev->umc.ras->get_die_id_from_pa) - die_id = adev->umc.ras->get_die_id_from_pa(adev, bps->address, - bps->retired_page << AMDGPU_GPU_PAGE_SHIFT); - else - return -EINVAL; - - /* reinit err_data */ - err_data->err_addr_cnt = 0; - err_data->err_addr_len = adev->umc.retire_unit; - - memset(&addr_in, 0, sizeof(addr_in)); - addr_in.ma.err_addr = bps->address; - addr_in.ma.ch_inst = bps->mem_channel; - addr_in.ma.umc_inst = bps->mcumc_id; - addr_in.ma.node_inst = die_id; - addr_in.ma.socket_id = socket; - - if (adev->umc.ras && adev->umc.ras->convert_ras_err_addr) - return adev->umc.ras->convert_ras_err_addr(adev, err_data, - &addr_in, NULL, false); - else - return -EINVAL; -} - static bool __check_record_in_range(struct amdgpu_device *adev, struct eeprom_table_record *bps, int count) { @@ -3013,117 +2942,13 @@ static int __amdgpu_ras_convert_rec_array_from_rom(struct amdgpu_device *adev, struct eeprom_table_record *bps, struct ras_err_data *err_data, enum amdgpu_memory_partition nps) { - int i = 0; - uint64_t chan_idx_v2; - enum amdgpu_memory_partition save_nps; - - save_nps = (bps[0].retired_page >> UMC_NPS_SHIFT) & UMC_NPS_MASK; - chan_idx_v2 = bps[0].retired_page & UMC_CHANNEL_IDX_V2; - /*old asics just have pa in eeprom*/ - if (IP_VERSION_MAJ(amdgpu_ip_version(adev, UMC_HWIP, 0)) < 12) { - memcpy(err_data->err_addr, bps, - sizeof(struct eeprom_table_record) * adev->umc.retire_unit); - goto out; - } - - for (i = 0; i < adev->umc.retire_unit; i++) - bps[i].retired_page &= ~(UMC_NPS_MASK << UMC_NPS_SHIFT); - - if (save_nps || chan_idx_v2) { - if (save_nps == nps) { - if (amdgpu_umc_pages_in_a_row(adev, err_data, - bps[0].retired_page << AMDGPU_GPU_PAGE_SHIFT)) - return -EINVAL; - for (i = 0; i < adev->umc.retire_unit; i++) { - err_data->err_addr[i].address = bps[0].address; - err_data->err_addr[i].mem_channel = bps[0].mem_channel; - err_data->err_addr[i].bank = bps[0].bank; - err_data->err_addr[i].err_type = bps[0].err_type; - err_data->err_addr[i].mcumc_id = bps[0].mcumc_id; - } - } else { - if (amdgpu_ras_mca2pa_by_idx(adev, &bps[0], err_data)) - return -EINVAL; - } - } else { - if (bps[0].address == 0) { - /* for specific old eeprom data, mca address is not stored, - * calc it from pa - */ - if (amdgpu_umc_pa2mca(adev, bps[0].retired_page << AMDGPU_GPU_PAGE_SHIFT, - &(bps[0].address), AMDGPU_NPS1_PARTITION_MODE)) - return -EINVAL; - } + memcpy(err_data->err_addr, bps, + sizeof(struct eeprom_table_record) * adev->umc.retire_unit); - if (amdgpu_ras_mca2pa(adev, &bps[0], err_data)) { - if (nps == AMDGPU_NPS1_PARTITION_MODE) - memcpy(err_data->err_addr, bps, - sizeof(struct eeprom_table_record) * adev->umc.retire_unit); - else - return -EOPNOTSUPP; - } - } - -out: return __amdgpu_ras_restore_bad_pages(adev, err_data->err_addr, adev->umc.retire_unit); } -static int __amdgpu_ras_convert_rec_from_rom(struct amdgpu_device *adev, - struct eeprom_table_record *bps, struct ras_err_data *err_data, - enum amdgpu_memory_partition nps) -{ - int i = 0; - uint64_t chan_idx_v2; - enum amdgpu_memory_partition save_nps; - - if (!amdgpu_ras_smu_eeprom_supported(adev)) { - save_nps = (bps->retired_page >> UMC_NPS_SHIFT) & UMC_NPS_MASK; - chan_idx_v2 = bps->retired_page & UMC_CHANNEL_IDX_V2; - bps->retired_page &= ~(UMC_NPS_MASK << UMC_NPS_SHIFT); - } else { - /* if pmfw manages eeprom, save_nps is not stored on eeprom, - * we should always convert mca address into physical address, - * make save_nps different from nps - */ - save_nps = nps + 1; - } - - if (save_nps == nps) { - if (amdgpu_umc_pages_in_a_row(adev, err_data, - bps->retired_page << AMDGPU_GPU_PAGE_SHIFT)) - return -EINVAL; - for (i = 0; i < adev->umc.retire_unit; i++) { - err_data->err_addr[i].address = bps->address; - err_data->err_addr[i].mem_channel = bps->mem_channel; - err_data->err_addr[i].bank = bps->bank; - err_data->err_addr[i].err_type = bps->err_type; - err_data->err_addr[i].mcumc_id = bps->mcumc_id; - } - } else { - if (save_nps || chan_idx_v2) { - if (amdgpu_ras_mca2pa_by_idx(adev, bps, err_data)) - return -EINVAL; - } else { - /* for specific old eeprom data, mca address is not stored, - * calc it from pa - */ - if (bps->address == 0) - if (amdgpu_umc_pa2mca(adev, - bps->retired_page << AMDGPU_GPU_PAGE_SHIFT, - &(bps->address), - AMDGPU_NPS1_PARTITION_MODE)) - return -EINVAL; - - if (amdgpu_ras_mca2pa(adev, bps, err_data)) - return -EOPNOTSUPP; - } - } - - return __amdgpu_ras_restore_bad_pages(adev, err_data->err_addr, - adev->umc.retire_unit); -} - /* it deal with vram only. */ int amdgpu_ras_add_bad_pages(struct amdgpu_device *adev, struct eeprom_table_record *bps, int pages, bool from_rom) @@ -3156,8 +2981,7 @@ int amdgpu_ras_add_bad_pages(struct amdgpu_device *adev, if (from_rom) { /* there is no pa recs in V3, so skip pa recs processing */ - if ((control->tbl_hdr.version < RAS_TABLE_VER_V3) && - !amdgpu_ras_smu_eeprom_supported(adev)) { + if (control->tbl_hdr.version < RAS_TABLE_VER_V3) { for (i = 0; i < pages; i++) { if (control->ras_num_recs - i >= adev->umc.retire_unit) { if ((bps[i].address == bps[i + 1].address) && @@ -3174,10 +2998,8 @@ int amdgpu_ras_add_bad_pages(struct amdgpu_device *adev, } } } - for (; i < pages; i++) { - ret = __amdgpu_ras_convert_rec_from_rom(adev, - &bps[i], &err_data, nps); - } + for (; i < pages; i++) + bps[i].retired_page &= ~(UMC_NPS_MASK << UMC_NPS_SHIFT); con->eh_data->count_saved = con->eh_data->count; } else { @@ -3202,7 +3024,7 @@ int amdgpu_ras_save_bad_pages(struct amdgpu_device *adev, struct amdgpu_ras *con = amdgpu_ras_get_context(adev); struct ras_err_handler_data *data; struct amdgpu_ras_eeprom_control *control; - int save_count, unit_num, i; + int save_count, unit_num; if (!con || !con->eh_data) { if (new_cnt) @@ -3239,21 +3061,10 @@ int amdgpu_ras_save_bad_pages(struct amdgpu_device *adev, /* only new entries are saved */ if (unit_num && save_count) { /*old asics only save pa to eeprom like before*/ - if (IP_VERSION_MAJ(amdgpu_ip_version(adev, UMC_HWIP, 0)) < 12) { - if (amdgpu_ras_eeprom_append(control, - &data->bps[data->count_saved], unit_num)) { - dev_err(adev->dev, "Failed to save EEPROM table data!"); - return -EIO; - } - } else { - for (i = 0; i < unit_num; i++) { - if (amdgpu_ras_eeprom_append(control, - &data->bps[data->count_saved + - i * adev->umc.retire_unit], 1)) { - dev_err(adev->dev, "Failed to save EEPROM table data!"); - return -EIO; - } - } + if (amdgpu_ras_eeprom_append(control, + &data->bps[data->count_saved], unit_num)) { + dev_err(adev->dev, "Failed to save EEPROM table data!"); + return -EIO; } dev_info(adev->dev, "Saved %d pages to EEPROM table.\n", save_count); @@ -3272,7 +3083,7 @@ static int amdgpu_ras_load_bad_pages(struct amdgpu_device *adev) struct amdgpu_ras_eeprom_control *control = &adev->psp.ras_context.ras->eeprom_control; struct eeprom_table_record *bps; - int ret, i = 0; + int ret; /* no bad page record, skip eeprom access */ if (control->ras_num_recs == 0 || amdgpu_bad_page_threshold == 0) @@ -3286,33 +3097,6 @@ static int amdgpu_ras_load_bad_pages(struct amdgpu_device *adev) if (ret) { dev_err(adev->dev, "Failed to load EEPROM table records!"); } else { - if (adev->umc.ras && adev->umc.ras->convert_ras_err_addr) { - /*In V3, there is no pa recs, and some cases(when address==0) may be parsed - as pa recs, so add verion check to avoid it. - */ - if ((control->tbl_hdr.version < RAS_TABLE_VER_V3) && - !amdgpu_ras_smu_eeprom_supported(adev)) { - for (i = 0; i < control->ras_num_recs; i++) { - if ((control->ras_num_recs - i) >= adev->umc.retire_unit) { - if ((bps[i].address == bps[i + 1].address) && - (bps[i].mem_channel == bps[i + 1].mem_channel)) { - control->ras_num_pa_recs += adev->umc.retire_unit; - i += (adev->umc.retire_unit - 1); - } else { - control->ras_num_mca_recs += - (control->ras_num_recs - i); - break; - } - } else { - control->ras_num_mca_recs += (control->ras_num_recs - i); - break; - } - } - } else { - control->ras_num_mca_recs = control->ras_num_recs; - } - } - ret = amdgpu_ras_add_bad_pages(adev, bps, control->ras_num_recs, true); if (ret) goto out; @@ -3431,9 +3215,6 @@ int amdgpu_ras_init_badpage_info(struct amdgpu_device *adev) ret = amdgpu_ras_eeprom_init(control); control->is_eeprom_valid = !ret; - if (!adev->umc.ras || !adev->umc.ras->convert_ras_err_addr) - control->ras_num_pa_recs = control->ras_num_recs; - if (adev->umc.ras && adev->umc.ras->get_retire_flip_bits) adev->umc.ras->get_retire_flip_bits(adev); @@ -3453,13 +3234,6 @@ int amdgpu_ras_init_badpage_info(struct amdgpu_device *adev) adev, control->bad_channel_bitmap); con->update_channel_flag = false; } - - /* The format action is only applied to new ASICs */ - if (IP_VERSION_MAJ(amdgpu_ip_version(adev, UMC_HWIP, 0)) >= 12 && - control->tbl_hdr.version < RAS_TABLE_VER_V3) - if (!amdgpu_ras_eeprom_reset_table(control)) - if (amdgpu_ras_save_bad_pages(adev, NULL)) - dev_warn(adev->dev, "Failed to format RAS EEPROM data in V3 version!\n"); } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index 36f584f05e2f..292d76021644 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -665,7 +665,6 @@ amdgpu_ras_eeprom_append_table(struct amdgpu_ras_eeprom_control *control, const u32 num) { struct amdgpu_ras *con = amdgpu_ras_get_context(to_amdgpu_device(control)); - struct amdgpu_device *adev = to_amdgpu_device(control); u32 a, b, i; u8 *buf, *pp; int res; @@ -770,10 +769,7 @@ amdgpu_ras_eeprom_append_table(struct amdgpu_ras_eeprom_control *control, % control->ras_max_record_count; /*old asics only save pa to eeprom like before*/ - if (IP_VERSION_MAJ(amdgpu_ip_version(adev, UMC_HWIP, 0)) < 12) - control->ras_num_pa_recs += num; - else - control->ras_num_mca_recs += num; + control->ras_num_pa_recs += num; control->ras_num_bad_pages = con->bad_page_num; Out: diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index 26c39437dc8c..a9a32ba8d308 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -501,43 +501,3 @@ int amdgpu_umc_loop_channels(struct amdgpu_device *adev, return 0; } - -int amdgpu_umc_pages_in_a_row(struct amdgpu_device *adev, - struct ras_err_data *err_data, uint64_t pa_addr) -{ - struct ta_ras_query_address_output addr_out; - - /* reinit err_data */ - err_data->err_addr_cnt = 0; - err_data->err_addr_len = adev->umc.retire_unit; - - addr_out.pa.pa = pa_addr; - if (adev->umc.ras && adev->umc.ras->convert_ras_err_addr) - return adev->umc.ras->convert_ras_err_addr(adev, err_data, NULL, - &addr_out, false); - else - return -EINVAL; -} - -int amdgpu_umc_pa2mca(struct amdgpu_device *adev, - uint64_t pa, uint64_t *mca, enum amdgpu_memory_partition nps) -{ - struct ta_ras_query_address_input addr_in; - struct ta_ras_query_address_output addr_out; - int ret; - - /* nps: the pa belongs to */ - addr_in.pa.pa = pa | ((uint64_t)nps << 58); - addr_in.addr_type = TA_RAS_PA_TO_MCA; - ret = psp_ras_query_address(&adev->psp, &addr_in, &addr_out); - if (ret) { - dev_warn(adev->dev, "Failed to query RAS MCA address for 0x%llx", - pa); - - return ret; - } - - *mca = addr_out.ma.err_addr; - - return 0; -} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h index f65f3e082c64..cdaee4a049c3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h @@ -103,13 +103,6 @@ struct amdgpu_umc_ras { void *ras_error_status); bool (*check_ecc_err_status)(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, void *ras_error_status); - int (*convert_ras_err_addr)(struct amdgpu_device *adev, - struct ras_err_data *err_data, - struct ta_ras_query_address_input *addr_in, - struct ta_ras_query_address_output *addr_out, - bool dump_addr); - uint32_t (*get_die_id_from_pa)(struct amdgpu_device *adev, - uint64_t mca_addr, uint64_t retired_page); void (*get_retire_flip_bits)(struct amdgpu_device *adev); void (*mca_ipid_parse)(struct amdgpu_device *adev, uint64_t ipid, uint32_t *did, uint32_t *ch, uint32_t *umc_inst, uint32_t *sid); @@ -179,10 +172,4 @@ int amdgpu_umc_loop_channels(struct amdgpu_device *adev, void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, void *ras_error_status); -int amdgpu_umc_pages_in_a_row(struct amdgpu_device *adev, - struct ras_err_data *err_data, uint64_t pa_addr); -int amdgpu_umc_lookup_bad_pages_in_a_row(struct amdgpu_device *adev, - uint64_t pa_addr, uint64_t *pfns, int len); -int amdgpu_umc_pa2mca(struct amdgpu_device *adev, - uint64_t pa, uint64_t *mca, enum amdgpu_memory_partition nps); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index 4d6197c0efb1..beb89b0f9f3e 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -197,31 +197,6 @@ static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, return false; } -static uint32_t umc_v12_0_get_die_id(struct amdgpu_device *adev, - uint64_t mca_addr, uint64_t retired_page) -{ - uint32_t die = 0; - - /* we only calculate die id for nps1 mode right now */ - die += ((((retired_page >> 12) & 0x1ULL)^ - ((retired_page >> 20) & 0x1ULL) ^ - ((retired_page >> 27) & 0x1ULL) ^ - ((retired_page >> 34) & 0x1ULL) ^ - ((retired_page >> 41) & 0x1ULL)) << 0); - - /* the original PA_C4 and PA_R13 may be cleared in retired_page, so - * get them from mca_addr. - */ - die += ((((retired_page >> 13) & 0x1ULL) ^ - ((mca_addr >> 5) & 0x1ULL) ^ - ((retired_page >> 28) & 0x1ULL) ^ - ((mca_addr >> 23) & 0x1ULL) ^ - ((retired_page >> 42) & 0x1ULL)) << 1); - die &= 3; - - return die; -} - static void umc_v12_0_mca_ipid_parse(struct amdgpu_device *adev, uint64_t ipid, uint32_t *did, uint32_t *ch, uint32_t *umc_inst, uint32_t *sid) { @@ -240,7 +215,6 @@ struct amdgpu_umc_ras umc_v12_0_ras = { .hw_ops = NULL, }, .check_ecc_err_status = umc_v12_0_check_ecc_err_status, - .get_die_id_from_pa = umc_v12_0_get_die_id, .get_retire_flip_bits = umc_v12_0_get_retire_flip_bits, .mca_ipid_parse = umc_v12_0_mca_ipid_parse, }; -- cgit v1.2.3 From fe3ede5cdac00fe17a6fdee3e0447835c03a913e Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Tue, 24 Mar 2026 11:29:52 +0800 Subject: drm/amdgpu: retire legacy PMFW eeprom RAS bad page handling retire legacy PMFW eeprom RAS bad page handling Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 33 ++------------------------ 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index 292d76021644..d28e8958b0ff 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -916,33 +916,6 @@ int amdgpu_ras_eeprom_update_record_num(struct amdgpu_ras_eeprom_control *contro return ret; } -static int amdgpu_ras_smu_eeprom_append(struct amdgpu_ras_eeprom_control *control) -{ - struct amdgpu_device *adev = to_amdgpu_device(control); - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - - if (!amdgpu_ras_smu_eeprom_supported(adev) || !con) - return 0; - - control->ras_num_bad_pages = con->bad_page_num; - - if (amdgpu_bad_page_threshold != 0 && - control->ras_num_bad_pages > con->bad_page_cnt_threshold) { - dev_warn(adev->dev, - "Saved bad pages %d reaches threshold value %d\n", - control->ras_num_bad_pages, con->bad_page_cnt_threshold); - - if (adev->cper.enabled && amdgpu_cper_generate_bp_threshold_record(adev)) - dev_warn(adev->dev, "fail to generate bad page threshold cper records\n"); - - if ((amdgpu_bad_page_threshold != -1) && - (amdgpu_bad_page_threshold != -2)) - con->is_rma = true; - } - - return 0; -} - /** * amdgpu_ras_eeprom_append -- append records to the EEPROM RAS table * @control: pointer to control structure @@ -961,15 +934,13 @@ int amdgpu_ras_eeprom_append(struct amdgpu_ras_eeprom_control *control, const u32 num) { struct amdgpu_device *adev = to_amdgpu_device(control); + struct amdgpu_ras *con = amdgpu_ras_get_context(adev); int res, i; uint64_t nps = AMDGPU_NPS1_PARTITION_MODE; - if (!__is_ras_eeprom_supported(adev)) + if (!__is_ras_eeprom_supported(adev) || !con) return 0; - if (amdgpu_ras_smu_eeprom_supported(adev)) - return amdgpu_ras_smu_eeprom_append(control); - if (num == 0) { dev_err(adev->dev, "will not append 0 records\n"); return -EINVAL; -- cgit v1.2.3 From 33f0bfcf1683527715ae8ed72a31203b963d47cf Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 3 Jun 2026 16:06:23 +0800 Subject: drm/amdgpu: retire legacy PMFW bad page loading in page Remove the legacy logic that loads RAS bad pages from PMFW during page retirement Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 7 +- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 130 +++++++++++++++----------------- 2 files changed, 60 insertions(+), 77 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index bb83b7396881..148bb4cb0a2d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -3045,12 +3045,7 @@ int amdgpu_ras_save_bad_pages(struct amdgpu_device *adev, mutex_lock(&con->recovery_lock); control = &con->eeprom_control; data = con->eh_data; - if (amdgpu_ras_smu_eeprom_supported(adev)) - unit_num = control->ras_num_recs - - control->ras_num_recs_old; - else - unit_num = data->count / adev->umc.retire_unit - - control->ras_num_recs; + unit_num = data->count / adev->umc.retire_unit - control->ras_num_recs; save_count = con->bad_page_num - control->ras_num_bad_pages; mutex_unlock(&con->recovery_lock); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index a9a32ba8d308..2a5f5e6188bb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -97,7 +97,6 @@ void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, { struct ras_err_data *err_data = (struct ras_err_data *)ras_error_status; struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - struct amdgpu_ras_eeprom_control *control = &con->eeprom_control; unsigned int error_query_mode; int ret = 0; unsigned long err_count; @@ -118,77 +117,66 @@ void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, err_data->err_addr_len = adev->umc.max_ras_err_cnt_per_query; mutex_lock(&con->page_retirement_lock); - if (!amdgpu_ras_smu_eeprom_supported(adev)) { - ret = amdgpu_dpm_get_ecc_info(adev, (void *)&(con->umc_ecc)); - if (ret == -EOPNOTSUPP && - error_query_mode == AMDGPU_RAS_DIRECT_ERROR_QUERY) { - if (adev->umc.ras && adev->umc.ras->ras_block.hw_ops && - adev->umc.ras->ras_block.hw_ops->query_ras_error_count) - adev->umc.ras->ras_block.hw_ops->query_ras_error_count(adev, - ras_error_status); - - if (adev->umc.ras && adev->umc.ras->ras_block.hw_ops && - adev->umc.ras->ras_block.hw_ops->query_ras_error_address && - adev->umc.max_ras_err_cnt_per_query) { - kfree(err_data->err_addr); - err_data->err_addr = - kzalloc_objs(struct eeprom_table_record, - adev->umc.max_ras_err_cnt_per_query); - - /* still call query_ras_error_address to clear error status - * even NOMEM error is encountered - */ - if (!err_data->err_addr) - dev_warn(adev->dev, - "Failed to alloc memory for umc error address record!\n"); - else - err_data->err_addr_len = - adev->umc.max_ras_err_cnt_per_query; - - /* umc query_ras_error_address is also responsible for clearing - * error status - */ - adev->umc.ras->ras_block.hw_ops->query_ras_error_address(adev, - ras_error_status); - } - } else if (error_query_mode == AMDGPU_RAS_FIRMWARE_ERROR_QUERY || - (!ret && error_query_mode == AMDGPU_RAS_DIRECT_ERROR_QUERY)) { - if (adev->umc.ras && - adev->umc.ras->ecc_info_query_ras_error_count) - adev->umc.ras->ecc_info_query_ras_error_count(adev, - ras_error_status); - - if (adev->umc.ras && - adev->umc.ras->ecc_info_query_ras_error_address && - adev->umc.max_ras_err_cnt_per_query) { - kfree(err_data->err_addr); - err_data->err_addr = - kzalloc_objs(struct eeprom_table_record, - adev->umc.max_ras_err_cnt_per_query); - - /* still call query_ras_error_address to clear error status - * even NOMEM error is encountered - */ - if (!err_data->err_addr) - dev_warn(adev->dev, - "Failed to alloc memory for umc error address record!\n"); - else - err_data->err_addr_len = - adev->umc.max_ras_err_cnt_per_query; - - /* umc query_ras_error_address is also responsible for clearing - * error status - */ - adev->umc.ras->ecc_info_query_ras_error_address(adev, - ras_error_status); - } + ret = amdgpu_dpm_get_ecc_info(adev, (void *)&(con->umc_ecc)); + if (ret == -EOPNOTSUPP && + error_query_mode == AMDGPU_RAS_DIRECT_ERROR_QUERY) { + if (adev->umc.ras && adev->umc.ras->ras_block.hw_ops && + adev->umc.ras->ras_block.hw_ops->query_ras_error_count) + adev->umc.ras->ras_block.hw_ops->query_ras_error_count(adev, + ras_error_status); + + if (adev->umc.ras && adev->umc.ras->ras_block.hw_ops && + adev->umc.ras->ras_block.hw_ops->query_ras_error_address && + adev->umc.max_ras_err_cnt_per_query) { + err_data->err_addr = + kzalloc_objs(struct eeprom_table_record, + adev->umc.max_ras_err_cnt_per_query); + + /* still call query_ras_error_address to clear error status + * even NOMEM error is encountered + */ + if (!err_data->err_addr) + dev_warn(adev->dev, + "Failed to alloc memory for umc error address record!\n"); + else + err_data->err_addr_len = + adev->umc.max_ras_err_cnt_per_query; + + /* umc query_ras_error_address is also responsible for clearing + * error status + */ + adev->umc.ras->ras_block.hw_ops->query_ras_error_address(adev, + ras_error_status); } - } else { - if (!amdgpu_ras_eeprom_update_record_num(control)) { - err_data->err_addr_cnt = err_data->de_count = - control->ras_num_recs - control->ras_num_recs_old; - amdgpu_ras_eeprom_read_idx(control, err_data->err_addr, - control->ras_num_recs_old, err_data->de_count); + } else if (error_query_mode == AMDGPU_RAS_FIRMWARE_ERROR_QUERY || + (!ret && error_query_mode == AMDGPU_RAS_DIRECT_ERROR_QUERY)) { + if (adev->umc.ras && + adev->umc.ras->ecc_info_query_ras_error_count) + adev->umc.ras->ecc_info_query_ras_error_count(adev, + ras_error_status); + + if (adev->umc.ras && + adev->umc.ras->ecc_info_query_ras_error_address && + adev->umc.max_ras_err_cnt_per_query) { + err_data->err_addr = + kcalloc(adev->umc.max_ras_err_cnt_per_query, + sizeof(struct eeprom_table_record), GFP_KERNEL); + + /* still call query_ras_error_address to clear error status + * even NOMEM error is encountered + */ + if (!err_data->err_addr) + dev_warn(adev->dev, + "Failed to alloc memory for umc error address record!\n"); + else + err_data->err_addr_len = + adev->umc.max_ras_err_cnt_per_query; + + /* umc query_ras_error_address is also responsible for clearing + * error status + */ + adev->umc.ras->ecc_info_query_ras_error_address(adev, + ras_error_status); } } -- cgit v1.2.3 From 0b2fa33b4235991a100dd799c891cf5c242aaed1 Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Fri, 29 May 2026 17:30:50 +0200 Subject: drm/amdgpu: Only set bo->moved when the BO was actually moved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "moved" VM state is a bit unfortunately named, because BOs can end up in this state without being physically moved. While we need to invalidate every mapping when BOs are physically moved, in some other cases like PRT binds/unbinds there is no need to refresh mappings except those affected by the bind. Full invalidation of all BO mappings manifested as severe regressions in PRT bind performance, which this patch fixes. The offending patch is 4cdbba5a16aa ("drm/amdgpu: restructure VM state machine v4") in the amd-staging-drm-next tree, although it has not yet propagated anywhere else. Fixes: 4cdbba5a16aa ("drm/amdgpu: restructure VM state machine v4") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5437 Signed-off-by: Natalie Vock Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index fee4c94c2585..3f3369d427a1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -232,7 +232,6 @@ static void amdgpu_vm_bo_moved(struct amdgpu_vm_bo_base *vm_bo) vm_bo->moved = false; list_move(&vm_bo->vm_status, &lists->idle); } else { - vm_bo->moved = true; list_move(&vm_bo->vm_status, &lists->moved); } amdgpu_vm_bo_unlock_lists(vm_bo); @@ -608,6 +607,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm, return r; vm->update_funcs->map_table(to_amdgpu_bo_vm(bo_base->bo)); + bo_base->moved = true; amdgpu_vm_bo_moved(bo_base); } @@ -625,6 +625,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm, if (r) return r; + bo_base->moved = true; amdgpu_vm_bo_moved(bo_base); } @@ -645,6 +646,7 @@ restart: if (r) return r; + bo_base->moved = true; amdgpu_vm_bo_moved(bo_base); /* It's a bit inefficient to always jump back to the start, but @@ -2284,6 +2286,7 @@ void amdgpu_vm_bo_invalidate(struct amdgpu_bo *bo, bool evicted) if (bo_base->moved) continue; + bo_base->moved = true; amdgpu_vm_bo_moved(bo_base); } } -- cgit v1.2.3 From 1f7a795fb9f8186bd81ca9c4a80f75482db53c9e Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Fri, 29 May 2026 17:30:51 +0200 Subject: drm/amdgpu: Rename moved state to needs_update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This state can be reached via other means than physical moves, like PRT bindings. Make the name match the actual purpose of the state. Signed-off-by: Natalie Vock Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 53 +++++++++++++++++----------------- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h | 9 +++--- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 4ad8f1c31e55..d777375e5350 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -1315,7 +1315,7 @@ static int amdgpu_cs_submit(struct amdgpu_cs_parser *p, e->range = NULL; } - if (r || !list_empty(&vm->individual.moved)) { + if (r || !list_empty(&vm->individual.needs_update)) { r = -EAGAIN; mutex_unlock(&p->adev->notifier_lock); return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 3f3369d427a1..f317f888b59f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -142,7 +142,7 @@ static void amdgpu_vm_assert_locked(struct amdgpu_vm *vm) static void amdgpu_vm_bo_status_init(struct amdgpu_vm_bo_status *lists) { INIT_LIST_HEAD(&lists->evicted); - INIT_LIST_HEAD(&lists->moved); + INIT_LIST_HEAD(&lists->needs_update); INIT_LIST_HEAD(&lists->idle); } @@ -211,14 +211,14 @@ static void amdgpu_vm_bo_evicted(struct amdgpu_vm_bo_base *vm_bo) amdgpu_vm_bo_unlock_lists(vm_bo); } /** - * amdgpu_vm_bo_moved - vm_bo is moved + * amdgpu_vm_bo_needs_update - vm_bo needs pagetable update * - * @vm_bo: vm_bo which is moved + * @vm_bo: vm_bo which is out of date * - * State for vm_bo objects meaning the underlying BO was moved but the new - * location not yet reflected in the page tables. + * State for vm_bo objects meaning the underlying BO had mapping changes (move, PRT bind/unbind) + * but the new location is not yet reflected in the page tables. */ -static void amdgpu_vm_bo_moved(struct amdgpu_vm_bo_base *vm_bo) +static void amdgpu_vm_bo_needs_update(struct amdgpu_vm_bo_base *vm_bo) { struct amdgpu_vm_bo_status *lists; struct amdgpu_bo *bo = vm_bo->bo; @@ -232,7 +232,7 @@ static void amdgpu_vm_bo_moved(struct amdgpu_vm_bo_base *vm_bo) vm_bo->moved = false; list_move(&vm_bo->vm_status, &lists->idle); } else { - list_move(&vm_bo->vm_status, &lists->moved); + list_move(&vm_bo->vm_status, &lists->needs_update); } amdgpu_vm_bo_unlock_lists(vm_bo); } @@ -273,14 +273,14 @@ static void amdgpu_vm_bo_reset_state_machine(struct amdgpu_vm *vm) */ amdgpu_vm_assert_locked(vm); list_for_each_entry_safe(vm_bo, tmp, &vm->kernel.idle, vm_status) - amdgpu_vm_bo_moved(vm_bo); + amdgpu_vm_bo_needs_update(vm_bo); list_for_each_entry_safe(vm_bo, tmp, &vm->always_valid.idle, vm_status) - amdgpu_vm_bo_moved(vm_bo); + amdgpu_vm_bo_needs_update(vm_bo); spin_lock(&vm->individual_lock); list_for_each_entry_safe(vm_bo, tmp, &vm->individual.idle, vm_status) { vm_bo->moved = true; - list_move(&vm_bo->vm_status, &vm->individual.moved); + list_move(&vm_bo->vm_status, &vm->individual.needs_update); } spin_unlock(&vm->individual_lock); } @@ -435,7 +435,7 @@ void amdgpu_vm_bo_base_init(struct amdgpu_vm_bo_base *base, */ if (bo->preferred_domains & amdgpu_mem_type_to_domain(bo->tbo.resource->mem_type)) - amdgpu_vm_bo_moved(base); + amdgpu_vm_bo_needs_update(base); else amdgpu_vm_bo_evicted(base); } @@ -608,7 +608,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm, vm->update_funcs->map_table(to_amdgpu_bo_vm(bo_base->bo)); bo_base->moved = true; - amdgpu_vm_bo_moved(bo_base); + amdgpu_vm_bo_needs_update(bo_base); } /* @@ -626,7 +626,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm, return r; bo_base->moved = true; - amdgpu_vm_bo_moved(bo_base); + amdgpu_vm_bo_needs_update(bo_base); } if (!ticket) @@ -647,7 +647,7 @@ restart: return r; bo_base->moved = true; - amdgpu_vm_bo_moved(bo_base); + amdgpu_vm_bo_needs_update(bo_base); /* It's a bit inefficient to always jump back to the start, but * we would need to re-structure the KFD for properly fixing @@ -981,7 +981,7 @@ int amdgpu_vm_update_pdes(struct amdgpu_device *adev, amdgpu_vm_assert_locked(vm); - if (list_empty(&vm->kernel.moved)) + if (list_empty(&vm->kernel.needs_update)) return 0; if (!drm_dev_enter(adev_to_drm(adev), &idx)) @@ -997,7 +997,7 @@ int amdgpu_vm_update_pdes(struct amdgpu_device *adev, if (r) goto error; - list_for_each_entry(entry, &vm->kernel.moved, vm_status) { + list_for_each_entry(entry, &vm->kernel.needs_update, vm_status) { /* vm_flush_needed after updating moved PDEs */ flush_tlb_needed |= entry->moved; @@ -1013,7 +1013,8 @@ int amdgpu_vm_update_pdes(struct amdgpu_device *adev, if (flush_tlb_needed) atomic64_inc(&vm->tlb_seq); - list_for_each_entry_safe(entry, tmp, &vm->kernel.moved, vm_status) + list_for_each_entry_safe(entry, tmp, &vm->kernel.needs_update, + vm_status) amdgpu_vm_bo_idle(entry); error: @@ -1617,7 +1618,7 @@ int amdgpu_vm_handle_moved(struct amdgpu_device *adev, bool clear, unlock; int r; - list_for_each_entry_safe(bo_va, tmp, &vm->always_valid.moved, + list_for_each_entry_safe(bo_va, tmp, &vm->always_valid.needs_update, base.vm_status) { /* Per VM BOs never need to bo cleared in the page tables */ r = amdgpu_vm_bo_update(adev, bo_va, false); @@ -1626,8 +1627,8 @@ int amdgpu_vm_handle_moved(struct amdgpu_device *adev, } spin_lock(&vm->individual_lock); - while (!list_empty(&vm->individual.moved)) { - bo_va = list_first_entry(&vm->individual.moved, + while (!list_empty(&vm->individual.needs_update)) { + bo_va = list_first_entry(&vm->individual.needs_update, typeof(*bo_va), base.vm_status); bo = bo_va->base.bo; resv = bo->tbo.base.resv; @@ -1788,7 +1789,7 @@ static void amdgpu_vm_bo_insert_map(struct amdgpu_device *adev, amdgpu_vm_prt_get(adev); if (amdgpu_vm_is_bo_always_valid(vm, bo) && !bo_va->base.moved) - amdgpu_vm_bo_moved(&bo_va->base); + amdgpu_vm_bo_needs_update(&bo_va->base); trace_amdgpu_vm_bo_map(bo_va, mapping); } @@ -2097,7 +2098,7 @@ int amdgpu_vm_bo_clear_mappings(struct amdgpu_device *adev, if (amdgpu_vm_is_bo_always_valid(vm, bo) && !before->bo_va->base.moved) - amdgpu_vm_bo_moved(&before->bo_va->base); + amdgpu_vm_bo_needs_update(&before->bo_va->base); } else { kfree(before); } @@ -2112,7 +2113,7 @@ int amdgpu_vm_bo_clear_mappings(struct amdgpu_device *adev, if (amdgpu_vm_is_bo_always_valid(vm, bo) && !after->bo_va->base.moved) - amdgpu_vm_bo_moved(&after->bo_va->base); + amdgpu_vm_bo_needs_update(&after->bo_va->base); } else { kfree(after); } @@ -2287,7 +2288,7 @@ void amdgpu_vm_bo_invalidate(struct amdgpu_bo *bo, bool evicted) if (bo_base->moved) continue; bo_base->moved = true; - amdgpu_vm_bo_moved(bo_base); + amdgpu_vm_bo_needs_update(bo_base); } } @@ -3101,7 +3102,7 @@ static void amdgpu_debugfs_vm_bo_status_info(struct seq_file *m, id = 0; seq_puts(m, "\tMoved BOs:\n"); - list_for_each_entry(base, &lists->moved, vm_status) { + list_for_each_entry(base, &lists->needs_update, vm_status) { if (!base->bo) continue; @@ -3110,7 +3111,7 @@ static void amdgpu_debugfs_vm_bo_status_info(struct seq_file *m, id = 0; seq_puts(m, "\tIdle BOs:\n"); - list_for_each_entry(base, &lists->moved, vm_status) { + list_for_each_entry(base, &lists->needs_update, vm_status) { if (!base->bo) continue; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h index b32f51a78cd8..5822836fa4a3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h @@ -212,7 +212,8 @@ struct amdgpu_vm_bo_base { * protected by vm BO being reserved */ bool shared; - /* protected by the BO being reserved */ + /* if the BO was moved and all mappings are invalid + * protected by the BO being reserved */ bool moved; }; @@ -220,14 +221,14 @@ struct amdgpu_vm_bo_base { * The following status lists contain amdgpu_vm_bo_base objects for * either PD/PTs, per VM BOs or BOs with individual resv object. * - * The state transits are: evicted -> moved -> idle + * The state transits are: evicted -> needs_update -> idle */ struct amdgpu_vm_bo_status { /* BOs evicted which need to move into place again */ struct list_head evicted; - /* BOs which moved but new location hasn't been updated in the PDs/PTs */ - struct list_head moved; + /* BOs whose mappings changed but PDs/PTs haven't been updated */ + struct list_head needs_update; /* BOs done with the state machine and need no further action */ struct list_head idle; -- cgit v1.2.3 From 30af09db33696f7e0de5c0c505cbb0cb92b6e25b Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Thu, 25 Jun 2026 10:31:00 +0800 Subject: drm/amdgpu/mes11: set doorbell offset for suspending userq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updating the union MESAPI__SUSPEND and union MESAPI__RESUME to add the doorbell offset for suspending userq. Signed-off-by: Prike Liang Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 2 ++ drivers/gpu/drm/amd/include/mes_v11_api_def.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 76e6769cf7ac..2c2df80c1ffc 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -785,6 +785,7 @@ static int mes_v11_0_suspend_gang(struct amdgpu_mes *mes, mes_suspend_gang_pkt.gang_context_addr = input->gang_context_addr; mes_suspend_gang_pkt.suspend_fence_addr = input->suspend_fence_addr; mes_suspend_gang_pkt.suspend_fence_value = input->suspend_fence_value; + mes_suspend_gang_pkt.doorbell_offset = input->doorbell_offset; return mes_v11_0_submit_pkt_and_poll_completion(mes, &mes_suspend_gang_pkt, sizeof(mes_suspend_gang_pkt), @@ -804,6 +805,7 @@ static int mes_v11_0_resume_gang(struct amdgpu_mes *mes, mes_resume_gang_pkt.resume_all_gangs = input->resume_all_gangs; mes_resume_gang_pkt.gang_context_addr = input->gang_context_addr; + mes_resume_gang_pkt.doorbell_offset = input->doorbell_offset; return mes_v11_0_submit_pkt_and_poll_completion(mes, &mes_resume_gang_pkt, sizeof(mes_resume_gang_pkt), diff --git a/drivers/gpu/drm/amd/include/mes_v11_api_def.h b/drivers/gpu/drm/amd/include/mes_v11_api_def.h index 6644fabeb0b7..b06412ac8583 100644 --- a/drivers/gpu/drm/amd/include/mes_v11_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v11_api_def.h @@ -428,6 +428,7 @@ union MESAPI__SUSPEND { uint32_t suspend_fence_value; struct MES_API_STATUS api_status; + uint32_t doorbell_offset; }; uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; @@ -445,6 +446,7 @@ union MESAPI__RESUME { uint64_t gang_context_addr; struct MES_API_STATUS api_status; + uint32_t doorbell_offset; }; uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; -- cgit v1.2.3 From 5b58a2c120063544869d0284d3b355527f9f04f5 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Thu, 25 Jun 2026 10:42:27 +0800 Subject: drm/amdgpu/mes12: set doorbell offset for suspending userq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updating the union MESAPI__SUSPEND and union MESAPI__RESUME to add the doorbell offset for suspending userq. Signed-off-by: Prike Liang Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/mes_v12_1.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 1b0c649d97a2..ce5064200743 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -794,6 +794,7 @@ static int mes_v12_0_suspend_gang(struct amdgpu_mes *mes, mes_suspend_gang_pkt.gang_context_addr = input->gang_context_addr; mes_suspend_gang_pkt.suspend_fence_addr = input->suspend_fence_addr; mes_suspend_gang_pkt.suspend_fence_value = input->suspend_fence_value; + mes_suspend_gang_pkt.doorbell_offset = input->doorbell_offset; return mes_v12_0_submit_pkt_and_poll_completion(mes, AMDGPU_MES_SCHED_PIPE, &mes_suspend_gang_pkt, sizeof(mes_suspend_gang_pkt), @@ -813,6 +814,7 @@ static int mes_v12_0_resume_gang(struct amdgpu_mes *mes, mes_resume_gang_pkt.resume_all_gangs = input->resume_all_gangs; mes_resume_gang_pkt.gang_context_addr = input->gang_context_addr; + mes_resume_gang_pkt.doorbell_offset = input->doorbell_offset; return mes_v12_0_submit_pkt_and_poll_completion(mes, AMDGPU_MES_SCHED_PIPE, &mes_resume_gang_pkt, sizeof(mes_resume_gang_pkt), diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c index c449efa70b60..f7d5879c6e44 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c @@ -496,6 +496,7 @@ static int mes_v12_1_suspend_gang(struct amdgpu_mes *mes, mes_suspend_gang_pkt.gang_context_addr = input->gang_context_addr; mes_suspend_gang_pkt.suspend_fence_addr = input->suspend_fence_addr; mes_suspend_gang_pkt.suspend_fence_value = input->suspend_fence_value; + mes_suspend_gang_pkt.doorbell_offset = input->doorbell_offset; /* Suspend gang is handled by master MES */ return mes_v12_1_submit_pkt_and_poll_completion(mes, input->xcc_id, AMDGPU_MES_SCHED_PIPE, @@ -516,6 +517,7 @@ static int mes_v12_1_resume_gang(struct amdgpu_mes *mes, mes_resume_gang_pkt.resume_all_gangs = input->resume_all_gangs; mes_resume_gang_pkt.gang_context_addr = input->gang_context_addr; + mes_resume_gang_pkt.doorbell_offset = input->doorbell_offset; /* Resume gang is handled by master MES */ return mes_v12_1_submit_pkt_and_poll_completion(mes, input->xcc_id, AMDGPU_MES_SCHED_PIPE, -- cgit v1.2.3 From 16c231ff4f4fe49b28ed60b8d42742d2be6e339b Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 10:50:37 +0800 Subject: drm/amdgpu: retire legacy PMFW RAS eeprom write skip Remove the legacy logic that skips eeprom writes for PMFW-managed RAS data Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 43 +------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.h | 3 -- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index d28e8958b0ff..80de2459c76a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -124,8 +124,6 @@ RAS_TABLE_V2_1_INFO_SIZE) \ / RAS_TABLE_RECORD_SIZE) -#define RAS_SMU_MESSAGE_TIMEOUT_MS 1000 /* 1s */ - /* Given a zero-based index of an EEPROM RAS record, yields the EEPROM * offset off of RAS_TABLE_START. That is, this is something you can * add to control->i2c_address, and then tell I2C layer to read @@ -878,44 +876,6 @@ Out: return res; } -int amdgpu_ras_eeprom_update_record_num(struct amdgpu_ras_eeprom_control *control) -{ - struct amdgpu_device *adev = to_amdgpu_device(control); - int ret, retry = 20; - - if (!amdgpu_ras_smu_eeprom_supported(adev)) - return 0; - - control->ras_num_recs_old = control->ras_num_recs; - - do { - /* 1000ms timeout is long enough, smu_get_badpage_count won't - * return -EBUSY before timeout. - */ - ret = amdgpu_ras_smu_get_badpage_count(adev, - &(control->ras_num_recs), RAS_SMU_MESSAGE_TIMEOUT_MS); - if (!ret && - (control->ras_num_recs_old == control->ras_num_recs)) { - /* record number update in PMFW needs some time, - * smu_get_badpage_count may return immediately without - * count update, sleep for a while and retry again. - */ - msleep(50); - retry--; - } else { - break; - } - } while (retry); - - /* no update of record number is not a real failure, - * don't print warning here - */ - if (!ret && (control->ras_num_recs_old == control->ras_num_recs)) - ret = -EINVAL; - - return ret; -} - /** * amdgpu_ras_eeprom_append -- append records to the EEPROM RAS table * @control: pointer to control structure @@ -934,11 +894,10 @@ int amdgpu_ras_eeprom_append(struct amdgpu_ras_eeprom_control *control, const u32 num) { struct amdgpu_device *adev = to_amdgpu_device(control); - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); int res, i; uint64_t nps = AMDGPU_NPS1_PARTITION_MODE; - if (!__is_ras_eeprom_supported(adev) || !con) + if (!__is_ras_eeprom_supported(adev)) return 0; if (num == 0) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.h index a62114800a92..3c7fcce5fe8b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.h @@ -82,7 +82,6 @@ struct amdgpu_ras_eeprom_control { /* Number of records in the table. */ u32 ras_num_recs; - u32 ras_num_recs_old; /* the bad page number is ras_num_recs or * ras_num_recs * umc.retire_unit @@ -191,8 +190,6 @@ int amdgpu_ras_eeprom_read_idx(struct amdgpu_ras_eeprom_control *control, struct eeprom_table_record *record, u32 rec_idx, const u32 num); -int amdgpu_ras_eeprom_update_record_num(struct amdgpu_ras_eeprom_control *control); - void amdgpu_ras_check_bad_page_status(struct amdgpu_device *adev); extern const struct file_operations amdgpu_ras_debugfs_eeprom_size_ops; -- cgit v1.2.3 From cf591e67c095542a16475df293ec7bc9a118e4ee Mon Sep 17 00:00:00 2001 From: Granthali Vinodkumar Dhandar Date: Wed, 17 Jun 2026 17:39:58 +0530 Subject: drm/amdgpu: add support for GC IP version 11.7.0 Initialize GC IP 11_7_0 Signed-off-by: Granthali Vinodkumar Dhandar Reviewed-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 6 ++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 1 + drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 12 +++++++++++- drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/imu_v11_0.c | 1 + drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/soc21.c | 28 +++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_crat.c | 1 + drivers/gpu/drm/amd/amdkfd/kfd_device.c | 5 +++++ 9 files changed, 57 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 5605bc42ffc1..aa7b94414477 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -2344,6 +2344,7 @@ static int amdgpu_discovery_set_common_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): amdgpu_device_ip_block_add(adev, &soc21_common_ip_block); break; case IP_VERSION(12, 0, 0): @@ -2405,6 +2406,7 @@ static int amdgpu_discovery_set_gmc_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): amdgpu_device_ip_block_add(adev, &gmc_v11_0_ip_block); break; case IP_VERSION(12, 0, 0): @@ -2731,6 +2733,7 @@ static int amdgpu_discovery_set_gc_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): amdgpu_device_ip_block_add(adev, &gfx_v11_0_ip_block); break; case IP_VERSION(12, 0, 0): @@ -2949,6 +2952,7 @@ static int amdgpu_discovery_set_mes_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): amdgpu_device_ip_block_add(adev, &mes_v11_0_ip_block); adev->enable_mes = true; adev->enable_mes_kiq = true; @@ -3357,6 +3361,7 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): adev->family = AMDGPU_FAMILY_GC_11_5_0; break; case IP_VERSION(12, 0, 0): @@ -3386,6 +3391,7 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): adev->flags |= AMD_IS_APU; break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index aeda54ee2c9d..7a1709879393 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -977,6 +977,7 @@ void amdgpu_gmc_tmz_set(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): /* Don't enable it by default yet. */ if (amdgpu_tmz < 1) { diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index b08a0aa5e22b..1cf790ec0434 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -133,6 +133,10 @@ MODULE_FIRMWARE("amdgpu/gc_11_5_6_pfp.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_me.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_mec.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_rlc.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_pfp.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_me.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_mec.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_rlc.bin"); static const struct amdgpu_hwip_reg_entry gc_reg_list_11_0[] = { SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS), @@ -1128,6 +1132,7 @@ static int gfx_v11_0_gpu_early_init(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): adev->gfx.config.max_hw_contexts = 8; adev->gfx.config.sc_prim_fifo_size_frontend = 0x20; adev->gfx.config.sc_prim_fifo_size_backend = 0x100; @@ -1612,6 +1617,7 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): adev->gfx.me.num_me = 1; adev->gfx.me.num_pipe_per_me = 1; adev->gfx.me.num_queue_per_pipe = 2; @@ -3090,7 +3096,8 @@ static int gfx_v11_0_wait_for_rlc_autoload_complete(struct amdgpu_device *adev) amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 2) || amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 3) || amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 4) || - amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 6)) + amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 6) || + amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 7, 0)) bootload_status = RREG32_SOC15(GC, 0, regRLC_RLCS_BOOTLOAD_STATUS_gc_11_0_1); else @@ -5739,6 +5746,7 @@ static void gfx_v11_cntl_power_gating(struct amdgpu_device *adev, bool enable) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): WREG32_SOC15(GC, 0, regRLC_PG_DELAY_3, RLC_PG_DELAY_3_DEFAULT_GC_11_0_1); break; default: @@ -5779,6 +5787,7 @@ static int gfx_v11_0_set_powergating_state(struct amdgpu_ip_block *ip_block, case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): if (!enable) amdgpu_gfx_off_ctrl(adev, false); @@ -5815,6 +5824,7 @@ static int gfx_v11_0_set_clockgating_state(struct amdgpu_ip_block *ip_block, case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): gfx_v11_0_update_gfx_clock_gating(adev, state == AMD_CG_STATE_GATE); break; diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c index 8eb9847d9e1e..8a0a88551461 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c @@ -606,6 +606,7 @@ static void gmc_v11_0_set_gfxhub_funcs(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): adev->gfxhub.funcs = &gfxhub_v11_5_0_funcs; break; default: @@ -781,6 +782,7 @@ static int gmc_v11_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): set_bit(AMDGPU_GFXHUB(0), adev->vmhubs_mask); set_bit(AMDGPU_MMHUB0(0), adev->vmhubs_mask); /* diff --git a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c index f5927c3553ce..177d702e612a 100644 --- a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c @@ -43,6 +43,7 @@ MODULE_FIRMWARE("amdgpu/gc_11_5_2_imu.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_3_imu.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_4_imu.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_imu.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_imu.bin"); static int imu_v11_0_init_microcode(struct amdgpu_device *adev) { diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 2c2df80c1ffc..5f08fa1242a5 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -60,6 +60,8 @@ MODULE_FIRMWARE("amdgpu/gc_11_5_4_mes_2.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_4_mes1.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_mes_2.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_mes1.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_mes_2.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_0_mes1.bin"); static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block); static int mes_v11_0_hw_fini(struct amdgpu_ip_block *ip_block); diff --git a/drivers/gpu/drm/amd/amdgpu/soc21.c b/drivers/gpu/drm/amd/amdgpu/soc21.c index 223702e5c220..b07cd3bf787a 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc21.c +++ b/drivers/gpu/drm/amd/amdgpu/soc21.c @@ -826,6 +826,34 @@ static int soc21_common_early_init(struct amdgpu_ip_block *ip_block) adev->pg_flags = 0; adev->external_rev_id = adev->rev_id + 0xd0; break; + case IP_VERSION(11, 7, 0): + adev->cg_flags = AMD_CG_SUPPORT_VCN_MGCG | + AMD_CG_SUPPORT_JPEG_MGCG | + AMD_CG_SUPPORT_GFX_CGCG | + AMD_CG_SUPPORT_GFX_CGLS | + AMD_CG_SUPPORT_GFX_MGCG | + AMD_CG_SUPPORT_GFX_FGCG | + AMD_CG_SUPPORT_REPEATER_FGCG | + AMD_CG_SUPPORT_GFX_PERF_CLK | + AMD_CG_SUPPORT_GFX_3D_CGCG | + AMD_CG_SUPPORT_GFX_3D_CGLS | + AMD_CG_SUPPORT_MC_MGCG | + AMD_CG_SUPPORT_MC_LS | + AMD_CG_SUPPORT_HDP_LS | + AMD_CG_SUPPORT_HDP_DS | + AMD_CG_SUPPORT_HDP_SD | + AMD_CG_SUPPORT_ATHUB_MGCG | + AMD_CG_SUPPORT_ATHUB_LS | + AMD_CG_SUPPORT_IH_CG | + AMD_CG_SUPPORT_BIF_MGCG | + AMD_CG_SUPPORT_BIF_LS; + adev->pg_flags = AMD_PG_SUPPORT_VCN_DPG | + AMD_PG_SUPPORT_VCN | + AMD_PG_SUPPORT_JPEG_DPG | + AMD_PG_SUPPORT_JPEG | + AMD_PG_SUPPORT_GFX_PG; + adev->external_rev_id = adev->rev_id + 0xF; + break; default: /* FIXME: not supported yet */ return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c index f28259d13818..a6a7888c7a8d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c @@ -1715,6 +1715,7 @@ int kfd_get_gpu_cache_info(struct kfd_node *kdev, struct kfd_gpu_cache_info **pc case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): /* Cacheline size not available in IP discovery for gc11. * kfd_fill_gpu_cache_info_from_gfx_config to hard code it */ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device.c b/drivers/gpu/drm/amd/amdkfd/kfd_device.c index b40b6a566aae..882a23ce9431 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device.c @@ -169,6 +169,7 @@ static void kfd_device_info_set_event_interrupt_class(struct kfd_dev *kfd) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + case IP_VERSION(11, 7, 0): kfd->device_info.event_interrupt_class = &event_interrupt_class_v11; break; case IP_VERSION(12, 0, 0): @@ -451,6 +452,10 @@ struct kfd_dev *kgd2kfd_probe(struct amdgpu_device *adev, bool vf) gfx_target_version = 110504; f2g = &gfx_v11_kfd2kgd; break; + case IP_VERSION(11, 7, 0): + gfx_target_version = 110700; + f2g = &gfx_v11_kfd2kgd; + break; case IP_VERSION(12, 0, 0): gfx_target_version = 120000; f2g = &gfx_v12_kfd2kgd; -- cgit v1.2.3 From 683711c296a95ff2fa982386ecfb02d70d14941c Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 3 Jun 2026 16:12:57 +0800 Subject: drm/amdgpu: retire legacy ras_eeprom_read_idx interface Remove the legacy ras_eeprom_read_idx interface for PMFW-managed RAS eeprom Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 49 -------------------------- 1 file changed, 49 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index 80de2459c76a..9a9633b57022 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -975,52 +975,6 @@ static int __amdgpu_ras_eeprom_read(struct amdgpu_ras_eeprom_control *control, return res; } -int amdgpu_ras_eeprom_read_idx(struct amdgpu_ras_eeprom_control *control, - struct eeprom_table_record *record, u32 rec_idx, - const u32 num) -{ - struct amdgpu_device *adev = to_amdgpu_device(control); - uint64_t ts, end_idx; - int i, ret; - u64 mca, ipid; - u32 cu, mem_channel, mcumc_id; - - if (!amdgpu_ras_smu_eeprom_supported(adev)) - return 0; - - if (!adev->umc.ras || !adev->umc.ras->mca_ipid_parse) - return -EOPNOTSUPP; - - end_idx = rec_idx + num; - for (i = rec_idx; i < end_idx; i++) { - ret = amdgpu_ras_smu_get_badpage_mca_addr(adev, i, &mca); - if (ret) - return ret; - - ret = amdgpu_ras_smu_get_badpage_ipid(adev, i, &ipid); - if (ret) - return ret; - - ret = amdgpu_ras_smu_get_timestamp(adev, i, &ts); - if (ret) - return ret; - - record[i - rec_idx].address = mca; - /* retired_page (pa) is unused now */ - record[i - rec_idx].retired_page = 0x1ULL; - record[i - rec_idx].ts = ts; - record[i - rec_idx].err_type = AMDGPU_RAS_EEPROM_ERR_NON_RECOVERABLE; - - adev->umc.ras->mca_ipid_parse(adev, ipid, - &cu, &mem_channel, &mcumc_id, NULL); - record[i - rec_idx].cu = (u8)cu; - record[i - rec_idx].mem_channel = (u8)mem_channel; - record[i - rec_idx].mcumc_id = (u8)mcumc_id; - } - - return 0; -} - /** * amdgpu_ras_eeprom_read -- read EEPROM * @control: pointer to control structure @@ -1042,9 +996,6 @@ int amdgpu_ras_eeprom_read(struct amdgpu_ras_eeprom_control *control, u8 *buf, *pp; u32 g0, g1; - if (amdgpu_ras_smu_eeprom_supported(adev)) - return amdgpu_ras_eeprom_read_idx(control, record, 0, num); - if (!__is_ras_eeprom_supported(adev)) return 0; -- cgit v1.2.3 From 814cfcc36740616b2bd4890962bca9bfb5d9999d Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Tue, 24 Mar 2026 13:07:51 +0800 Subject: drm/amdgpu: retire legacy MCA IPID parse global interface Remove the legacy global MCA IPID parse interface Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h | 2 -- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h index cdaee4a049c3..cf06d5f856f9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h @@ -104,8 +104,6 @@ struct amdgpu_umc_ras { bool (*check_ecc_err_status)(struct amdgpu_device *adev, enum amdgpu_mca_error_type type, void *ras_error_status); void (*get_retire_flip_bits)(struct amdgpu_device *adev); - void (*mca_ipid_parse)(struct amdgpu_device *adev, uint64_t ipid, - uint32_t *did, uint32_t *ch, uint32_t *umc_inst, uint32_t *sid); }; struct amdgpu_umc_funcs { diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index beb89b0f9f3e..67bdf7303e6b 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -197,25 +197,11 @@ static bool umc_v12_0_check_ecc_err_status(struct amdgpu_device *adev, return false; } -static void umc_v12_0_mca_ipid_parse(struct amdgpu_device *adev, uint64_t ipid, - uint32_t *did, uint32_t *ch, uint32_t *umc_inst, uint32_t *sid) -{ - if (did) - *did = MCA_IPID_2_DIE_ID(ipid); - if (ch) - *ch = MCA_IPID_2_UMC_CH(ipid); - if (umc_inst) - *umc_inst = MCA_IPID_2_UMC_INST(ipid); - if (sid) - *sid = MCA_IPID_2_SOCKET_ID(ipid); -} - struct amdgpu_umc_ras umc_v12_0_ras = { .ras_block = { .hw_ops = NULL, }, .check_ecc_err_status = umc_v12_0_check_ecc_err_status, .get_retire_flip_bits = umc_v12_0_get_retire_flip_bits, - .mca_ipid_parse = umc_v12_0_mca_ipid_parse, }; -- cgit v1.2.3 From 4c032a556a82f436f2905c17fead5b8e148e3a04 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 23 Mar 2026 14:39:11 +0800 Subject: drm/amd/pm: retire legacy pmfw eeprom feature check retire legacy pmfw eeprom feature check Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c | 6 ------ drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c | 3 --- 2 files changed, 9 deletions(-) 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 688b863672bb..dea27fcb2b20 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 @@ -1137,16 +1137,10 @@ static const struct ras_eeprom_smu_funcs smu_v13_0_12_eeprom_smu_funcs = { static void smu_v13_0_12_ras_smu_feature_flags(struct amdgpu_device *adev, uint64_t *flags) { - struct smu_context *smu = adev->powerplay.pp_handle; - if (!flags) return; *flags = 0ULL; - - if (smu_v13_0_6_cap_supported(smu, SMU_CAP(RAS_EEPROM))) - *flags |= RAS_SMU_FEATURE_BIT__RAS_EEPROM; - } const struct ras_smu_drv smu_v13_0_12_ras_smu_drv = { 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 957c158c8e2a..334c92a28994 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 @@ -3281,9 +3281,6 @@ static int smu_v13_0_6_get_ras_smu_drv(struct smu_context *smu, const struct ras if (amdgpu_sriov_vf(smu->adev)) return -EOPNOTSUPP; - if (smu_cmn_feature_is_enabled(smu, SMU_FEATURE_HROM_EN_BIT)) - smu_v13_0_6_cap_set(smu, SMU_CAP(RAS_EEPROM)); - switch (amdgpu_ip_version(smu->adev, MP1_HWIP, 0)) { case IP_VERSION(13, 0, 12): *ras_smu_drv = &smu_v13_0_12_ras_smu_drv; -- cgit v1.2.3 From afd7c94353b7028b0bae000ad54bdf3f4285511a Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 23 Mar 2026 14:45:43 +0800 Subject: drm/amdgpu: retire legacy pmfw eeprom check Remove the legacy pmfw eeprom check function, as the feature is deprecated and unused Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 44 -------------------------- 1 file changed, 44 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index 9a9633b57022..f5d1bc1142a8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -1627,47 +1627,6 @@ int amdgpu_ras_eeprom_init(struct amdgpu_ras_eeprom_control *control) return 0; } -static int amdgpu_ras_smu_eeprom_check(struct amdgpu_ras_eeprom_control *control) -{ - struct amdgpu_device *adev = to_amdgpu_device(control); - struct amdgpu_ras *ras = amdgpu_ras_get_context(adev); - - if (!__is_ras_eeprom_supported(adev)) - return 0; - - control->ras_num_bad_pages = ras->bad_page_num; - - if ((ras->bad_page_cnt_threshold < control->ras_num_bad_pages) && - amdgpu_bad_page_threshold != 0) { - dev_warn(adev->dev, - "RAS records:%d exceed threshold:%d\n", - control->ras_num_bad_pages, ras->bad_page_cnt_threshold); - if ((amdgpu_bad_page_threshold == -1) || - (amdgpu_bad_page_threshold == -2)) { - dev_warn(adev->dev, - "Please consult AMD Service Action Guide (SAG) for appropriate service procedures\n"); - } else { - ras->is_rma = true; - dev_warn(adev->dev, - "User defined threshold is set, runtime service will be halt when threshold is reached\n"); - } - - return 0; - } - - dev_dbg(adev->dev, - "Found existing EEPROM table with %d records", - control->ras_num_bad_pages); - - /* Warn if we are at 90% of the threshold or above - */ - if (10 * control->ras_num_bad_pages >= 9 * ras->bad_page_cnt_threshold) - dev_warn(adev->dev, "RAS records:%u exceeds 90%% of threshold:%d", - control->ras_num_bad_pages, - ras->bad_page_cnt_threshold); - return 0; -} - int amdgpu_ras_eeprom_check(struct amdgpu_ras_eeprom_control *control) { struct amdgpu_device *adev = to_amdgpu_device(control); @@ -1675,9 +1634,6 @@ int amdgpu_ras_eeprom_check(struct amdgpu_ras_eeprom_control *control) struct amdgpu_ras *ras = amdgpu_ras_get_context(adev); int res = 0; - if (amdgpu_ras_smu_eeprom_supported(adev)) - return amdgpu_ras_smu_eeprom_check(control); - if (!__is_ras_eeprom_supported(adev)) return 0; -- cgit v1.2.3 From 719f83f3c67541b1392fb45851fb8ca7716dbde9 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 24 Jun 2026 13:47:18 +0800 Subject: drm/amd/pm: drop unused smu pptable callbacks struct pptable_funcs still carries callback slots that no longer have call paths, drop the following unused callback slots: - baco_get_state() - baco_set_state() - set_power_state() - get_clock_by_type_with_voltage() - set_azalia_d3_pme() Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 29 ---------------------- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v11_0.h | 2 -- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h | 2 -- drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c | 1 - drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c | 1 - .../drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c | 1 - drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c | 5 ---- drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c | 1 - drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c | 1 - drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c | 9 ------- .../gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 2 -- 11 files changed, 54 deletions(-) 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 d76e0b005308..38a8249570a9 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -849,8 +849,6 @@ struct pptable_funcs { */ int (*set_default_dpm_table)(struct smu_context *smu); - int (*set_power_state)(struct smu_context *smu); - /** * @populate_umd_state_clk: Populate the UMD power state table with * defaults. @@ -903,16 +901,6 @@ struct pptable_funcs { struct pp_clock_levels_with_latency *clocks); - /** - * @get_clock_by_type_with_voltage: Get the speed and voltage of a clock - * domain. - */ - int (*get_clock_by_type_with_voltage)(struct smu_context *smu, - enum amd_pp_clock_type type, - struct - pp_clock_levels_with_voltage - *clocks); - /** * @get_power_profile_mode: Print all power profile modes to * buffer. Star current mode. @@ -1354,11 +1342,6 @@ struct pptable_funcs { */ int (*register_irq_handler)(struct smu_context *smu); - /** - * @set_azalia_d3_pme: Wake the audio decode engine from d3 sleep. - */ - int (*set_azalia_d3_pme)(struct smu_context *smu); - /** * @get_max_sustainable_clocks_by_dc: Get a copy of the max sustainable * clock speeds table. @@ -1375,18 +1358,6 @@ struct pptable_funcs { */ int (*get_bamaco_support)(struct smu_context *smu); - /** - * @baco_get_state: Get the current BACO state. - * - * Return: Current BACO state. - */ - enum smu_baco_state (*baco_get_state)(struct smu_context *smu); - - /** - * @baco_set_state: Enter/exit BACO. - */ - int (*baco_set_state)(struct smu_context *smu, enum smu_baco_state state); - /** * @baco_enter: Enter BACO. */ diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v11_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v11_0.h index dd94e8a9e218..c0accee9a9c8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v11_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v11_0.h @@ -199,8 +199,6 @@ int smu_v11_0_gfx_off_control(struct smu_context *smu, bool enable); int smu_v11_0_register_irq_handler(struct smu_context *smu); -int smu_v11_0_set_azalia_d3_pme(struct smu_context *smu); - int smu_v11_0_get_max_sustainable_clocks_by_dc(struct smu_context *smu, struct pp_smu_nv_clock_table *max_clocks); diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h index 68f4de5f800c..7f21f867d73c 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h @@ -180,8 +180,6 @@ int smu_v13_0_gfx_off_control(struct smu_context *smu, bool enable); int smu_v13_0_register_irq_handler(struct smu_context *smu); -int smu_v13_0_set_azalia_d3_pme(struct smu_context *smu); - int smu_v13_0_get_max_sustainable_clocks_by_dc(struct smu_context *smu, struct pp_smu_nv_clock_table *max_clocks); 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 06898eaa96b8..db5db2c9c8e8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c @@ -1933,7 +1933,6 @@ static const struct pptable_funcs arcturus_ppt_funcs = { .set_xgmi_pstate = smu_v11_0_set_xgmi_pstate, .gfx_off_control = smu_v11_0_gfx_off_control, .register_irq_handler = smu_v11_0_register_irq_handler, - .set_azalia_d3_pme = smu_v11_0_set_azalia_d3_pme, .get_max_sustainable_clocks_by_dc = smu_v11_0_get_max_sustainable_clocks_by_dc, .get_bamaco_support = smu_v11_0_get_bamaco_support, .baco_enter = smu_v11_0_baco_enter, 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 7e7b082fce19..8feea44f3ca0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c @@ -3338,7 +3338,6 @@ static const struct pptable_funcs navi10_ppt_funcs = { .set_xgmi_pstate = smu_v11_0_set_xgmi_pstate, .gfx_off_control = smu_v11_0_gfx_off_control, .register_irq_handler = smu_v11_0_register_irq_handler, - .set_azalia_d3_pme = smu_v11_0_set_azalia_d3_pme, .get_max_sustainable_clocks_by_dc = smu_v11_0_get_max_sustainable_clocks_by_dc, .get_bamaco_support = smu_v11_0_get_bamaco_support, .baco_enter = navi10_baco_enter, 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 0ac789058d12..c0de73b85353 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 @@ -3145,7 +3145,6 @@ static const struct pptable_funcs sienna_cichlid_ppt_funcs = { .set_xgmi_pstate = smu_v11_0_set_xgmi_pstate, .gfx_off_control = smu_v11_0_gfx_off_control, .register_irq_handler = smu_v11_0_register_irq_handler, - .set_azalia_d3_pme = smu_v11_0_set_azalia_d3_pme, .get_max_sustainable_clocks_by_dc = smu_v11_0_get_max_sustainable_clocks_by_dc, .get_bamaco_support = smu_v11_0_get_bamaco_support, .baco_enter = sienna_cichlid_baco_enter, diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c index b2cba36046a1..a889d846e9c5 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c @@ -1417,11 +1417,6 @@ int smu_v11_0_get_max_sustainable_clocks_by_dc(struct smu_context *smu, return 0; } -int smu_v11_0_set_azalia_d3_pme(struct smu_context *smu) -{ - return smu_cmn_send_smc_msg(smu, SMU_MSG_BacoAudioD3PME, NULL); -} - int smu_v11_0_baco_set_armd3_sequence(struct smu_context *smu, enum smu_baco_seq baco_seq) { 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 75335da224c7..2b011610e3c7 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c @@ -1444,7 +1444,6 @@ static int renoir_get_enabled_mask(struct smu_context *smu, } static const struct pptable_funcs renoir_ppt_funcs = { - .set_power_state = NULL, .emit_clk_levels = renoir_emit_clk_levels, .get_current_power_state = renoir_get_current_power_state, .dpm_set_vcn_enable = renoir_dpm_set_vcn_enable, 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 9d8b1227388f..cd7bf36673cb 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c @@ -2004,7 +2004,6 @@ static const struct pptable_funcs aldebaran_ppt_funcs = { .disable_thermal_alert = smu_v13_0_disable_thermal_alert, .set_xgmi_pstate = smu_v13_0_set_xgmi_pstate, .register_irq_handler = smu_v13_0_register_irq_handler, - .set_azalia_d3_pme = smu_v13_0_set_azalia_d3_pme, .get_max_sustainable_clocks_by_dc = smu_v13_0_get_max_sustainable_clocks_by_dc, .get_bamaco_support = aldebaran_get_bamaco_support, .get_dpm_ultimate_freq = aldebaran_get_dpm_ultimate_freq, diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c index 492467154ab9..4f10bce36756 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c @@ -1320,15 +1320,6 @@ int smu_v13_0_get_max_sustainable_clocks_by_dc(struct smu_context *smu, return 0; } -int smu_v13_0_set_azalia_d3_pme(struct smu_context *smu) -{ - int ret = 0; - - ret = smu_cmn_send_smc_msg(smu, SMU_MSG_BacoAudioD3PME, NULL); - - return ret; -} - static int smu_v13_0_wait_for_reset_complete(struct smu_context *smu, uint64_t event_arg) { 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 1bb418f17025..d6cf643205ab 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 @@ -2890,8 +2890,6 @@ static const struct pptable_funcs smu_v14_0_2_ppt_funcs = { .deep_sleep_control = smu_v14_0_deep_sleep_control, .gfx_ulv_control = smu_v14_0_gfx_ulv_control, .get_bamaco_support = smu_v14_0_get_bamaco_support, - .baco_get_state = smu_v14_0_baco_get_state, - .baco_set_state = smu_v14_0_baco_set_state, .baco_enter = smu_v14_0_2_baco_enter, .baco_exit = smu_v14_0_2_baco_exit, .mode1_reset_is_support = smu_v14_0_2_is_mode1_reset_supported, -- cgit v1.2.3 From a928d8d81ec5cdb5a8944d08136720811efad0f6 Mon Sep 17 00:00:00 2001 From: Granthali Vinodkumar Dhandar Date: Wed, 17 Jun 2026 18:04:28 +0530 Subject: drm/amdgpu: add support for GC IP version 11.7.1 Initialize GC IP 11_7_1 Signed-off-by: Granthali Vinodkumar Dhandar Reviewed-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 6 ++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 1 + drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 12 +++++++++++- drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/imu_v11_0.c | 1 + drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/psp_v15_0.c | 2 ++ drivers/gpu/drm/amd/amdgpu/soc21.c | 28 +++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_crat.c | 1 + drivers/gpu/drm/amd/amdkfd/kfd_device.c | 5 +++++ 10 files changed, 59 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index aa7b94414477..a015d55aa158 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -2345,6 +2345,7 @@ static int amdgpu_discovery_set_common_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): amdgpu_device_ip_block_add(adev, &soc21_common_ip_block); break; case IP_VERSION(12, 0, 0): @@ -2407,6 +2408,7 @@ static int amdgpu_discovery_set_gmc_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): amdgpu_device_ip_block_add(adev, &gmc_v11_0_ip_block); break; case IP_VERSION(12, 0, 0): @@ -2734,6 +2736,7 @@ static int amdgpu_discovery_set_gc_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): amdgpu_device_ip_block_add(adev, &gfx_v11_0_ip_block); break; case IP_VERSION(12, 0, 0): @@ -2953,6 +2956,7 @@ static int amdgpu_discovery_set_mes_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): amdgpu_device_ip_block_add(adev, &mes_v11_0_ip_block); adev->enable_mes = true; adev->enable_mes_kiq = true; @@ -3362,6 +3366,7 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): adev->family = AMDGPU_FAMILY_GC_11_5_0; break; case IP_VERSION(12, 0, 0): @@ -3392,6 +3397,7 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): adev->flags |= AMD_IS_APU; break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 7a1709879393..4000b2c6fc98 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -978,6 +978,7 @@ void amdgpu_gmc_tmz_set(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): /* Don't enable it by default yet. */ if (amdgpu_tmz < 1) { diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 1cf790ec0434..2a121df90574 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -137,6 +137,10 @@ MODULE_FIRMWARE("amdgpu/gc_11_7_0_pfp.bin"); MODULE_FIRMWARE("amdgpu/gc_11_7_0_me.bin"); MODULE_FIRMWARE("amdgpu/gc_11_7_0_mec.bin"); MODULE_FIRMWARE("amdgpu/gc_11_7_0_rlc.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_pfp.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_me.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_mec.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_rlc.bin"); static const struct amdgpu_hwip_reg_entry gc_reg_list_11_0[] = { SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS), @@ -1133,6 +1137,7 @@ static int gfx_v11_0_gpu_early_init(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): adev->gfx.config.max_hw_contexts = 8; adev->gfx.config.sc_prim_fifo_size_frontend = 0x20; adev->gfx.config.sc_prim_fifo_size_backend = 0x100; @@ -1618,6 +1623,7 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): adev->gfx.me.num_me = 1; adev->gfx.me.num_pipe_per_me = 1; adev->gfx.me.num_queue_per_pipe = 2; @@ -3097,7 +3103,8 @@ static int gfx_v11_0_wait_for_rlc_autoload_complete(struct amdgpu_device *adev) amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 3) || amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 4) || amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 5, 6) || - amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 7, 0)) + amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 7, 0) || + amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(11, 7, 1)) bootload_status = RREG32_SOC15(GC, 0, regRLC_RLCS_BOOTLOAD_STATUS_gc_11_0_1); else @@ -5747,6 +5754,7 @@ static void gfx_v11_cntl_power_gating(struct amdgpu_device *adev, bool enable) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): WREG32_SOC15(GC, 0, regRLC_PG_DELAY_3, RLC_PG_DELAY_3_DEFAULT_GC_11_0_1); break; default: @@ -5788,6 +5796,7 @@ static int gfx_v11_0_set_powergating_state(struct amdgpu_ip_block *ip_block, case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): if (!enable) amdgpu_gfx_off_ctrl(adev, false); @@ -5825,6 +5834,7 @@ static int gfx_v11_0_set_clockgating_state(struct amdgpu_ip_block *ip_block, case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): gfx_v11_0_update_gfx_clock_gating(adev, state == AMD_CG_STATE_GATE); break; diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c index 8a0a88551461..c40d9c467204 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c @@ -607,6 +607,7 @@ static void gmc_v11_0_set_gfxhub_funcs(struct amdgpu_device *adev) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): adev->gfxhub.funcs = &gfxhub_v11_5_0_funcs; break; default: @@ -783,6 +784,7 @@ static int gmc_v11_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): set_bit(AMDGPU_GFXHUB(0), adev->vmhubs_mask); set_bit(AMDGPU_MMHUB0(0), adev->vmhubs_mask); /* diff --git a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c index 177d702e612a..05b164f38c97 100644 --- a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c @@ -44,6 +44,7 @@ MODULE_FIRMWARE("amdgpu/gc_11_5_3_imu.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_4_imu.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_imu.bin"); MODULE_FIRMWARE("amdgpu/gc_11_7_0_imu.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_imu.bin"); static int imu_v11_0_init_microcode(struct amdgpu_device *adev) { diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 5f08fa1242a5..8f136ff7d96f 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -62,6 +62,8 @@ MODULE_FIRMWARE("amdgpu/gc_11_5_6_mes_2.bin"); MODULE_FIRMWARE("amdgpu/gc_11_5_6_mes1.bin"); MODULE_FIRMWARE("amdgpu/gc_11_7_0_mes_2.bin"); MODULE_FIRMWARE("amdgpu/gc_11_7_0_mes1.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_mes_2.bin"); +MODULE_FIRMWARE("amdgpu/gc_11_7_1_mes1.bin"); static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block); static int mes_v11_0_hw_fini(struct amdgpu_ip_block *ip_block); diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c index 2a8582e87f2b..2a4d91368ac6 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c @@ -33,6 +33,8 @@ MODULE_FIRMWARE("amdgpu/psp_15_0_0_toc.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_0_ta.bin"); +MODULE_FIRMWARE("amdgpu/psp_15_0_9_toc.bin"); +MODULE_FIRMWARE("amdgpu/psp_15_0_9_ta.bin"); static int psp_v15_0_0_init_microcode(struct psp_context *psp) { diff --git a/drivers/gpu/drm/amd/amdgpu/soc21.c b/drivers/gpu/drm/amd/amdgpu/soc21.c index b07cd3bf787a..09f28dbd60ee 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc21.c +++ b/drivers/gpu/drm/amd/amdgpu/soc21.c @@ -854,6 +854,34 @@ static int soc21_common_early_init(struct amdgpu_ip_block *ip_block) AMD_PG_SUPPORT_GFX_PG; adev->external_rev_id = adev->rev_id + 0xF; break; + case IP_VERSION(11, 7, 1): + adev->cg_flags = AMD_CG_SUPPORT_VCN_MGCG | + AMD_CG_SUPPORT_JPEG_MGCG | + AMD_CG_SUPPORT_GFX_CGCG | + AMD_CG_SUPPORT_GFX_CGLS | + AMD_CG_SUPPORT_GFX_MGCG | + AMD_CG_SUPPORT_GFX_FGCG | + AMD_CG_SUPPORT_REPEATER_FGCG | + AMD_CG_SUPPORT_GFX_PERF_CLK | + AMD_CG_SUPPORT_GFX_3D_CGCG | + AMD_CG_SUPPORT_GFX_3D_CGLS | + AMD_CG_SUPPORT_MC_MGCG | + AMD_CG_SUPPORT_MC_LS | + AMD_CG_SUPPORT_HDP_LS | + AMD_CG_SUPPORT_HDP_DS | + AMD_CG_SUPPORT_HDP_SD | + AMD_CG_SUPPORT_ATHUB_MGCG | + AMD_CG_SUPPORT_ATHUB_LS | + AMD_CG_SUPPORT_IH_CG | + AMD_CG_SUPPORT_BIF_MGCG | + AMD_CG_SUPPORT_BIF_LS; + adev->pg_flags = AMD_PG_SUPPORT_VCN_DPG | + AMD_PG_SUPPORT_VCN | + AMD_PG_SUPPORT_JPEG_DPG | + AMD_PG_SUPPORT_JPEG | + AMD_PG_SUPPORT_GFX_PG; + adev->external_rev_id = adev->rev_id + 0x40; + break; default: /* FIXME: not supported yet */ return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c index a6a7888c7a8d..2a239f45fc24 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c @@ -1716,6 +1716,7 @@ int kfd_get_gpu_cache_info(struct kfd_node *kdev, struct kfd_gpu_cache_info **pc case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): /* Cacheline size not available in IP discovery for gc11. * kfd_fill_gpu_cache_info_from_gfx_config to hard code it */ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device.c b/drivers/gpu/drm/amd/amdkfd/kfd_device.c index 882a23ce9431..586e640f13dc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device.c @@ -170,6 +170,7 @@ static void kfd_device_info_set_event_interrupt_class(struct kfd_dev *kfd) case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): case IP_VERSION(11, 7, 0): + case IP_VERSION(11, 7, 1): kfd->device_info.event_interrupt_class = &event_interrupt_class_v11; break; case IP_VERSION(12, 0, 0): @@ -456,6 +457,10 @@ struct kfd_dev *kgd2kfd_probe(struct amdgpu_device *adev, bool vf) gfx_target_version = 110700; f2g = &gfx_v11_kfd2kgd; break; + case IP_VERSION(11, 7, 1): + gfx_target_version = 110701; + f2g = &gfx_v11_kfd2kgd; + break; case IP_VERSION(12, 0, 0): gfx_target_version = 120000; f2g = &gfx_v12_kfd2kgd; -- cgit v1.2.3 From feaa5039f6c12acc9aa934c2d45dcd251a12c69f Mon Sep 17 00:00:00 2001 From: Perry Yuan Date: Thu, 25 Jun 2026 13:57:56 +0800 Subject: drm/amdgpu: flush pending RCU callbacks on module unload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call rcu_barrier() in module exit to wait for outstanding call_rcu() callbacks before freeing module text, preventing late callback execution in freed memory. BUG: unable to handle page fault for address: ffffffffc1d59c40 PGD 6a12067 P4D 6a12067 PUD 6a14067 PMD 13698b067 PTE 0 Oops: 0010 [#1] SMP NOPTI RIP: 0010:0xffffffffc1d59c40 Code: Unable to access opcode bytes at RIP 0xffffffffc1d59c16. RSP: 0018:ffffc900198c0f28 EFLAGS: 00010286 RAX: ffffffffc1d59c40 RBX: ffff897c7d6b61c0 RCX: ffff88826aff4590 RDX: ffff8884d8b35490 RSI: ffffc900198c0f30 RDI: ffff88812af67290 RBP: 000000000000000a (DONE segment entries) R08: 0000000000000000 R09: 0000000000000100 R10: 0000000000000000 R11: ffffffff82a06100 R12: ffff88811a4e3700 R13: 0000000000000000 R14: ffff897c7d6b6270 R15: 0000000000000000 FS: 0000000000000000(0000) GS:ffff897c7d680000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: ffffffffc1d59c16 CR3: 00000104a980a001 CR4: 0000000002770ee0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe07f0 DR7: 0000000000000400 PKRU: 55555554 Call Trace: ? rcu_do_batch+0x163/0x450 ? rcu_core+0x177/0x1c0 ? __do_softirq+0xc1/0x280 ? asm_call_irq_on_stack+0xf/0x20 ? do_softirq_own_stack+0x37/0x50 ? irq_exit_rcu+0xc4/0x100 ? sysvec_apic_timer_interrupt+0x36/0x80 ? asm_sysvec_apic_timer_interrupt+0x12/0x20 ? cpuidle_enter_state+0xd4/0x360 ? cpuidle_enter+0x29/0x40 ? cpuidle_idle_call+0x108/0x1a0 ? do_idle+0x77/0xf0 ? cpu_startup_entry+0x19/0x20 ? secondary_startup_64_no_verify+0xbf/0xcb Signed-off-by: Perry Yuan Reviewed-by: Yifan Zhang Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 87885326f68b..ad631ad31899 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -3215,6 +3215,14 @@ static void __exit amdgpu_exit(void) amdgpu_sync_fini(); mmu_notifier_synchronize(); amdgpu_xcp_drv_release(); + + /* + * Flush outstanding call_rcu() callbacks before the + * module text is freed. Otherwise a grace period elapsing after + * unload invokes a callback in already-freed module memory and + * faults in rcu_do_batch(). + */ + rcu_barrier(); } module_init(amdgpu_init); -- cgit v1.2.3 From f45bbf0f62f266ed8422d84f347d75d5fca846a7 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 1 Jul 2026 09:11:15 +0800 Subject: drm/amd/pm: fix smu13 power limit range calculation SMU13 reports SocketPowerLimitAc/Dc as the default power limit, but MsgLimits.Power may carry a different firmware bound for the same PPT throttler. Using only the socket limit for both min and max can therefore expose an incorrect power range. Keep the socket limit as the default, but derive the range from both values: use the lower value for the min base and the higher value for the max base before applying OD percentages. Keep the current limit query independent from the cap calculation. Fixes: 1eaf26db9590 ("drm/amd/pm: fix smu13 power limit default/cap calculation") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5419 Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 11 +++++++---- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 15 ++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) 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 4e1d6a8da8e8..4ce1429cf57b 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 @@ -2403,11 +2403,14 @@ static int smu_v13_0_0_get_power_limit(struct smu_context *smu, uint32_t pp_limit = smu->adev->pm.ac_power ? skutable->SocketPowerLimitAc[PPT_THROTTLER_PPT0] : skutable->SocketPowerLimitDc[PPT_THROTTLER_PPT0]; - uint32_t power_limit = 0, od_percent_upper = 0, od_percent_lower = 0; + uint32_t msg_limit = skutable->MsgLimits.Power[PPT_THROTTLER_PPT0][POWER_SOURCE_AC]; + uint32_t min_limit = min_t(uint32_t, pp_limit, msg_limit); + uint32_t max_limit = max_t(uint32_t, pp_limit, msg_limit); + uint32_t od_percent_upper = 0, od_percent_lower = 0; int ret; if (current_power_limit) { - ret = smu_v13_0_get_current_power_limit(smu, &power_limit); + ret = smu_v13_0_get_current_power_limit(smu, current_power_limit); if (ret) *current_power_limit = pp_limit; } @@ -2430,12 +2433,12 @@ static int smu_v13_0_0_get_power_limit(struct smu_context *smu, od_percent_upper, od_percent_lower, pp_limit); if (max_power_limit) { - *max_power_limit = pp_limit * (100 + od_percent_upper); + *max_power_limit = max_limit * (100 + od_percent_upper); *max_power_limit /= 100; } if (min_power_limit) { - *min_power_limit = pp_limit * (100 - od_percent_lower); + *min_power_limit = min_limit * (100 - od_percent_lower); *min_power_limit /= 100; } 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 81d4ba8013e8..5f23f2e7f401 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 @@ -2385,15 +2385,16 @@ static int smu_v13_0_7_get_power_limit(struct smu_context *smu, uint32_t pp_limit = smu->adev->pm.ac_power ? skutable->SocketPowerLimitAc[PPT_THROTTLER_PPT0] : skutable->SocketPowerLimitDc[PPT_THROTTLER_PPT0]; - uint32_t power_limit = 0, od_percent_upper = 0, od_percent_lower = 0; + uint32_t msg_limit = skutable->MsgLimits.Power[PPT_THROTTLER_PPT0][POWER_SOURCE_AC]; + uint32_t min_limit = min_t(uint32_t, pp_limit, msg_limit); + uint32_t max_limit = max_t(uint32_t, pp_limit, msg_limit); + uint32_t od_percent_upper = 0, od_percent_lower = 0; int ret; if (current_power_limit) { - ret = smu_v13_0_get_current_power_limit(smu, &power_limit); + ret = smu_v13_0_get_current_power_limit(smu, current_power_limit); if (ret) - power_limit = pp_limit; - - *current_power_limit = power_limit; + *current_power_limit = pp_limit; } if (default_power_limit) @@ -2414,12 +2415,12 @@ static int smu_v13_0_7_get_power_limit(struct smu_context *smu, od_percent_upper, od_percent_lower, pp_limit); if (max_power_limit) { - *max_power_limit = pp_limit * (100 + od_percent_upper); + *max_power_limit = max_limit * (100 + od_percent_upper); *max_power_limit /= 100; } if (min_power_limit) { - *min_power_limit = pp_limit * (100 - od_percent_lower); + *min_power_limit = min_limit * (100 - od_percent_lower); *min_power_limit /= 100; } -- cgit v1.2.3 From fa6478865c9d682dd09cc688f8c0e96f87522011 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 3 Apr 2026 10:52:01 +0800 Subject: drm/amdgpu: retire legacy pmfw eeprom init Remove the legacy pmfw eeprom initialization function, as the feature is deprecated and unused Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 39 -------------------------- 1 file changed, 39 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index f5d1bc1142a8..09aa5655e3c1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -1485,42 +1485,6 @@ Out: return res == RAS_TABLE_V2_1_INFO_SIZE ? 0 : res; } -static int amdgpu_ras_smu_eeprom_init(struct amdgpu_ras_eeprom_control *control) -{ - struct amdgpu_device *adev = to_amdgpu_device(control); - struct amdgpu_ras_eeprom_table_header *hdr = &control->tbl_hdr; - struct amdgpu_ras *ras = amdgpu_ras_get_context(adev); - uint64_t local_time; - int res; - - ras->is_rma = false; - - if (!__is_ras_eeprom_supported(adev)) - return 0; - mutex_init(&control->ras_tbl_mutex); - - res = amdgpu_ras_smu_get_table_version(adev, &(hdr->version)); - if (res) - return res; - - res = amdgpu_ras_smu_get_badpage_count(adev, - &(control->ras_num_recs), 100); - if (res) - return res; - - local_time = (uint64_t)ktime_get_real_seconds(); - res = amdgpu_ras_smu_set_timestamp(adev, local_time); - if (res) - return res; - - control->ras_max_record_count = 4000; - - control->ras_num_mca_recs = 0; - control->ras_num_pa_recs = 0; - - return 0; -} - int amdgpu_ras_eeprom_init(struct amdgpu_ras_eeprom_control *control) { struct amdgpu_device *adev = to_amdgpu_device(control); @@ -1531,9 +1495,6 @@ int amdgpu_ras_eeprom_init(struct amdgpu_ras_eeprom_control *control) uint32_t vram_type = adev->gmc.vram_type; int res; - if (amdgpu_ras_smu_eeprom_supported(adev)) - return amdgpu_ras_smu_eeprom_init(control); - ras->is_rma = false; if (!__is_ras_eeprom_supported(adev)) -- cgit v1.2.3 From 8db95ea238e660283b86653b601695f697edbb17 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 23 Mar 2026 14:57:34 +0800 Subject: drm/amdgpu: retire legacy pmfw eeprom reset Remove the legacy pmfw eeprom reset adaptation function, as the feature is deprecated and unused Reviewed-by: Hawking Zhang Signed-off-by: Ce Sun Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 71 +++++++++++--------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index 09aa5655e3c1..baa8cc3646d5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -450,57 +450,46 @@ int amdgpu_ras_eeprom_reset_table(struct amdgpu_ras_eeprom_control *control) struct amdgpu_ras_eeprom_table_header *hdr = &control->tbl_hdr; struct amdgpu_ras_eeprom_table_ras_info *rai = &control->tbl_rai; struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - u32 erase_res = 0; u8 csum; int res; mutex_lock(&control->ras_tbl_mutex); - if (!amdgpu_ras_smu_eeprom_supported(adev)) { - hdr->header = RAS_TABLE_HDR_VAL; - amdgpu_ras_set_eeprom_table_version(control); - - if (hdr->version >= RAS_TABLE_VER_V2_1) { - hdr->first_rec_offset = RAS_RECORD_START_V2_1; - hdr->tbl_size = RAS_TABLE_HEADER_SIZE + - RAS_TABLE_V2_1_INFO_SIZE; - rai->rma_status = GPU_HEALTH_USABLE; - - control->ras_record_offset = RAS_RECORD_START_V2_1; - control->ras_max_record_count = RAS_MAX_RECORD_COUNT_V2_1; - /** - * GPU health represented as a percentage. - * 0 means worst health, 100 means fully health. - */ - rai->health_percent = 100; - /* ecc_page_threshold = 0 means disable bad page retirement */ - rai->ecc_page_threshold = con->bad_page_cnt_threshold; - } else { - hdr->first_rec_offset = RAS_RECORD_START; - hdr->tbl_size = RAS_TABLE_HEADER_SIZE; + hdr->header = RAS_TABLE_HDR_VAL; + amdgpu_ras_set_eeprom_table_version(control); - control->ras_record_offset = RAS_RECORD_START; - control->ras_max_record_count = RAS_MAX_RECORD_COUNT; - } + if (hdr->version >= RAS_TABLE_VER_V2_1) { + hdr->first_rec_offset = RAS_RECORD_START_V2_1; + hdr->tbl_size = RAS_TABLE_HEADER_SIZE + + RAS_TABLE_V2_1_INFO_SIZE; + rai->rma_status = GPU_HEALTH_USABLE; - csum = __calc_hdr_byte_sum(control); - if (hdr->version >= RAS_TABLE_VER_V2_1) - csum += __calc_ras_info_byte_sum(control); - csum = -csum; - hdr->checksum = csum; - res = __write_table_header(control); - if (!res && hdr->version > RAS_TABLE_VER_V1) - res = __write_table_ras_info(control); + control->ras_record_offset = RAS_RECORD_START_V2_1; + control->ras_max_record_count = RAS_MAX_RECORD_COUNT_V2_1; + /** + * GPU health represented as a percentage. + * 0 means worst health, 100 means fully health. + */ + rai->health_percent = 100; + /* ecc_page_threshold = 0 means disable bad page retirement */ + rai->ecc_page_threshold = con->bad_page_cnt_threshold; } else { - res = amdgpu_ras_smu_erase_ras_table(adev, &erase_res); - if (res || erase_res) { - dev_warn(adev->dev, "RAS EEPROM reset failed, res:%d result:%d", - res, erase_res); - if (!res) - res = -EIO; - } + hdr->first_rec_offset = RAS_RECORD_START; + hdr->tbl_size = RAS_TABLE_HEADER_SIZE; + + control->ras_record_offset = RAS_RECORD_START; + control->ras_max_record_count = RAS_MAX_RECORD_COUNT; } + csum = __calc_hdr_byte_sum(control); + if (hdr->version >= RAS_TABLE_VER_V2_1) + csum += __calc_ras_info_byte_sum(control); + csum = -csum; + hdr->checksum = csum; + res = __write_table_header(control); + if (!res && hdr->version > RAS_TABLE_VER_V1) + res = __write_table_ras_info(control); + control->ras_num_recs = 0; control->ras_num_bad_pages = 0; control->ras_num_mca_recs = 0; -- cgit v1.2.3 From ad2af2fbcc19dac7c6d8682eac7c779a99102ebb Mon Sep 17 00:00:00 2001 From: Ke Zhao Date: Tue, 30 Jun 2026 15:28:38 +0800 Subject: drm/amdgpu: Fix typo in comment It should be doorbell. Signed-off-by: Ke Zhao Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 025625e7e800..b10b0878df37 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2194,7 +2194,7 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) return r; } - /* Create a boorbell page for kernel usages */ + /* Create a doorbell page for kernel usages */ r = amdgpu_doorbell_create_kernel_doorbells(adev); if (r) { dev_err(adev->dev, "Failed to initialize kernel doorbells.\n"); -- cgit v1.2.3 From 951d2a891e7681adc4b52890158c4cf99d8c0f0a Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 26 Jun 2026 09:55:56 +0100 Subject: drm/amdgpu: Remove unused amdgpu_device_ip_is_hw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function is unused so lets remove it. Reviewed-by: Timur Kristóf Signed-off-by: Tvrtko Ursulin Cc: Alex Deucher Cc: Christian König Cc: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c | 21 --------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h | 2 -- 2 files changed, 23 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c index 99ed0b0d82e9..33a04113ed74 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c @@ -368,27 +368,6 @@ int amdgpu_device_ip_wait_for_idle(struct amdgpu_device *adev, return 0; } -/** - * amdgpu_device_ip_is_hw - is the hardware IP enabled - * - * @adev: amdgpu_device pointer - * @block_type: Type of hardware IP (SMU, GFX, UVD, etc.) - * - * Check if the hardware IP is enable or not. - * Returns true if it the IP is enable, false if not. - */ -bool amdgpu_device_ip_is_hw(struct amdgpu_device *adev, - enum amd_ip_block_type block_type) -{ - struct amdgpu_ip_block *ip_block; - - ip_block = amdgpu_device_ip_get_ip_block(adev, block_type); - if (ip_block) - return ip_block->status.hw; - - return false; -} - /** * amdgpu_device_ip_is_valid - is the hardware IP valid * diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h index 18fd8631a092..70fc4e5db51f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h @@ -150,8 +150,6 @@ void amdgpu_device_ip_get_clockgating_state(struct amdgpu_device *adev, u64 *flags); int amdgpu_device_ip_wait_for_idle(struct amdgpu_device *adev, enum amd_ip_block_type block_type); -bool amdgpu_device_ip_is_hw(struct amdgpu_device *adev, - enum amd_ip_block_type block_type); bool amdgpu_device_ip_is_valid(struct amdgpu_device *adev, enum amd_ip_block_type block_type); int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, -- cgit v1.2.3 From 875a785373327f4c11aaec20e3c765e26f68604e Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 26 Jun 2026 09:55:57 +0100 Subject: drm/amdgpu: Save some cycles on the job submission path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every job submission on the Steam Deck ends up walking the list of IP blocks looking for AMD_IP_BLOCK_TYPE_SMC. Half of the call chain is like the below, while the second half is from amdgpu_gfx_profile_ring_end_use: amdgpu_gfx_profile_ring_begin_use amdgpu_dpm_is_overdrive_enabled is_support_sw_smu amdgpu_device_ip_is_valid On a game menu screen at 90Hz refresh rate we end up with ~840 calls per second which sticks out when the submission worker is profiled with perf: 13.78% [kernel] [k] __lock_text_start 10.86% [kernel] [k] __lookup_object 8.76% [kernel] [k] __mod_timer 4.94% [kernel] [k] queued_spin_lock_slowpath 1.66% [kernel] [k] amdgpu_device_ip_is_valid 1.54% [kernel] [k] preempt_count_add 1.42% [kernel] [k] amdgpu_sync_peek_fence 1.18% [kernel] [k] amdgpu_vmid_grab 1.17% [kernel] [k] amdgpu_ib_schedule 1.14% [kernel] [k] kthread_worker_fn Lets short-circuit this walk by simply caching the result of is_support_sw_smu() in the device. This is a micro-improvement but it is at least conceptually nicer to avoid repeating the same walk so much. Reviewed-by: Timur Kristóf Signed-off-by: Tvrtko Ursulin Cc: Alex Deucher Cc: Christian König Cc: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 3 +++ drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 14 +++++--------- drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 8 +++++++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 13d6f31344c4..dd8ea71077af 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -818,6 +818,7 @@ struct amdgpu_device { struct dev_pm_domain vga_pm_domain; bool have_disp_power_ref; bool have_atomics_support; + bool is_sw_smu; /* BIOS */ bool is_atom_fw; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 7265de3889e3..78c96c7102e4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -74,6 +74,7 @@ #include "amdgpu_ras.h" #include "amdgpu_ras_mgr.h" #include "amdgpu_pmu.h" +#include "amdgpu_smu.h" #include "amdgpu_fru_eeprom.h" #include "amdgpu_reset.h" #include "amdgpu_virt.h" @@ -2130,6 +2131,8 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) adev->cg_flags &= amdgpu_cg_mask; adev->pg_flags &= amdgpu_pg_mask; + amdgpu_smu_early_init(adev); + return 0; } diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 9abfac9f81d1..541cf0a985eb 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -591,17 +591,13 @@ static int smu_get_power_num_states(void *handle, return 0; } -bool is_support_sw_smu(struct amdgpu_device *adev) +void amdgpu_smu_early_init(struct amdgpu_device *adev) { /* vega20 is 11.0.2, but it's supported via the powerplay code */ - if (adev->asic_type == CHIP_VEGA20) - return false; - - if ((amdgpu_ip_version(adev, MP1_HWIP, 0) >= IP_VERSION(11, 0, 0)) && - amdgpu_device_ip_is_valid(adev, AMD_IP_BLOCK_TYPE_SMC)) - return true; - - return false; + adev->is_sw_smu = adev->asic_type != CHIP_VEGA20 && + (amdgpu_ip_version(adev, MP1_HWIP, 0) >= + IP_VERSION(11, 0, 0) && + amdgpu_device_ip_is_valid(adev, AMD_IP_BLOCK_TYPE_SMC)); } bool is_support_cclk_dpm(struct amdgpu_device *adev) 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 38a8249570a9..378781c05bea 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -1923,7 +1923,13 @@ int smu_link_reset(struct smu_context *smu); extern const struct amd_ip_funcs smu_ip_funcs; -bool is_support_sw_smu(struct amdgpu_device *adev); +void amdgpu_smu_early_init(struct amdgpu_device *adev); + +static inline bool is_support_sw_smu(struct amdgpu_device *adev) +{ + return adev->is_sw_smu; +} + bool is_support_cclk_dpm(struct amdgpu_device *adev); int smu_write_watermarks_table(struct smu_context *smu); -- cgit v1.2.3 From 50be7c9b5d5ea55fd40bb411cf324cec99ec7417 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 26 Jun 2026 09:55:58 +0100 Subject: drm/amdgpu: Do not fiddle with the idle workers too much MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Idle workers only need to be canceled or pushed back if we are potentially idle. Make the both operations conditional on the pre-increment and post- decrement status of the in-flight job counter. Reviewed-by: Timur Kristóf Signed-off-by: Tvrtko Ursulin Cc: Alex Deucher Cc: Christian König Cc: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 11 +++++------ drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c | 9 +++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c | 12 +++++------- drivers/gpu/drm/amd/amdgpu/vcn_v2_5.c | 12 +++++------- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 419992589df3..96c9d4f00b27 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -2733,9 +2733,8 @@ void amdgpu_gfx_profile_ring_begin_use(struct amdgpu_ring *ring) else profile = PP_SMC_POWER_PROFILE_COMPUTE; - atomic_inc(&adev->gfx.total_submission_cnt); - - cancel_delayed_work_sync(&adev->gfx.idle_work); + if (!atomic_fetch_inc(&adev->gfx.total_submission_cnt)) + cancel_delayed_work_sync(&adev->gfx.idle_work); /* We can safely return early here because we've cancelled the * the delayed work so there is no one else to set it to false @@ -2763,9 +2762,9 @@ void amdgpu_gfx_profile_ring_end_use(struct amdgpu_ring *ring) if (amdgpu_dpm_is_overdrive_enabled(adev)) return; - atomic_dec(&ring->adev->gfx.total_submission_cnt); - - schedule_delayed_work(&ring->adev->gfx.idle_work, GFX_PROFILE_IDLE_TIMEOUT); + if (atomic_dec_and_test(&ring->adev->gfx.total_submission_cnt)) + schedule_delayed_work(&ring->adev->gfx.idle_work, + GFX_PROFILE_IDLE_TIMEOUT); } /** diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c index 63ee6ba6a931..57935c321515 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c @@ -134,8 +134,8 @@ void amdgpu_jpeg_ring_begin_use(struct amdgpu_ring *ring) { struct amdgpu_device *adev = ring->adev; - atomic_inc(&adev->jpeg.total_submission_cnt); - cancel_delayed_work_sync(&adev->jpeg.idle_work); + if (!atomic_fetch_inc(&adev->jpeg.total_submission_cnt)) + cancel_delayed_work_sync(&adev->jpeg.idle_work); mutex_lock(&adev->jpeg.jpeg_pg_lock); amdgpu_device_ip_set_powergating_state(adev, AMD_IP_BLOCK_TYPE_JPEG, @@ -145,8 +145,9 @@ void amdgpu_jpeg_ring_begin_use(struct amdgpu_ring *ring) void amdgpu_jpeg_ring_end_use(struct amdgpu_ring *ring) { - atomic_dec(&ring->adev->jpeg.total_submission_cnt); - schedule_delayed_work(&ring->adev->jpeg.idle_work, JPEG_IDLE_TIMEOUT); + if (atomic_dec_and_test(&ring->adev->jpeg.total_submission_cnt)) + schedule_delayed_work(&ring->adev->jpeg.idle_work, + JPEG_IDLE_TIMEOUT); } int amdgpu_jpeg_dec_ring_test_ring(struct amdgpu_ring *ring) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c index e4d435d4a629..fe504f1a3fc8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c @@ -506,9 +506,8 @@ void amdgpu_vcn_ring_begin_use(struct amdgpu_ring *ring) struct amdgpu_device *adev = ring->adev; struct amdgpu_vcn_inst *vcn_inst = &adev->vcn.inst[ring->me]; - atomic_inc(&vcn_inst->total_submission_cnt); - - cancel_delayed_work_sync(&vcn_inst->idle_work); + if (!atomic_fetch_inc(&vcn_inst->total_submission_cnt)) + cancel_delayed_work_sync(&vcn_inst->idle_work); mutex_lock(&vcn_inst->vcn_pg_lock); vcn_inst->set_pg_state(vcn_inst, AMD_PG_STATE_UNGATE); @@ -550,10 +549,9 @@ void amdgpu_vcn_ring_end_use(struct amdgpu_ring *ring) !adev->vcn.inst[ring->me].using_unified_queue) atomic_dec(&ring->adev->vcn.inst[ring->me].dpg_enc_submission_cnt); - atomic_dec(&ring->adev->vcn.inst[ring->me].total_submission_cnt); - - schedule_delayed_work(&ring->adev->vcn.inst[ring->me].idle_work, - VCN_IDLE_TIMEOUT); + if (atomic_dec_and_test(&ring->adev->vcn.inst[ring->me].total_submission_cnt)) + schedule_delayed_work(&ring->adev->vcn.inst[ring->me].idle_work, + VCN_IDLE_TIMEOUT); } int amdgpu_vcn_dec_ring_test_ring(struct amdgpu_ring *ring) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v2_5.c b/drivers/gpu/drm/amd/amdgpu/vcn_v2_5.c index 8b8184fe6764..0d8a3cea63ee 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v2_5.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v2_5.c @@ -159,9 +159,8 @@ static void vcn_v2_5_ring_begin_use(struct amdgpu_ring *ring) struct amdgpu_device *adev = ring->adev; struct amdgpu_vcn_inst *v = &adev->vcn.inst[ring->me]; - atomic_inc(&adev->vcn.inst[0].total_submission_cnt); - - cancel_delayed_work_sync(&adev->vcn.inst[0].idle_work); + if (!atomic_fetch_inc(&adev->vcn.inst[0].total_submission_cnt)) + cancel_delayed_work_sync(&adev->vcn.inst[0].idle_work); /* We can safely return early here because we've cancelled the * the delayed work so there is no one else to set it to false @@ -207,10 +206,9 @@ static void vcn_v2_5_ring_end_use(struct amdgpu_ring *ring) !adev->vcn.inst[ring->me].using_unified_queue) atomic_dec(&adev->vcn.inst[ring->me].dpg_enc_submission_cnt); - atomic_dec(&adev->vcn.inst[0].total_submission_cnt); - - schedule_delayed_work(&adev->vcn.inst[0].idle_work, - VCN_IDLE_TIMEOUT); + if (atomic_dec_and_test(&adev->vcn.inst[0].total_submission_cnt)) + schedule_delayed_work(&adev->vcn.inst[0].idle_work, + VCN_IDLE_TIMEOUT); } /** -- cgit v1.2.3