From 9413959fa9fe2d94d4814f8cc2b60409f4cd46b5 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 25 Aug 2026 23:09:32 +0800 Subject: [PATCH 01/12] drm/amd/pm: report energy accumulator for smu 14.0.3 add energy accumulator on pmfw 0x00685000 and above version. Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 4aa733ab15b303e2a40e2985ac21a0e01f24cc4a) --- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 3 +++ 1 file changed, 3 insertions(+) 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 56a5c11bc196..ed99e61f18e1 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 @@ -2142,6 +2142,7 @@ static void smu_v14_0_2_init_msg_ctl(struct smu_context *smu) static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, void **table) { + uint32_t mp1_ver = amdgpu_ip_version(smu->adev, MP1_HWIP, 0); struct gpu_metrics_v1_3 *gpu_metrics = (struct gpu_metrics_v1_3 *)smu_driver_table_ptr( smu, SMU_DRIVER_TABLE_GPU_METRICS); @@ -2171,6 +2172,8 @@ static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, metrics->Vcn1ActivityPercentage); gpu_metrics->average_socket_power = metrics->AverageSocketPower; + if (mp1_ver == IP_VERSION(14, 0, 3) && smu->smc_fw_version >= 0x00685000) + gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_14_0_2_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; From 63e19ef3ddab806c472748c825f4dc88dcd994e8 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 25 Aug 2026 23:21:53 -0400 Subject: [PATCH 02/12] drm/amd/display: Atomize IRQ register read/modify/write ops [Why] The OTG_GLOBAL_SYNC_STATUS register controls various HW IRQ sources for the output timing generator (OTG). VUPDATE_NO_LOCK is one of them. To enable the IRQ, driver sets the VUPDATE_NO_LOCK_EN bit in the GLOBAL_SYNC_STATUS register. To ack the IRQ after it fires, the driver sets the VUPDATE_NO_LOCK_CLEAR bit in the same GLOBAL_SYNC_STATUS register. The bit sets are done through read/modify/write operations, which are not atomic. Thus, the following race is possible: Thread A: IRQ handler: *HW IRQ fires* # IRQ disable val = read(GLOBAL_SYNC_STATUS) unset(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # ACK reads VUPDATE_NO_LOCK_EN unset val1 = read(GLOBAL_SYNC_STATUS) set(val1, VUPDATE_NO_LOCK_CLEAR) # IRQ enable val = read(GLOBAL_SYNC_STATUS) set(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # BAD! clears VUPDATE_NO_LOCK_EN write(val1, GLOBAL_SYNC_STATUS) Regarding the tagged Fixes: change, it appears the change made this race more likely to occur. Since VUPDATE_NO_LOCK is now the sole IRQ source for vblank handling, a single race on high refresh panels can lead to a time out. [How] The GLOBAL_SYNC_STATUS register is only one example, other IRQ control registers also share the same scheme. On top of GLOBAL_SYNC_STATUS, let's clean up those as well. To keep things simple, Let's atomize the IRQ rmw ops via a single driver-wide spinlock. Due to the small scope of this lock, it is unlikely to cause noticeable overhead on top of all the existing locking within the IRQ set/handle paths. Since DM is responsible for locking, wrap dc_interrupt_set/ack with the spinlock in the new amdgpu_dm_irq_set/ack functions. Migrate/drop all references in DM to dc_interrupt_set/ack to use amdgpu_dm_irq_set/ack instead. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5616 Fixes: c87e6635d2db ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Reviewed-by: Mario Limonciello Signed-off-by: Leo Li Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 70de0a0216583a53c946155f8c8adedfdca6b4e7) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 12 ++++ .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 3 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 3 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 64 +++++++++++-------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 28 ++++++++ .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 16 +++-- .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 8 ++- .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 34 +++++----- 9 files changed, 117 insertions(+), 55 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 08b8605029ab..36d2f86f000a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1420,7 +1420,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, if (acrtc && state->stream_status[i].plane_count != 0 && amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; if (rc) drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", enable ? "enable" : "disable"); @@ -1444,7 +1444,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, /* During gpu-reset we disable and then enable vblank irq, so * don't use amdgpu_irq_get/put() to avoid refcount change. */ - if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) + if (!amdgpu_dm_irq_set(adev, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); } else if (acrtc && state->stream_status[i].plane_count != 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 3524931451c8..881c8d1c3cc0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -552,6 +552,18 @@ struct amdgpu_display_manager { struct common_irq_params vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + /** + * @irq_reg_lock: + * + * Serializes the read-modify-writes of the HW interrupt control + * registers. Several interrupt sources share one register - e.g. the + * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK + * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must + * not race with acking another. Held only across amdgpu_dm_irq_set() + * and amdgpu_dm_irq_ack(). + */ + spinlock_t irq_reg_lock; + /** * @dmub_trace_params: * 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 62eac6e65334..1d941be73561 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 @@ -31,6 +31,7 @@ #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" #include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_irq.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" @@ -91,7 +92,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", acrtc->crtc_id, enable ? "en" : "dis", rc); 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 298de7b75ca8..ced8b3d2d762 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 @@ -1439,12 +1439,13 @@ void dm_helpers_free_gpu_mem( bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) { + struct amdgpu_device *adev = ctx->driver_context; enum dc_irq_source irq_source; bool ret; irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; - ret = dc_interrupt_set(ctx->dc, irq_source, enable); + ret = amdgpu_dm_irq_set(adev, irq_source, enable); DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", enable ? "en" : "dis", ret); 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 d0239a3de2e1..74a8735168aa 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 @@ -396,6 +396,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) DRM_DEBUG_KMS("DM_IRQ\n"); spin_lock_init(&adev->dm.irq_handler_list_table_lock); + spin_lock_init(&adev->dm.irq_reg_lock); adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", WQ_UNBOUND | WQ_HIGHPRI, 0); @@ -530,7 +531,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; - dc_interrupt_set(adev->dm.dc, src, false); + amdgpu_dm_irq_set(adev, src, false); DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -568,7 +569,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -594,7 +595,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -690,6 +691,23 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + return dc_interrupt_set(adev->dm.dc, src, enable); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_set); + +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + dc_interrupt_ack(adev->dm.dc, src); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_ack); + /** * amdgpu_dm_irq_handler - Generic DM IRQ handler * @adev: amdgpu base driver device containing the DM device @@ -710,7 +728,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, entry->src_id, entry->src_data[0]); - dc_interrupt_ack(adev->dm.dc, src); + amdgpu_dm_irq_ack(adev, src); /* Call high irq work immediately */ amdgpu_dm_irq_immediate_work(adev, src); @@ -750,7 +768,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, src, st); + amdgpu_dm_irq_set(adev, src, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); @@ -785,7 +803,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) dc_allow_idle_optimizations(dc, false); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } @@ -842,7 +860,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *a enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); @@ -870,7 +888,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *ad enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); @@ -937,9 +955,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs); void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { - dc_interrupt_set(adev->dm.dc, - DC_IRQ_SOURCE_DMCUB_OUTBOX, - true); + amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); @@ -962,7 +978,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) /* First, clear all hpd and hpdrx interrupts */ for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { - if (!dc_interrupt_set(adev->dm.dc, i, false)) + if (!amdgpu_dm_irq_set(adev, i, false)) drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", i); } @@ -991,7 +1007,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * of dm. Note that only hpd interrupt types are registered with * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on * hpd_rx isn't available. DM currently controls hpd_rx - * explicitly with dc_interrupt_set() + * explicitly with amdgpu_dm_irq_set() */ if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; @@ -1000,23 +1016,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * and what bios reports as the # of connectors with hpd * sources. Since the # of hpd source types registered * with base driver == mode_info.num_hpd, we have to - * fallback to dc_interrupt_set for the remaining types. + * fallback to amdgpu_dm_irq_set for the remaining types. */ if (irq_type < adev->mode_info.num_hpd) { if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + true); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + true); } } drm_connector_list_iter_end(&iter); @@ -1061,16 +1075,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + false); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + false); } } drm_connector_list_iter_end(&iter); 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 4c200a9614a7..bc16ecc67329 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 @@ -89,6 +89,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, enum dc_irq_source irq_source, void *ih_index); +/** + * amdgpu_dm_irq_set - enable or disable a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to toggle + * @enable: true to enable the source, false to disable it + * + * DM-wide replacement for dc_interrupt_set(). As locking is DM's + * responsibility, this is a thin wrapper serializes the underlying + * read-modify-write against the other interrupt sources sharing HW control + * registers with @src, so DM must never call dc_interrupt_set() directly. + * + * Returns: true if the source was toggled. + */ +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable); + +/** + * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to acknowledge + * + * DM-wide replacement for dc_interrupt_ack(), serialized the same way as + * amdgpu_dm_irq_set(). + */ +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); void amdgpu_dm_outbox_init(struct amdgpu_device *adev); 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 0d998f204250..ae0f4da96252 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 @@ -436,7 +436,7 @@ static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) * dm_test_crtc_set_vupdate_irq_dc_busy - Test vupdate irq when DC rejects request * @test: The KUnit test context * - * With an OTG instance assigned but no DC attached, dc_interrupt_set() returns + * With an OTG instance assigned but no DC attached, amdgpu_dm_irq_set() returns * false and the function must report the request as busy (-EBUSY). */ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) @@ -453,12 +453,12 @@ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) acrtc->base.dev = &adev->ddev; acrtc->otg_inst = 0; - /* adev->dm.dc is NULL, so dc_interrupt_set() returns false. */ + /* adev->dm.dc is NULL, so amdgpu_dm_irq_set() returns false. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), -EBUSY); } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static bool dm_test_vupdate_irq_src_set(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -477,7 +477,7 @@ static struct irq_source_info_funcs dm_test_vupdate_irq_src_funcs = { .ack = dm_test_vupdate_irq_src_ack, }; -/* A .set that fails so dc_interrupt_set() reports the source as busy. */ +/* A .set that fails so amdgpu_dm_irq_set() reports the source as busy. */ static bool dm_test_vupdate_irq_src_set_busy(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -519,7 +519,9 @@ static void dm_test_crtc_set_vupdate_irq_enable(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Populate the per-source info table so dc_interrupt_set() succeeds. */ + /* + * Populate the per-source info table so amdgpu_dm_irq_set() succeeds. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); @@ -1018,7 +1020,9 @@ static void dm_test_crtc_enable_vblank_vupdate_busy(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Per-source .set fails so dc_interrupt_set() reports the source busy. */ + /* + * Per-source .set fails so amdgpu_dm_irq_set() reports the source busy. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); 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 82e0c984693c..639512bea275 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 @@ -2442,17 +2442,21 @@ static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc * @test: The KUnit test context * - * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * amdgpu_dm_irq_set() is NULL-safe and returns false when dc is NULL, so the * helper returns false without touching real interrupt hardware. */ static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) { + struct amdgpu_device *adev; struct dc_context *ctx; + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; - /* ctx->dc is NULL → dc_interrupt_set returns false */ + /* adev->dm.dc is NULL → amdgpu_dm_irq_set returns false */ KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); } 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 861ee9eaa032..95322d8c8613 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 @@ -268,7 +268,7 @@ static bool dm_test_irq_src_ack(struct irq_service *irq_service, return true; } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static struct irq_source_info_funcs dm_test_irq_src_funcs = { .set = dm_test_irq_src_set, .ack = dm_test_irq_src_ack, @@ -290,7 +290,7 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); /* - * Populate the per-source info table so dc_interrupt_set()/_ack() + * Populate the per-source info table so amdgpu_dm_irq_set()/_ack() * succeed without touching hardware registers. */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, @@ -1227,7 +1227,7 @@ static void dm_test_irq_suspend_empty(struct kunit *test) KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); /* - * With no registered handlers the HW dc_interrupt_set() calls are + * With no registered handlers the amdgpu_dm_irq_set() calls are * skipped, so suspend must complete without touching the (absent) DC. */ amdgpu_dm_irq_suspend(adev); @@ -1275,11 +1275,11 @@ static void dm_test_irq_resume_late_empty(struct kunit *test) } /** - * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set path + * dm_test_irq_suspend_registered - Test suspend reaches the irq set path * @test: The KUnit test context * * Registers a low-context HPD handler so the handler list is non-empty, - * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with + * forcing amdgpu_dm_irq_suspend() to call amdgpu_dm_irq_set() (NULL-safe with * no DC) and flush_work() on the registered handler. */ static void dm_test_irq_suspend_registered(struct kunit *test) @@ -1330,11 +1330,11 @@ static void dm_test_irq_suspend_disables_polling(struct kunit *test) } /** - * dm_test_irq_resume_early_registered - Test early resume reaches dc_interrupt_set + * dm_test_irq_resume_early_registered - Test early resume reaches irq set * @test: The KUnit test context * * Registers a low-context HPD RX handler so early resume calls - * dc_interrupt_set() for the short-pulse interrupt source. + * amdgpu_dm_irq_set() for the short-pulse interrupt source. */ static void dm_test_irq_resume_early_registered(struct kunit *test) { @@ -1358,10 +1358,10 @@ static void dm_test_irq_resume_early_registered(struct kunit *test) } /** - * dm_test_irq_resume_late_registered - Test late resume reaches dc_interrupt_set + * dm_test_irq_resume_late_registered - Test late resume reaches irq set * @test: The KUnit test context * - * Registers a low-context HPD handler so late resume calls dc_interrupt_set() + * Registers a low-context HPD handler so late resume calls amdgpu_dm_irq_set() * for the HPD interrupt source. */ static void dm_test_irq_resume_late_registered(struct kunit *test) @@ -1592,7 +1592,7 @@ static void dm_test_set_crtc_irq_state_enable(struct kunit *test) /* * otg_inst >= 0 computes the irq source and reaches the NULL-safe - * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + * amdgpu_dm_irq_set(); the ips_support branch is skipped (dc == NULL). */ acrtc->otg_inst = 3; adev->mode_info.crtcs[0] = acrtc; @@ -1671,8 +1671,8 @@ static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) * * With a non-NULL DC that advertises IPS support and currently allows idle * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before - * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe - * early return, and per-source stub funcs let dc_interrupt_set() succeed. + * amdgpu_dm_irq_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let amdgpu_dm_irq_set() succeed. */ static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) { @@ -1891,7 +1891,7 @@ static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* dc_interrupt_set() is a no-op when dc is NULL, so both states + /* amdgpu_dm_irq_set() is a no-op when dc is NULL, so both states * return 0 without dereferencing the (absent) DC. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, @@ -1951,7 +1951,7 @@ static void dm_test_outbox_init_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + /* Single amdgpu_dm_irq_set() call must be skipped when dc is NULL. */ amdgpu_dm_outbox_init(adev); } @@ -1969,7 +1969,7 @@ static void dm_test_hpd_init_empty_connectors(struct kunit *test) /* * With an empty connector list the per-connector loop is skipped and - * the initial clear loop relies on dc_interrupt_set() being a no-op + * the initial clear loop relies on amdgpu_dm_irq_set() being a no-op * for a NULL dc, so init must complete without touching the DC. */ amdgpu_dm_hpd_init(adev); @@ -2004,7 +2004,7 @@ static void dm_test_hpd_init_fini_with_connectors(struct kunit *test) /* * num_hpd = 0 forces irq_type >= num_hpd so the loop takes the HW - * fallback (dc_interrupt_set()) instead of amdgpu_irq_get(); with a + * fallback (amdgpu_dm_irq_set()) instead of amdgpu_irq_get(); with a * NULL dc that fallback is a safe no-op. */ adev->mode_info.num_hpd = 0; @@ -2090,7 +2090,7 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) /* * num_hpd >= 1 makes irq_type (0) < num_hpd, so the loop takes the * amdgpu_irq_get()/amdgpu_irq_put() branch instead of the - * dc_interrupt_set() fallback. The mock device has irq.installed == + * amdgpu_dm_irq_set() fallback. The mock device has irq.installed == * false, so both calls fail early with -ENOENT (logging an error) * without touching the base-driver irq state. */ From 5f28bb1c2cd9dcdb76a20d61b3ea069b85893c59 Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Wed, 12 Aug 2026 13:19:46 -0400 Subject: [PATCH 03/12] drm/amdkfd: implement restore_mqd callbacks for GFX12/12.1 kfd_mqd_manager_v12.c (GFX 12.0) and kfd_mqd_manager_v12_1.c (GFX 12.1) do not implement restore_mqd callbacks, leaving the function pointers NULL and causing CRIU restore to return -EOPNOTSUPP on GFX12. Implement restore_mqd for both compute and SDMA queues in kfd_mqd_manager_v12.c and kfd_mqd_manager_v12_1.c, modeled after the GFX 11 implementation with the following improvements: - update cp_mqd_base_addr_lo/hi to the newly allocated MQD address, fixing a pre-existing gap shared with v11 where the in-MQD copy still pointed at the old checkpoint-time address after restore - memset the full allocation before memcpy for compute queues to avoid stale data in the GTT sub-allocator tail; SDMA MQDs use sizeof(*m) since they are packed at mqd_size stride in a shared BO checkpoint_mqd registration is deferred to a follow-up patch that also implements get_checkpoint_info, so that checkpoint and restore are enabled together as a complete and testable unit. Note: GFX12.1 restore handles XCC0 only. Multi-XCC CRIU restore is currently unreachable due to a separate validation issue in kfd_criu_restore_queue(). A pr_warn_once() is emitted if a multi-XCC device is encountered. Signed-off-by: Vladimir Marioukhine Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit b1f9601237d050f5df478464cf51bf1fff29a256) Cc: stable@vger.kernel.org --- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 7 +- .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 59 ++++++++++++++++ .../drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 68 +++++++++++++++++++ 3 files changed, 132 insertions(+), 2 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 9811e4e10291..2f78395a0c31 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -770,10 +770,12 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto deallocate_vmid; } + if (q->properties.type == KFD_QUEUE_TYPE_COMPUTE) { retval = allocate_hqd(dqm, q); if (retval) @@ -2250,7 +2252,8 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto out_deallocate_doorbell; } 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 7c387fa90076..63f25a60baa5 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -380,6 +380,63 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_compute_mqd *m; + + m = (struct v12_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -407,6 +464,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_compute_mqd); mqd->get_wave_state = get_wave_state; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -453,6 +511,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif 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 431a940f91f3..708bbb08c089 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 @@ -641,6 +641,72 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_1_compute_mqd *m; + + /* + * GFX12.1 is multi-XCC capable but this restore handles XCC0 only. + * Multi-XCC CRIU restore is currently unreachable because + * kfd_criu_restore_queue() validates against unscaled mqd_size. + */ + if (NUM_XCC(mm->dev->xcc_mask) > 1) + pr_warn_once("GFX12.1 multi-XCC CRIU restore not fully supported\n"); + + m = (struct v12_1_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size) * + NUM_XCC(mm->dev->xcc_mask)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_SDMA_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -668,6 +734,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_1_compute_mqd); mqd->get_wave_state = get_wave_state_v12_1; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -714,6 +781,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif From 636139603b99d2e3a18a46cf3f8d39313ce8042e Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Sat, 12 Sep 2026 00:29:08 +0100 Subject: [PATCH 04/12] drm/amdgpu: hold a runtime PM reference for P2P dma-buf attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_dma_buf_map() adds VRAM to the allowed domains for a peer2peer attachment. GTT is only a fallback placement when VRAM is preferred, so ttm_bo_validate() migrates the buffer from GTT into VRAM. While the exporting device is runtime suspended its SDMA rings are down and the move fails: amdgpu: Move buffer fallback to memcpy unavailable An importer on a second GPU reaches this holding no runtime PM reference on the exporter, e.g. a compositor on the APU submitting a frame that references a buffer exported by an idle dGPU: amdgpu_cs_ioctl -> amdgpu_cs_parser_bos -> amdgpu_cs_bo_validate -> ttm_bo_validate -> amdgpu_bo_move -> dma_buf_map_attachment -> amdgpu_dma_buf_map -> ttm_bo_validate -> amdgpu_bo_move Pinning a dma-buf into VRAM has the same requirement, which commit 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") called out as the one case that would need the reference back. Take it in attach and drop it in detach. pm_runtime_get_if_active() never resumes the device, so it cannot deadlock against the reservation taken during resume, which is why the old pm_runtime_get_sync() had to go. If the device is not active, clear peer2peer instead: the buffer then stays in GTT, which remains accessible while the GPU is powered down. If runtime PM is disabled, take a plain reference so the put in detach stays balanced. Fixes: 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Mike Lothian Assisted-by: Claude:Opus-5 [Claude Code] Signed-off-by: Alex Deucher (cherry picked from commit 062ff15e30a48d14fb7d7558eba84f8dc97197f0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c | 43 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c index b33c300e26e2..9adf3eed8822 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c @@ -43,6 +43,7 @@ #include #include #include +#include static const struct dma_buf_attach_ops amdgpu_dma_buf_attach_ops; @@ -100,15 +101,54 @@ static int amdgpu_dma_buf_attach(struct dma_buf *dmabuf, pci_p2pdma_distance(adev->pdev, attach->dev, false) < 0) attach->peer2peer = false; + /* + * Only allow P2P while the exporter is active, and keep it active + * until detach. With runtime PM disabled take a plain reference so + * the put in detach stays balanced. + */ + if (attach->peer2peer) { + struct device *dev = adev_to_drm(adev)->dev; + int ret = pm_runtime_get_if_active(dev); + + if (!ret) + attach->peer2peer = false; + else if (ret < 0) + pm_runtime_get_noresume(dev); + } + r = dma_resv_lock(bo->tbo.base.resv, NULL); if (r) - return r; + goto err_pm_put; amdgpu_vm_bo_update_shared(bo); dma_resv_unlock(bo->tbo.base.resv); return 0; + +err_pm_put: + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); + return r; +} + +/** + * amdgpu_dma_buf_detach - &dma_buf_ops.detach implementation + * + * @dmabuf: DMA-buf where we remove the attachment from + * @attach: the attachment to remove + * + * Drop the runtime PM reference taken in amdgpu_dma_buf_attach(). + */ +static void amdgpu_dma_buf_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attach) +{ + struct drm_gem_object *obj = dmabuf->priv; + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); + + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); } /** @@ -350,6 +390,7 @@ static void amdgpu_dma_buf_vunmap(struct dma_buf *dma_buf, struct iosys_map *map const struct dma_buf_ops amdgpu_dmabuf_ops = { .attach = amdgpu_dma_buf_attach, + .detach = amdgpu_dma_buf_detach, .pin = amdgpu_dma_buf_pin, .unpin = amdgpu_dma_buf_unpin, .map_dma_buf = amdgpu_dma_buf_map, From 723d4dc628d764b19cf9efca14b82cca5ff020c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Chumachenko Date: Mon, 14 Sep 2026 17:33:03 +0300 Subject: [PATCH 05/12] drm/amdgpu: check ras and obj before dereference nbio_v7_9_handle_ras_controller_intr_no_bifring() dereferences ras and obj without checking either for NULL. Both amdgpu_ras_get_context() and amdgpu_ras_find_obj() can return NULL, e.g. during the window between adev->nbio.ras being set (early in amdgpu_ras_init(), by design, to enable the fatal-error interrupt as soon as possible) and the PCIE_BIF ras object actually being created in RAS late_init. Any interrupt in that window crashes in hard-IRQ context. This is analogous to commit d190b459b2a4 ("drm/amdgpu: the warning dereferencing obj for nbio_v7_4"), which fixed the same issue in the nbio_v7_4 handler. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7692e1ee2446 ("drm/amdgpu: add RAS fatal error handler for NBIO v7.9") Reviewed-by: Tao Zhou Signed-off-by: Dmitriy Chumachenko Signed-off-by: Alex Deucher (cherry picked from commit c7071767a50a32ed727cf800ac84372429e3b4b3) --- drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c index bdfd2917e3ca..def02993b7cf 100644 --- a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c +++ b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c @@ -535,7 +535,7 @@ static void nbio_v7_9_handle_ras_controller_intr_no_bifring(struct amdgpu_device RAS_CNTLR_INTERRUPT_CLEAR, 1); WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_DOORBELL_INT_CNTL, bif_doorbell_intr_cntl); - if (!ras->disable_ras_err_cnt_harvest) { + if (ras && !ras->disable_ras_err_cnt_harvest && obj) { /* * clear error status after ras_controller_intr * according to hw team and count ue number From 04de4007d32385b8b6a5dd72bff3146dfdc592c3 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 10:59:45 -0500 Subject: [PATCH 06/12] drm/amdgpu: Fix GPU PCIe link capability reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") made amdgpu_device_gpu_bandwidth() query the first device outside the dGPU. That is the host side of the physical link, not the GPU side. As a result, the ASIC and platform capability masks can both be based on the host port. drm_amdgpu_info_device then exposes the host capabilities to userspace, such as Gen5 x16 for a Gen4 x8 GPU. Cache both ends of the physical link during device initialization. Use link_dev for the GPU capability and link_partner for the platform capability and _PR3 detection. Reported-by: "Marek Olšák" Closes: https://lore.kernel.org/amd-gfx/CAAxE2A4VhsAzzO1QjBjUg+NgnbD04ZzMyN6xsUJxjKJHH6hxiw@mail.gmail.com/ Suggested-by: Lijo Lazar Fixes: eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 7ea6a47224e2c6e89a3a682d7fbaace4817a55aa) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 48 ++++++++-------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 7974f9b7944f..a9c6f5d4a639 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -621,6 +621,9 @@ enum amdgpu_enforce_isolation_mode { struct amdgpu_device { struct device *dev; struct pci_dev *pdev; + /* The two ends of the physical PCIe link outside the device. */ + struct pci_dev *link_dev; + struct pci_dev *link_partner; struct drm_device ddev; #ifdef CONFIG_DRM_AMD_ACP diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 104d1d2cbad9..933804349dbf 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1954,18 +1954,17 @@ static void amdgpu_uid_fini(struct amdgpu_device *adev) adev->uid_info = NULL; } -static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) +static void amdgpu_device_init_pcie_links(struct amdgpu_device *adev) { - struct pci_dev *parent = adev->pdev; + adev->link_dev = adev->pdev; + adev->link_partner = pci_upstream_bridge(adev->link_dev); - /* skip upstream/downstream switches internal to dGPU */ - while ((parent = pci_upstream_bridge(parent))) { - if (parent->vendor == PCI_VENDOR_ID_ATI) - continue; - break; + /* Skip upstream/downstream switches internal to the dGPU. */ + while (adev->link_partner && + adev->link_partner->vendor == PCI_VENDOR_ID_ATI) { + adev->link_dev = adev->link_partner; + adev->link_partner = pci_upstream_bridge(adev->link_dev); } - - return parent; } /** @@ -1981,7 +1980,6 @@ static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) { struct amdgpu_ip_block *ip_block; - struct pci_dev *parent; bool total, skip_bios, early_full_gpu_access = false; uint32_t bios_flags; int i, r; @@ -2077,10 +2075,9 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) !dev_is_removable(&adev->pdev->dev)) adev->flags |= AMD_IS_PX; - if (!(adev->flags & AMD_IS_APU)) { - parent = amdgpu_device_find_parent(adev); - adev->has_pr3 = parent ? pci_pr3_present(parent) : false; - } + if (!(adev->flags & AMD_IS_APU)) + adev->has_pr3 = adev->link_partner && + pci_pr3_present(adev->link_partner); adev->pm.pp_feature = amdgpu_pp_feature_mask; if (amdgpu_sriov_vf(adev) || sched_policy == KFD_SCHED_POLICY_NO_HWS) @@ -3776,6 +3773,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, adev->shutdown = false; adev->flags = flags; + amdgpu_device_init_pcie_links(adev); if (amdgpu_force_asic_type >= 0 && amdgpu_force_asic_type < CHIP_LAST) adev->asic_type = amdgpu_force_asic_type; @@ -5872,11 +5870,9 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev, *width = PCIE_LNK_WIDTH_UNKNOWN; if (amdgpu_device_pcie_dynamic_switching_supported(adev)) { - struct pci_dev *parent = amdgpu_device_find_parent(adev); - - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); + if (adev->link_partner) { + *speed = pcie_get_speed_cap(adev->link_partner); + *width = pcie_get_width_cap(adev->link_partner); } } else { /* use the current speeds rather than max if switching is not supported */ @@ -5898,21 +5894,11 @@ static void amdgpu_device_gpu_bandwidth(struct amdgpu_device *adev, enum pci_bus_speed *speed, enum pcie_link_width *width) { - struct pci_dev *parent = adev->pdev; - if (!speed || !width) return; - /* use the device itself */ - *speed = pcie_get_speed_cap(adev->pdev); - *width = pcie_get_width_cap(adev->pdev); - - /* use the link outside the device */ - parent = amdgpu_device_find_parent(adev); - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); - } + *speed = pcie_get_speed_cap(adev->link_dev); + *width = pcie_get_width_cap(adev->link_dev); } /** From c883d0a132d430ef7ebb23fd94323be94d0fbdb8 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:51:35 -0400 Subject: [PATCH 07/12] drm/amdkfd: Avoid integer underflow with ffs in EOP ring size calc The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1 But ffs can in theory return 1 or 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4) using properties of logarithms. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit 4f18c56630383c14bfc6b2d65f88f2f895d2121a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 2 +- 4 files changed, 4 insertions(+), 4 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 e034da638c07..4f8a8a1a6186 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c @@ -204,7 +204,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 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 350fcbbba4b2..bf015dc5b868 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c @@ -242,7 +242,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 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 63f25a60baa5..6ea09b031caf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -217,7 +217,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 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 708bbb08c089..c709db0210ce 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 @@ -295,7 +295,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 8ee521b8b189799e361d4233c5180ba56656d4d4 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:16:51 -0400 Subject: [PATCH 08/12] drm/amdkfd: Avoid integer underflow in EOP ring size calculation. The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as order_base_2(q->eop_ring_buffer_size / 4) - 1 But order_base_2 can in theory return 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to order_base_2(q->eop_ring_buffer_size / 8) using properties of logarithms. Also add to the above comment to make the mathematics more clear. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit f0f43fcf8b2b3a924cad9444340921c96ed5f634) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 6 +++++- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) 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 b95720198e28..6e6bc1ec0b64 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -285,6 +285,10 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, 1 << CP_HQD_IB_CONTROL__IB_EXE_DISABLE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. + * * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -296,7 +300,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? - min(0xA, order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + min(0xA, order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); 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 60b87a500698..029572548c14 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c @@ -208,6 +208,9 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, mtype << CP_HQD_IB_CONTROL__MTYPE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -215,7 +218,7 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control |= q->eop_ring_buffer_size ? min(0xA, - order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 0d2f4cfa564355fcbbc71498fd8ec09243036109 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Tue, 15 Sep 2026 14:07:39 +0530 Subject: [PATCH 09/12] drm/amd/display: Fix NULL dereference in dcn50/dcn60 init_hw dc->clk_mgr is checked for NULL earlier in dcn50_init_hw() and dcn60_init_hw(), but dcn50_initialize_min_clocks() and dcn401_initialize_min_clocks() are called without any guard, causing Smatch to report potential NULL dereferences. Guard both call sites with the same pattern used throughout both functions: if (dc->clk_mgr && dc->clk_mgr->funcs) Also fix dcn50_initialize_min_clocks() which calls get_dispclk_from_dentist without checking the function pointer, unlike the dcn401 equivalent which guards that call. Fix kernel-doc in dcn60_hwseq.c by adding missing parameter descriptions for @probe in dcn60_update_probe_status() and @type in is_probe_measurement_type_for_hubbub(). Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Aurabindo Pillai Cc: Ivan Lipski Cc: Dan Wheeler Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 325c9a827cdd748e126eafeadaffc556204773d2) --- drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c | 6 ++++-- drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c index a7f8fd03faea..e549556b9679 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c @@ -67,7 +67,8 @@ static void dcn50_initialize_min_clocks(struct dc *dc) * audio corruption. Read current DISPCLK from DENTIST and request the same * freq to ensure that the timing is valid and unchanged. */ - clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); + if (dc->clk_mgr->funcs->get_dispclk_from_dentist) + clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); } clocks->ref_dtbclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dtbclk_mhz * 1000; clocks->fclk_p_state_change_support = true; @@ -639,7 +640,8 @@ void dcn50_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn50_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn50_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c index 72c2d3ca52f6..61ad6efa7a74 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c @@ -643,7 +643,8 @@ void dcn60_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn401_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn401_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * @@ -1001,6 +1002,7 @@ static void dcn60_build_hubbub_perfmon_sequence( /** * dcn60_update_probe_status - Set the valid flag on a latched probe result. * @status: result sink whose u was written by the GET BLS step during execute + * @probe: current probe state used to determine measurement type and validity */ static void dcn60_update_probe_status(struct dc_probe_status *status) { @@ -1024,6 +1026,7 @@ static void dcn60_update_probe_status(struct dc_probe_status *status) /** * is_probe_measurement_type_for_hubbub - Returns true if the probe type is * served by the hubbub perfmon block on DCN60. + * @type: the probe measurement type to classify */ static bool is_probe_measurement_type_for_hubbub(enum dc_probe_type type) { From 7f9caa70aef0950e06d395ca0035831214d88187 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 12:51:24 -0500 Subject: [PATCH 10/12] drm/amdgpu: Skip KFD mapping clear before initialization amdgpu_amdkfd_clear_kfd_mapping() assumes that a non-NULL kfd_dev has a fully populated node array. This is not true when KFD device initialization fails after probe. For example, kgd2kfd_device_init() sets num_nodes before checking PCIe atomics support. On Polaris systems without the required atomics, it returns before allocating nodes[0], but the kfd_dev remains attached to the amdgpu device. A later GPU reset then dereferences nodes[0]->id. Require the authoritative KFD initialization flag before walking the node array, matching the existing KFD reset and teardown paths. Fixes: 70cadefcc616 ("drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5833 Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 4ac1835823c47903fbb278bbf474773c46f59edc) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 816d8817f0b2..054870e9078d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -330,7 +330,7 @@ void amdgpu_amdkfd_clear_kfd_mapping(struct amdgpu_device *adev) struct kfd_dev *kfd = adev->kfd.dev; unsigned int i; - if (!kfd) + if (!kfd || !kfd->init_complete) return; for (i = 0; i < kfd->num_nodes; i++) { From 5155002b03b24ba3ef91c5c313b8cf0171b24904 Mon Sep 17 00:00:00 2001 From: Chengjun Yao Date: Tue, 8 Sep 2026 10:15:43 +0800 Subject: [PATCH 11/12] drm/amdgpu: fix rmmio iounmap skipped on device removal amdgpu_pci_remove() calls drm_dev_unplug() before fini_sw(), so drm_dev_enter() is already false there and the iounmap() guarded by it is skipped. This .remove path runs on both hot-unplug and plain rmmod, so the register BAR ioremap mapping leaks one instance per unload. Unmap rmmio unconditionally (guard only on non-NULL) and drop the now unused idx. Fixes: 62d5f9f7110a ("drm/amdgpu: Unmap MMIO mappings when device is not unplugged") Signed-off-by: Chengjun Yao Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher (cherry picked from commit dd6f86a97260e5207d3329ad03aa89fdad61b1e6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 933804349dbf..9269e780feb7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4335,7 +4335,7 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) void amdgpu_device_fini_sw(struct amdgpu_device *adev) { - int i, idx; + int i; bool px; amdgpu_device_ip_fini(adev); @@ -4377,11 +4377,9 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev) if ((adev->pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) vga_client_unregister(adev->pdev); - if (drm_dev_enter(adev_to_drm(adev), &idx)) { - + if (adev->rmmio) { iounmap(adev->rmmio); adev->rmmio = NULL; - drm_dev_exit(idx); } if (IS_ENABLED(CONFIG_PERF_EVENTS)) From 2ac2fe765ef475f409616ac0b57c4a3922749b0f Mon Sep 17 00:00:00 2001 From: Francis Marlou Pacaro Date: Fri, 4 Sep 2026 08:20:21 +0800 Subject: [PATCH 12/12] drm/amd/display: fix MALL hysteresis timer underflow at high refresh rates dcn30_apply_idle_power_optimizations() derives the MALL frame cache hysteresis timer with tmr_delay = (uint32_t)(div_u64(..., denom) - 64LL); div_u64() returns a u64, so when the quotient is smaller than 64 the subtraction wraps instead of going negative and tmr_delay ends up huge. The loop that follows tries to squeeze it into the 6 bit register field by doubling denom, but that only makes the quotient smaller, so tmr_delay can never converge. tmr_scale is bumped past 3 and the function gives up with /* Delay exceeds range of hysteresis timer */ ASSERT(false); even though the requested delay is too *short* to encode, not too long. With mall_additional_timer_percent left at its default of 0, the quotient drops below 64 once the refresh rate used for the calculation goes above ~243 Hz. Every DCN 3.0 display above that loses MALL static screen entirely and splats a WARN once per boot. Reproduced on Navi 23 (RX 6600) driving 1920x1080, resetting /sys/kernel/debug/clear_warn_once between modes: refresh MALL ASSERT 144 Hz enabled no 240 Hz enabled no 280 Hz skipped yes 360 Hz skipped yes Commit 3bb68cec4db8 ("drm/amd/display: Add Overflow check to skip MALL") already covered the other end of the range, where a large stutter period makes the delay too long to encode. Cover the short end by clamping to 0, which selects the shortest hysteresis the register can express, 65.28us * 64 = ~4.18ms. That is marginally longer than what the formula asks for at these refresh rates, and erring long is the safe direction: it only delays MALL entry, it can never enter early. The numerator does not change between iterations, only denom does, so compute it once and keep both call sites inside 100 columns. The genuinely out of range case at very low refresh rates still reaches the ASSERT, which is where it belongs. Fixes: 52f2e83e2fe5 ("drm/amdgpu/display: add MALL support (v2)") Signed-off-by: Francis Marlou Pacaro Reviewed-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit 387550e53e1405f1f960b62b22f8783db17c8e1d) --- .../gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index cb163902e12e..d669b47af120 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 @@ -1064,10 +1064,12 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) */ unsigned int denom = refresh_hz * 6528; unsigned int stutter_period = dc->current_state->perf_params.stutter_period_us; + uint64_t num = (1000000LL + 2 * stutter_period * refresh_hz) * + (100LL + dc->debug.mall_additional_timer_percent); + uint64_t tmr_ticks; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; /* In some cases the stutter period is really big (tiny modes) in these * cases MALL cant be enabled, So skip these cases to avoid a ASSERT() @@ -1089,9 +1091,8 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) } denom *= 2; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; } /* Copy HW cursor */