From b1bbfb6c0245cd35ad197d290e26ee1cdee467ff Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 24 Sep 2026 14:30:43 +0200 Subject: [PATCH 01/52] drm/panthor: Don't invalidate OTHER caches Don't invalidate the OTHER caches (AKA L1 read-only caches) since this seems to trip out the flush-elimination logic on v13, and it's not needed in practice (can't leak other context data because of TLB invalidation or corrupt physical pages returned to the system because it's a RO cache). It's also worth noting that the downstream driver has been avoid OTHER caches invalidation from the start, and that we were doing the same until atomic page table update was introduced. Fixes: 6e2d3b3e8589 ("drm/panthor: Add support for atomic page table updates") Signed-off-by: Boris Brezillon Reviewed-by: Liviu Dudau [Update comment as suggested by sashiko] Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260924-panthor-mmu-fixes-v1-1-e25f9b106466@collabora.com --- drivers/gpu/drm/panthor/panthor_mmu.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index d75d575473da..dd7642872d59 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -630,7 +630,7 @@ static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr, /* Flush+invalidate RW caches, invalidate RO ones. */ ret = panthor_gpu_flush_caches(ptdev, CACHE_CLEAN | CACHE_INV, - CACHE_CLEAN | CACHE_INV, CACHE_INV); + CACHE_CLEAN | CACHE_INV, 0); if (ret) return ret; @@ -1832,13 +1832,12 @@ static void panthor_vm_unlock_region(struct panthor_vm *vm) if (vm->as.id >= 0) { int ret; - /* flush+invalidate RW caches and invalidate RO ones. + /* flush+invalidate RW caches. * TODO: See if we can use FLUSH_PA_RANGE when the physical * range is narrow enough and the HW supports it. */ ret = panthor_gpu_flush_caches(ptdev, CACHE_CLEAN | CACHE_INV, - CACHE_CLEAN | CACHE_INV, - CACHE_INV); + CACHE_CLEAN | CACHE_INV, 0); /* Unlock the region if the flush is effective. */ if (!ret) From a431d4ec5d938893060f69a018a2c47651c282d9 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 24 Sep 2026 14:30:44 +0200 Subject: [PATCH 02/52] drm/panthor: Fully disable the AS even if it's going to be re-assigned We're trying to be smart by skipping the UPDATE(UNMAPPED) step, but it remains to be proven it adds any noticeable overhead. Moreover, it's breaking the assumption that, once the VM is unbound, no access can happen on it. For instance, say the panthor_mmu_as_enable() call in panthor_vm_active() fails after we've called panthor_vm_release_as_locked(), we're now in a state where the HW still points to the old page table, but SW slot points to the new VM, which is not truly HW-bound. If, after the as_slots lock is released, the evicted VM itself is released, the HW might have access to memory that has been returned to the system until the reset we scheduled (because of the faulty AS_COMMAND) is effective. Let's make this bullet-proof by doing a full bound -> unbound -> bound cycle on AS slot recycling. Fixes: 32e593d74c39 ("drm/panthor: Make sure caches are flushed/invalidated when an AS is recycled") Signed-off-by: Boris Brezillon Reviewed-by: Liviu Dudau Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260924-panthor-mmu-fixes-v1-2-e25f9b106466@collabora.com --- drivers/gpu/drm/panthor/panthor_mmu.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index dd7642872d59..fe7d92de43f8 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -616,8 +616,7 @@ static int panthor_mmu_as_enable(struct panthor_device *ptdev, u32 as_nr, return as_send_cmd_and_wait(ptdev, as_nr, AS_COMMAND_UPDATE); } -static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr, - bool recycle_slot) +static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr) { struct panthor_mmu *mmu = ptdev->mmu; struct panthor_vm *vm = ptdev->mmu->as.slots[as_nr].vm; @@ -641,12 +640,6 @@ static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr, return ret; } - /* If the slot is going to be used immediately, don't bother changing - * the config. - */ - if (recycle_slot) - return 0; - gpu_write64(mmu->iomem, AS_TRANSTAB(as_nr), 0); gpu_write64(mmu->iomem, AS_MEMATTR(as_nr), 0); gpu_write64(mmu->iomem, AS_TRANSCFG(as_nr), AS_TRANSCFG_ADRMODE_UNMAPPED); @@ -773,7 +766,7 @@ int panthor_vm_active(struct panthor_vm *vm) drm_WARN_ON(&ptdev->base, refcount_read(&lru_vm->as.active_cnt)); as = lru_vm->as.id; - ret = panthor_mmu_as_disable(ptdev, as, true); + ret = panthor_mmu_as_disable(ptdev, as); if (ret) goto out_unlock; @@ -915,7 +908,7 @@ static void panthor_vm_declare_unusable(struct panthor_vm *vm) vm->unusable = true; mutex_lock(&ptdev->mmu->as.slots_lock); if (vm->as.id >= 0 && drm_dev_enter(&ptdev->base, &cookie)) { - panthor_mmu_as_disable(ptdev, vm->as.id, false); + panthor_mmu_as_disable(ptdev, vm->as.id); drm_dev_exit(cookie); } mutex_unlock(&ptdev->mmu->as.slots_lock); @@ -1908,7 +1901,7 @@ static void panthor_mmu_irq_handler(struct panthor_irq *pirq, u32 status) ptdev->mmu->as.slots[as].vm->unhandled_fault = true; /* Disable the MMU to kill jobs on this AS. */ - panthor_mmu_as_disable(ptdev, as, false); + panthor_mmu_as_disable(ptdev, as); mutex_unlock(&ptdev->mmu->as.slots_lock); status &= ~mask; @@ -1942,7 +1935,7 @@ void panthor_mmu_suspend(struct panthor_device *ptdev) if (vm) { drm_WARN_ON(&ptdev->base, - panthor_mmu_as_disable(ptdev, i, false)); + panthor_mmu_as_disable(ptdev, i)); panthor_vm_release_as_locked(vm); } } @@ -2067,7 +2060,7 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm) int cookie; if (drm_dev_enter(&ptdev->base, &cookie)) { - panthor_mmu_as_disable(ptdev, vm->as.id, false); + panthor_mmu_as_disable(ptdev, vm->as.id); drm_dev_exit(cookie); } @@ -3361,7 +3354,7 @@ void panthor_mmu_unplug(struct panthor_device *ptdev) if (vm) { drm_WARN_ON(&ptdev->base, - panthor_mmu_as_disable(ptdev, i, false)); + panthor_mmu_as_disable(ptdev, i)); panthor_vm_release_as_locked(vm); } } From 9a96e169c9267af918d28fb291df948d83112807 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 24 Sep 2026 14:30:45 +0200 Subject: [PATCH 03/52] drm/panthor: Move cache-flush after UPDATE(UNMAPPED) Right now, there's a theoretical window during which the GPU can populate the cache with data from a VM that's about to be evicted through the UPDATE(UNMAPPED) command. In practice this won't happen (hence the absence of Fixes tag) because when panthor_mmu_as_disable() is called, the VM is guaranteed to be idle (no active CSG pointing to this VM), but as we say, better safe than sorry. Signed-off-by: Boris Brezillon Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260924-panthor-mmu-fixes-v1-3-e25f9b106466@collabora.com --- drivers/gpu/drm/panthor/panthor_mmu.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index fe7d92de43f8..86d0b8f9e7d7 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -627,12 +627,6 @@ static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr) panthor_irq_disable_events(&ptdev->mmu->irq, panthor_mmu_as_fault_mask(ptdev, as_nr)); - /* Flush+invalidate RW caches, invalidate RO ones. */ - ret = panthor_gpu_flush_caches(ptdev, CACHE_CLEAN | CACHE_INV, - CACHE_CLEAN | CACHE_INV, 0); - if (ret) - return ret; - if (vm && vm->locked_region.size) { /* Unlock the region if there's a lock pending. */ ret = as_send_cmd_and_wait(ptdev, vm->as.id, AS_COMMAND_UNLOCK); @@ -644,7 +638,18 @@ static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr) gpu_write64(mmu->iomem, AS_MEMATTR(as_nr), 0); gpu_write64(mmu->iomem, AS_TRANSCFG(as_nr), AS_TRANSCFG_ADRMODE_UNMAPPED); - return as_send_cmd_and_wait(ptdev, as_nr, AS_COMMAND_UPDATE); + ret = as_send_cmd_and_wait(ptdev, as_nr, AS_COMMAND_UPDATE); + if (ret) + return ret; + + /* Flush+invalidate RW caches after we've unmapped, to make sure any + * cacheline eviction is effective before we potentially return + * memory pointed by this VM to the system. This needs to be done + * after the UPDATE(UNMAPPED) operation to guarantee that not further + * PT-walk can pull VM data into the cache. + */ + return panthor_gpu_flush_caches(ptdev, CACHE_CLEAN | CACHE_INV, + CACHE_CLEAN | CACHE_INV, 0); } static u32 panthor_mmu_fault_mask(struct panthor_device *ptdev, u32 value) From 4aac9c8fc95c5c2a67edd7f99697967a480247ed Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Sat, 12 Sep 2026 20:57:10 +0800 Subject: [PATCH 04/52] drm/amd/pm: Update smu_v15_0_8 PMFW headers Update smu_v15_0_8 PMFW headers to include NPM metrics Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 1ee4f4a88e9c0e4106643018222c9a7db9fd680b) --- .../drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_pmfw.h | 12 +++++++++--- .../drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h | 4 +++- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_pmfw.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_pmfw.h index a3401c4cc20b..61d76ebfdbd4 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_pmfw.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_pmfw.h @@ -328,7 +328,7 @@ typedef struct __attribute__((packed, aligned(4))) { uint64_t GfxclkLowUtilizationAcc[8]; } MetricsTable_t; -#define SMU_SYSTEM_METRICS_TABLE_VERSION 0x1 +#define SMU_SYSTEM_METRICS_TABLE_VERSION 0x2 #pragma pack(push, 4) typedef struct { @@ -343,9 +343,13 @@ typedef struct { //NPM: NODE POWER MANAGEMENT uint32_t NodePowerLimit; uint32_t NodePower; - uint32_t GlobalPPTResidencyAcc; + uint32_t NPMResidencyAcc; uint16_t SystemPower[SYSTEM_POWER_MAX_ENTRIES]; // UBB Current Power and Power Threshold + + // NPM flags + uint32_t NpmSupported; + uint32_t NpmEnabled; } SystemMetricsTable_t; #pragma pack(pop) @@ -368,7 +372,7 @@ typedef struct __attribute__((aligned(4))) { uint8_t FruId[PRODUCT_FRU_ID_LEN]; } FRUProductInfo_t; -#define SMU_STATIC_METRICS_TABLE_VERSION 0x1 +#define SMU_STATIC_METRICS_TABLE_VERSION 0x2 #pragma pack(push, 4) typedef struct { @@ -421,6 +425,8 @@ typedef struct { uint32_t PPT1Max; uint32_t PPT1Min; uint32_t PPT1Default; + + uint32_t MaxNodePowerLimit; } StaticMetricsTable_t; #pragma pack(pop) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h index 7ffb445f4c0c..bf73d10c0ce7 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h @@ -83,7 +83,9 @@ #define PPSMC_MSG_SetSoftMaxGl2clk 0x32 #define PPSMC_MSG_SetSoftMinFclk 0x33 #define PPSMC_MSG_SetSoftMaxFclk 0x34 -#define PPSMC_Message_Count 0x35 +/* NPM limit setter; NPM reads are served from the SystemMetricsTable. */ +#define PPSMC_MSG_SetNpmLimit 0x40 +#define PPSMC_Message_Count 0x41 /* PSMC Reset Types for driver msg argument */ #define PPSMC_RESET_TYPE_DRIVER_MODE_1_RESET 0x1 diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index 9e42c27f60ee..cd8e31e62c3f 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -622,7 +622,7 @@ static int smu_v15_0_8_get_npm_data(struct smu_context *smu, *value = !!SMUQ10_ROUND(metrics->NodePower); break; case AMDGPU_PP_SENSOR_GPPTRESIDENCY: - *value = SMUQ10_ROUND(metrics->GlobalPPTResidencyAcc); + *value = SMUQ10_ROUND(metrics->NPMResidencyAcc); break; default: return -EINVAL; From 1afded4c7642e3ad85895c9d55f53a4fb1fe5d3b Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Sat, 12 Sep 2026 21:13:50 +0800 Subject: [PATCH 05/52] drm/amd/pm: Add set_npm_limit function Wire up the set_npm_limit callback from pp_funcs through smu/dpm to the PPT layer. Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 65d4256d3cf016fc6dd136eee13280f824c84ea2) --- drivers/gpu/drm/amd/include/kgd_pp_interface.h | 1 + drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 18 ++++++++++++++++++ drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h | 1 + drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 14 ++++++++++++++ drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 6 ++++++ drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h | 3 ++- 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h index 4b863a2e38f1..ca818965afe1 100644 --- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h @@ -530,6 +530,7 @@ struct amd_pm_funcs { ssize_t (*get_temp_metrics)(void *handle, enum smu_temp_metric_type type, void *table); bool (*temp_metrics_is_supported)(void *handle, enum smu_temp_metric_type type); u64 (*get_npm_cap)(void *handle); + int (*set_npm_limit)(void *handle, uint32_t limit); ssize_t (*get_xcp_metrics)(void *handle, int xcp_id, void *table); ssize_t (*get_pm_metrics)(void *handle, void *pmmetrics, size_t size); int (*set_watermarks_for_clock_ranges)(void *handle, diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index df5bbdab1ea3..0394085a0328 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -2124,6 +2124,24 @@ u64 amdgpu_dpm_get_npm_cap(struct amdgpu_device *adev) return cap; } +int amdgpu_dpm_set_npm_limit(struct amdgpu_device *adev, uint32_t limit) +{ + const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; + int ret; + + if (!pp_funcs || !pp_funcs->set_npm_limit) + return -EOPNOTSUPP; + + if (amdgpu_sriov_vf(adev)) + return -EPERM; + + mutex_lock(&adev->pm.mutex); + ret = pp_funcs->set_npm_limit(adev->powerplay.pp_handle, limit); + mutex_unlock(&adev->pm.mutex); + + return ret; +} + /** * amdgpu_dpm_get_xcp_metrics - Retrieve metrics for a specific compute * partition diff --git a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h index 64d68a905f86..72b13ece22ef 100644 --- a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h +++ b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h @@ -617,5 +617,6 @@ bool amdgpu_dpm_reset_vcn_is_supported(struct amdgpu_device *adev); bool amdgpu_dpm_is_temp_metrics_supported(struct amdgpu_device *adev, enum smu_temp_metric_type type); u64 amdgpu_dpm_get_npm_cap(struct amdgpu_device *adev); +int amdgpu_dpm_set_npm_limit(struct amdgpu_device *adev, uint32_t limit); #endif diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index a65aa2129845..3bb36668eb39 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -3995,6 +3995,19 @@ static u64 smu_get_npm_cap(void *handle) return smu->ppt_funcs->get_npm_cap(smu); } +static int smu_set_npm_limit(void *handle, uint32_t limit) +{ + struct smu_context *smu = handle; + + if (!smu->pm_enabled || !smu->adev->pm.dpm_enabled) + return -EOPNOTSUPP; + + if (!smu->ppt_funcs || !smu->ppt_funcs->set_npm_limit) + return -EOPNOTSUPP; + + return smu->ppt_funcs->set_npm_limit(smu, limit); +} + static ssize_t smu_sys_get_xcp_metrics(void *handle, int xcp_id, void *table) { struct smu_context *smu = handle; @@ -4069,6 +4082,7 @@ static const struct amd_pm_funcs swsmu_pm_funcs = { .get_xcp_metrics = smu_sys_get_xcp_metrics, .get_temp_metrics = smu_sys_get_temp_metrics, .temp_metrics_is_supported = smu_temp_metrics_is_supported, + .set_npm_limit = smu_set_npm_limit, .get_npm_cap = smu_get_npm_cap, }; 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 1e5fc6ff76c8..f516d0d3bb92 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -1626,6 +1626,12 @@ struct pptable_funcs { * unsupported. */ u64 (*get_npm_cap)(struct smu_context *smu); + + /** + * @set_npm_limit: Set node power limit in watts. + */ + int (*set_npm_limit)(struct smu_context *smu, uint32_t limit); + /** * @ras_send_msg: Send a message with a parameter from Ras * &msg: Type of message. diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h index 2631e12b5e59..f86201764814 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h @@ -305,7 +305,8 @@ __SMU_DUMMY_MAP(SetSoftMaxFclk), \ __SMU_DUMMY_MAP(SetSoftMaxGl2clk), \ __SMU_DUMMY_MAP(SetSoftMinGl2clk), \ - __SMU_DUMMY_MAP(GetSystemMetricsVersion), + __SMU_DUMMY_MAP(GetSystemMetricsVersion), \ + __SMU_DUMMY_MAP(SetNpmLimit), #undef __SMU_DUMMY_MAP #define __SMU_DUMMY_MAP(type) SMU_MSG_##type From 0c8b2a9b97852779aff4ea7f3d62a395aaaf2477 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 17 Sep 2026 23:17:34 +0800 Subject: [PATCH 06/52] drm/amd/pm: Add NPM metrics reads and SetNpmLimit Add NPM metrics reads, SetNpmLimit for smu_v15_0_8 and enable NPM feature based on system metrics version v2: Move init caps during pptable init Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit f918ad983d61e5b14a02c43af78b2a45c04f96f2) --- .../drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c | 95 +++++++++++++++++-- .../drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h | 2 + 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index cd8e31e62c3f..8806c8f407e3 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -137,6 +137,7 @@ static const struct cmn2asic_msg_mapping smu_v15_0_8_message_map[SMU_MSG_MAX_COU MSG_MAP(SetSoftMaxGl2clk, PPSMC_MSG_SetSoftMaxGl2clk, 0), MSG_MAP(SetSoftMinFclk, PPSMC_MSG_SetSoftMinFclk, 0), MSG_MAP(SetSoftMaxFclk, PPSMC_MSG_SetSoftMaxFclk, 0), + MSG_MAP(SetNpmLimit, PPSMC_MSG_SetNpmLimit, 0), }; /* TODO: Update the clk map once enum PPCLK is updated in smu15_driver_if_v15_0_8.h */ @@ -588,19 +589,86 @@ static int smu_v15_0_8_get_system_metrics_table(struct smu_context *smu) return 0; } -static int smu_v15_0_8_get_npm_data(struct smu_context *smu, - enum amd_pp_sensors sensor, - uint32_t *value) +static inline void smu_v15_0_8_cap_set(struct smu_context *smu, u32 cap) +{ + struct smu_15_0_dpm_context *dpm_context = smu->smu_dpm.dpm_context; + + dpm_context->caps |= BIT_ULL(cap); +} + +static bool smu_v15_0_8_cap_supported(struct smu_context *smu, u32 cap) +{ + struct smu_15_0_dpm_context *dpm_context = smu->smu_dpm.dpm_context; + + return !!(dpm_context->caps & BIT_ULL(cap)); +} + +static void smu_v15_0_8_init_npm_cap(struct smu_context *smu) { struct smu_table_context *smu_table = &smu->smu_table; struct smu_table *tables = smu_table->tables; SystemMetricsTable_t *metrics; struct smu_table *sys_table; + uint32_t table_version; int ret; + if (smu->adev->gmc.xgmi.physical_node_id != 0) + return; + + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_GetSystemMetricsVersion, + &table_version); + if (ret) + return; + + if (table_version < SMU_SYSTEM_METRICS_TABLE_VERSION) + return; + + ret = smu_v15_0_8_get_system_metrics_table(smu); + if (ret) + return; + + sys_table = &tables[SMU_TABLE_PMFW_SYSTEM_METRICS]; + metrics = (SystemMetricsTable_t *)sys_table->cache.buffer; + if (!metrics->NpmSupported) + return; + + smu_v15_0_8_cap_set(smu, SMU_15_0_8_CAP_NPM); +} + +static void smu_v15_0_8_init_caps(struct smu_context *smu) +{ + smu_v15_0_8_init_npm_cap(smu); +} + +static u64 smu_v15_0_8_get_npm_cap(struct smu_context *smu) +{ + if (!smu_v15_0_8_cap_supported(smu, SMU_15_0_8_CAP_NPM)) + return 0; + + return AMDGPU_NPM_CAP_R(AMDGPU_NPM_CAP_CUR_NODE_POWER_LIMIT) | + AMDGPU_NPM_CAP_W(AMDGPU_NPM_CAP_CUR_NODE_POWER_LIMIT) | + AMDGPU_NPM_CAP_R(AMDGPU_NPM_CAP_NODE_POWER) | + AMDGPU_NPM_CAP_R(AMDGPU_NPM_CAP_GLOBAL_PPT_RESID) | + AMDGPU_NPM_CAP_R(AMDGPU_NPM_CAP_MAX_NODE_POWER_LIMIT) | + AMDGPU_NPM_CAP_R(AMDGPU_NPM_CAP_NPM_STATUS); +} + +static int smu_v15_0_8_get_npm_data(struct smu_context *smu, + enum amd_pp_sensors sensor, + uint32_t *value) +{ + struct smu_table_context *smu_table = &smu->smu_table; + PPTable_t *pptable = (PPTable_t *)smu_table->driver_pptable; + struct smu_table *tables = smu_table->tables; + SystemMetricsTable_t *metrics; + struct smu_table *sys_table; + int ret; + + if (!smu_v15_0_8_cap_supported(smu, SMU_15_0_8_CAP_NPM)) + return -EOPNOTSUPP; + if (sensor == AMDGPU_PP_SENSOR_MAXNODEPOWERLIMIT) { - /*TBD as of now put 0 */ - *value = 0; + *value = pptable->MaxNodePowerLimit; return 0; } @@ -619,7 +687,7 @@ static int smu_v15_0_8_get_npm_data(struct smu_context *smu, *value = SMUQ10_ROUND(metrics->NodePower); break; case AMDGPU_PP_SENSOR_NPMSTATUS: - *value = !!SMUQ10_ROUND(metrics->NodePower); + *value = !!metrics->NpmEnabled; break; case AMDGPU_PP_SENSOR_GPPTRESIDENCY: *value = SMUQ10_ROUND(metrics->NPMResidencyAcc); @@ -631,6 +699,15 @@ static int smu_v15_0_8_get_npm_data(struct smu_context *smu, return 0; } +static int smu_v15_0_8_set_npm_limit(struct smu_context *smu, uint32_t limit) +{ + if (!smu_v15_0_8_cap_supported(smu, SMU_15_0_8_CAP_NPM)) + return -EOPNOTSUPP; + + return smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_SetNpmLimit, + limit, NULL); +} + static int smu_v15_0_8_read_sensor(struct smu_context *smu, enum amd_pp_sensors sensor, void *data, uint32_t *size) @@ -1149,6 +1226,10 @@ static int smu_v15_0_8_set_driver_pptable(struct smu_context *smu) static_metrics->pldmVersion[0]; dpm_context->board_volt = static_metrics->InputTelemetryVoltageInmV; smu_v15_0_8_init_xgmi_data(smu, static_metrics); + smu_v15_0_8_init_caps(smu); + if (smu_v15_0_8_cap_supported(smu, SMU_15_0_8_CAP_NPM)) + pptable->MaxNodePowerLimit = + static_metrics->MaxNodePowerLimit; pptable->init = true; } @@ -2382,6 +2463,8 @@ static const struct pptable_funcs smu_v15_0_8_ppt_funcs = { .get_unique_id = smu_v15_0_8_get_unique_id, .get_ppt_limit = smu_v15_0_8_get_ppt_limit, .set_ppt_limit = smu_v15_0_8_set_ppt_limit, + .get_npm_cap = smu_v15_0_8_get_npm_cap, + .set_npm_limit = smu_v15_0_8_set_npm_limit, .emit_clk_levels = smu_v15_0_8_emit_clk_levels, .read_sensor = smu_v15_0_8_read_sensor, .populate_umd_state_clk = smu_v15_0_8_populate_umd_state_clk, diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h index 46fce0a79c21..bc0f20e43e0a 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h @@ -32,6 +32,7 @@ #define SMU_15_0_8_MAX_AID 2 #define SMU_15_0_8_MAX_MID 2 #define SMU_15_0_8_MAX_HBM_STACKS 12 +#define SMU_15_0_8_CAP_NPM 1 extern void smu_v15_0_8_set_ppt_funcs(struct smu_context *smu); typedef struct { @@ -61,6 +62,7 @@ typedef struct { uint32_t PPT1Max; uint32_t PPT1Min; uint32_t PPT1Default; + uint32_t MaxNodePowerLimit; bool init; } PPTable_t; From 548c9f2f74dbadb3a1a9922c43be605ac1218350 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 17 Sep 2026 23:28:44 +0800 Subject: [PATCH 07/52] drm/amd/pm: Add store npm limit function Add npm store limit function and use it to update current power limit Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 9bee41d32a36f43bca6dd1f2527feab2c0a8b42a) --- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 33 ++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index c6e3748bb11b..48c52ac0fc77 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -2211,8 +2211,10 @@ static ssize_t amdgpu_get_gpuboard_temp_metrics(struct device *dev, /** * DOC: cur_node_power_limit * - * The amdgpu driver provides a sysfs API for retrieving current node power limit. - * The file cur_node_power_limit is used for this. + * The amdgpu driver provides a sysfs API for reading and writing the current node + * power limit in watts. The file cur_node_power_limit is used for this. Writing a + * new limit sends SetNpmLimit to PMFW. The file is writable only when the ASIC + * reports the write capability bit for this attribute. */ static ssize_t amdgpu_show_cur_node_power_limit(struct device *dev, struct device_attribute *attr, char *buf) @@ -2231,6 +2233,27 @@ static ssize_t amdgpu_show_cur_node_power_limit(struct device *dev, return sysfs_emit(buf, "%u\n", nplimit); } +static ssize_t amdgpu_store_cur_node_power_limit(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(ddev); + u32 limit; + int r; + + r = amdgpu_pm_get_access_if_active(adev); + if (r) + return r; + + r = kstrtou32(buf, 0, &limit); + if (!r) + r = amdgpu_dpm_set_npm_limit(adev, limit); + + amdgpu_pm_put_access(adev); + return r ? r : count; +} + /** * DOC: node_power * @@ -2259,7 +2282,8 @@ static ssize_t amdgpu_show_node_power(struct device *dev, * * The amdgpu driver provides a sysfs API for retrieving current node power management status. * The file npm_status is used for this. It shows the status as enabled or disabled based on - * current node power value. If node power is zero, status is disabled else enabled. + * current node power or npm status value. If node power or npm status is zero, status is + * disabled else enabled. */ static ssize_t amdgpu_show_npm_status(struct device *dev, struct device_attribute *attr, char *buf) @@ -2371,7 +2395,8 @@ static ssize_t amdgpu_show_baseboard_power_limit(struct device *dev, static DEVICE_ATTR(baseboard_temp, 0444, amdgpu_get_baseboard_temp_metrics, NULL); static DEVICE_ATTR(gpuboard_temp, 0444, amdgpu_get_gpuboard_temp_metrics, NULL); -static DEVICE_ATTR(cur_node_power_limit, 0444, amdgpu_show_cur_node_power_limit, NULL); +static DEVICE_ATTR(cur_node_power_limit, 0644, amdgpu_show_cur_node_power_limit, + amdgpu_store_cur_node_power_limit); static DEVICE_ATTR(node_power, 0444, amdgpu_show_node_power, NULL); static DEVICE_ATTR(global_ppt_resid, 0444, amdgpu_show_global_ppt_resid, NULL); static DEVICE_ATTR(max_node_power_limit, 0444, amdgpu_show_max_node_power_limit, NULL); From 4c2c17f639b396bd743f2a94bacd86b1accc4989 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 17 Sep 2026 23:49:47 +0800 Subject: [PATCH 08/52] drm/amd/pm: Notify node unload during driver unload Notify node unload on smu_v15_0_8 during driver unload Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 8b810d7825aa9748a6c3f95ab75a98cb9d116576) --- .../pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h | 3 +- drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h | 3 +- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h | 1 + .../drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c | 60 +++++++++++++++++++ .../drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h | 2 + drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c | 26 ++++++++ drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h | 2 + 7 files changed, 95 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h index bf73d10c0ce7..888261c26409 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v15_0_8_ppsmc.h @@ -85,7 +85,8 @@ #define PPSMC_MSG_SetSoftMaxFclk 0x34 /* NPM limit setter; NPM reads are served from the SystemMetricsTable. */ #define PPSMC_MSG_SetNpmLimit 0x40 -#define PPSMC_Message_Count 0x41 +#define PPSMC_MSG_PrepareNodeForUnload 0x41 +#define PPSMC_Message_Count 0x42 /* PSMC Reset Types for driver msg argument */ #define PPSMC_RESET_TYPE_DRIVER_MODE_1_RESET 0x1 diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h index f86201764814..31b4ef025351 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h @@ -306,7 +306,8 @@ __SMU_DUMMY_MAP(SetSoftMaxGl2clk), \ __SMU_DUMMY_MAP(SetSoftMinGl2clk), \ __SMU_DUMMY_MAP(GetSystemMetricsVersion), \ - __SMU_DUMMY_MAP(SetNpmLimit), + __SMU_DUMMY_MAP(SetNpmLimit), \ + __SMU_DUMMY_MAP(PrepareNodeForUnload), #undef __SMU_DUMMY_MAP #define __SMU_DUMMY_MAP(type) SMU_MSG_##type 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 0c97bee2c4bb..f443000ba657 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 @@ -89,6 +89,7 @@ struct smu_15_0_dpm_context { uint32_t workload_policy_mask; uint32_t dcef_min_ds_clk; uint64_t caps; + bool node_unload; uint32_t board_volt; }; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index 8806c8f407e3..ce56d540e85e 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -25,6 +25,7 @@ #include #include "amdgpu.h" +#include "amdgpu_xgmi.h" #include "amdgpu_smu.h" #include "smu_v15_0_8_pmfw.h" #include "smu15_driver_if_v15_0_8.h" @@ -138,6 +139,7 @@ static const struct cmn2asic_msg_mapping smu_v15_0_8_message_map[SMU_MSG_MAX_COU MSG_MAP(SetSoftMinFclk, PPSMC_MSG_SetSoftMinFclk, 0), MSG_MAP(SetSoftMaxFclk, PPSMC_MSG_SetSoftMaxFclk, 0), MSG_MAP(SetNpmLimit, PPSMC_MSG_SetNpmLimit, 0), + MSG_MAP(PrepareNodeForUnload, PPSMC_MSG_PrepareNodeForUnload, 0), }; /* TODO: Update the clk map once enum PPCLK is updated in smu15_driver_if_v15_0_8.h */ @@ -635,9 +637,22 @@ static void smu_v15_0_8_init_npm_cap(struct smu_context *smu) smu_v15_0_8_cap_set(smu, SMU_15_0_8_CAP_NPM); } +static void smu_v15_0_8_init_node_unload_cap(struct smu_context *smu) +{ + uint32_t fw_ver = smu->smc_fw_version; + uint32_t pgm = (fw_ver >> 24) & 0xFF; + + if (smu_v15_0_8_cap_supported(smu, SMU_15_0_8_CAP_NODE_UNLOAD)) + return; + + if (pgm == 2 && fw_ver >= SMU_15_0_8_NODE_UNLOAD_MIN_FW_VERSION) + smu_v15_0_8_cap_set(smu, SMU_15_0_8_CAP_NODE_UNLOAD); +} + static void smu_v15_0_8_init_caps(struct smu_context *smu) { smu_v15_0_8_init_npm_cap(smu); + smu_v15_0_8_init_node_unload_cap(smu); } static u64 smu_v15_0_8_get_npm_cap(struct smu_context *smu) @@ -1366,8 +1381,46 @@ static int smu_v15_0_8_register_irq_handler(struct smu_context *smu) return ret; } +static void smu_v15_0_8_notify_node_unload(struct smu_context *smu) +{ + if (!smu_v15_0_8_cap_supported(smu, SMU_15_0_8_CAP_NODE_UNLOAD)) + return; + + smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_PrepareNodeForUnload, 0, NULL); +} + +static void smu_v15_0_8_mark_node_unload(struct smu_context *smu) +{ + struct amdgpu_device *adev = smu->adev; + struct amdgpu_hive_info *hive; + struct amdgpu_device *peer; + struct smu_context *peer_smu; + struct smu_15_0_dpm_context *dpm; + + hive = amdgpu_get_xgmi_hive(adev); + if (!hive) { + dpm = smu->smu_dpm.dpm_context; + if (dpm) + dpm->node_unload = true; + return; + } + + list_for_each_entry(peer, &hive->device_list, gmc.xgmi.head) { + peer_smu = peer->powerplay.pp_handle; + if (!peer_smu) + continue; + dpm = peer_smu->smu_dpm.dpm_context; + if (dpm) + dpm->node_unload = true; + } + + amdgpu_put_xgmi_hive(hive); +} + static int smu_v15_0_8_notify_unload(struct smu_context *smu) { + struct smu_context *primary_smu; + struct smu_15_0_dpm_context *dpm = smu->smu_dpm.dpm_context; struct smu_msg_ctl *ctl = &smu->msg_ctl; struct smu_msg_args args = { .msg = SMU_MSG_PrepareMp1ForUnload, @@ -1377,6 +1430,13 @@ static int smu_v15_0_8_notify_unload(struct smu_context *smu) if (amdgpu_in_reset(smu->adev)) return 0; + if (dpm && !dpm->node_unload) { + primary_smu = smu_get_primary_dev(smu); + if (primary_smu) + smu_v15_0_8_notify_node_unload(primary_smu); + smu_v15_0_8_mark_node_unload(smu); + } + dev_dbg(smu->adev->dev, "Notify PMFW about driver unload"); /* Ignore return, just intimate FW that driver is not going to be there */ ctl->ops->send_msg(ctl, &args); diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h index bc0f20e43e0a..dff601bc0f9a 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h @@ -33,6 +33,8 @@ #define SMU_15_0_8_MAX_MID 2 #define SMU_15_0_8_MAX_HBM_STACKS 12 #define SMU_15_0_8_CAP_NPM 1 +#define SMU_15_0_8_CAP_NODE_UNLOAD 2 +#define SMU_15_0_8_NODE_UNLOAD_MIN_FW_VERSION 0x027d0c00u /* 2.125.12.0 */ extern void smu_v15_0_8_set_ppt_funcs(struct smu_context *smu); typedef struct { diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c index 2bd3ea17e789..ff59bf19c3e7 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c @@ -27,6 +27,32 @@ #include "smu_cmn.h" #include "soc15_common.h" +#include "amdgpu_xgmi.h" + +struct smu_context *smu_get_primary_dev(struct smu_context *smu) +{ + struct amdgpu_device *adev = smu->adev; + struct amdgpu_hive_info *hive; + struct amdgpu_device *peer; + + if (adev->gmc.xgmi.physical_node_id == 0) + return smu; + + hive = amdgpu_get_xgmi_hive(adev); + if (!hive) + return NULL; + + list_for_each_entry(peer, &hive->device_list, gmc.xgmi.head) { + if (peer->gmc.xgmi.physical_node_id == 0) { + amdgpu_put_xgmi_hive(hive); + return peer->powerplay.pp_handle; + } + } + + amdgpu_put_xgmi_hive(hive); + return NULL; +} + /* * DO NOT use these for err/warn/info/debug messages. * Use dev_err, dev_warn, dev_info and dev_dbg instead. diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h index ae6742f5298f..6d7f47c934f2 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h @@ -249,6 +249,8 @@ 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); +struct smu_context *smu_get_primary_dev(struct smu_context *smu); + int smu_cmn_get_pptable_from_firmware(struct smu_context *smu, void **table, uint32_t *size, uint32_t pptable_id); From 955418796b2933be41c197b8b210da9191da29f7 Mon Sep 17 00:00:00 2001 From: Philip Yang Date: Mon, 5 Oct 2026 14:44:36 -0400 Subject: [PATCH 09/52] drm/amdkfd: Fix svm_bo use after free in svm_range_clone prange->svm_bo stays set after the range drops its reference in svm_range_vram_node_free(), it is only cleared later by svm_range_bo_release(). Taking a new reference based on old->svm_bo can therefore hit a svm_bo with refcount 0 and svm_range_bo_release_async() pending, leaving the clone with a freed svm_bo. Use svm_bo_ref_unless_zero() under old->lock to only share the svm_bo if it is still alive, same as svm_range_validate_svm_bo() does. A range only holds a svm_bo reference while ttm_res is set, so drop the reference again if old had already dropped its own. The clone keeps the svm_bo pointer and stays on svm_bo->range_list, so svm_range_bo_release() clears the pointer when the svm_bo goes away. The unref must be done outside old->lock because svm_range_bo_release() takes the lock of every range on the list, including old. v3: drop the reference if ttm_res is not set, don't leak it. v2: use svm_bo_ref_unless_zero instead of checking old->ttm_res, to keep sharing the svm_bo when the range dropped its own reference (Felix). Signed-off-by: Philip Yang Signed-off-by: Mukul Joshi Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 0f862d8774161af543fdf6bbc279ba9182c3f8de) --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index cfb428c72686..1f5d73f0ebce 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -2158,14 +2158,25 @@ static struct svm_range *svm_range_clone(struct svm_range *old) svm_range_free(new, false); return NULL; } - if (old->svm_bo) { + + /* old->lock keeps old->svm_bo alive while taking the reference */ + mutex_lock(&old->lock); + if (svm_bo_ref_unless_zero(old->svm_bo)) { new->ttm_res = old->ttm_res; new->offset = old->offset; - new->svm_bo = svm_range_bo_ref(old->svm_bo); + new->svm_bo = old->svm_bo; spin_lock(&new->svm_bo->list_lock); list_add(&new->svm_bo_list, &new->svm_bo->range_list); spin_unlock(&new->svm_bo->list_lock); } + mutex_unlock(&old->lock); + + /* prange only holds an svm_bo reference while ttm_res is set, unref + * outside old->lock, svm_range_bo_release takes it and would deadlock. + */ + if (new->svm_bo && !new->ttm_res) + svm_range_bo_unref(new->svm_bo); + new->flags = old->flags; new->preferred_loc = old->preferred_loc; new->prefetch_loc = old->prefetch_loc; From 2304e9596f1d6021811335c054be2c7567d6ca38 Mon Sep 17 00:00:00 2001 From: Philip Yang Date: Wed, 26 Aug 2026 17:22:53 -0400 Subject: [PATCH 10/52] drm/amdgpu: Reject UALink source-identification address mode Source-identification address mode has never been tested or enabled, so reject it in __check_vpod_info() instead of running with an untested config. Both the inband and the sideband vpod config paths go through this validation. The SOURCE_IDENT handling in the rest of the driver is left in place for when the mode is enabled. Also drop r from the vpod-commit error message, __check_vpod_info() returns bool, so r is uninitialized there. v2: use switch to check address mode. Signed-off-by: Philip Yang Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit a925a168ba7e8afc06884058dfdd0cf0dc8995ca) --- drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c index 17888cf54447..a13703b67762 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c @@ -137,14 +137,21 @@ static bool __check_vpod_info(struct amdgpu_device *adev, vpod->size, ppod->size); return false; } - if (vpod->addr_mode >= AMDGPU_UALINK_ADDR_MODE_MAX) { + + switch (vpod->addr_mode) { + case AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS: + break; + case AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT: dev_warn(adev->dev, - "UALINK: invalid addr mode %u\n", + "UALINK: source-identification addr mode is not supported\n"); + return false; + default: + dev_warn(adev->dev, "UALINK: invalid addr mode %u\n", vpod->addr_mode); return false; } - weight = - bitmap_weight(vpod->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX); + + weight = bitmap_weight(vpod->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX); if (weight != vpod->size) { dev_warn(adev->dev, "UALINK: vPod size doesn't match vpod_active_accels list: %u != %u\n", @@ -1351,8 +1358,7 @@ static ssize_t ualink_vpod_config_commit_store(struct kobject *kobj, if (info->vpod.id != AMDGPU_UALINK_VPOD_ID_INVALID && !__check_vpod_info(adev, info)) { dev_err(adev->dev, - "UALINK: vpod-commit __check_vpod_info() failed: %d\n", - r); + "UALINK: vpod-commit __check_vpod_info() failed\n"); return -EINVAL; } From 948baa3fc47b01fc20d4beefd5cd176c11c1457c Mon Sep 17 00:00:00 2001 From: Philip Yang Date: Thu, 27 Aug 2026 20:06:06 -0400 Subject: [PATCH 11/52] drm/amdgpu: Enable remote TLB shootdown FW remote TLB shootdown support is ready now. Remove the early-return stub so the command is actually sent to the peer GPU. This requires nHT firmware version >= 1.2.E.1 (IFWI >= BRP0660). Signed-off-by: Philip Yang Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit f1be774d6d0128346fea1dc616fdf623c898c82c) --- drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c index a13703b67762..6cd3927a7e82 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c @@ -6081,9 +6081,8 @@ static int amdgpu_ualink_remote_shootdown(struct amdgpu_device *adev, struct amdgpu_ualink_ring *ring; int r; - dev_dbg(adev->dev, "skip remote shootdown to remote_accel_id %u\n", + dev_dbg(adev->dev, "send remote shootdown to remote_accel_id %u\n", remote_accel_id); - return 0; peer = &remote->peer[remote_accel_id]; ring = &peer->shootdown; From 947da95066248b8d5beddc61b8b52e1f695353f3 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 8 Oct 2026 12:13:26 +0800 Subject: [PATCH 12/52] drm/amdgpu: wake the GPU for INFO queries that access the hardware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since amdgpu_drm_ioctl() was removed, amdgpu_info_ioctl() no longer holds a runtime PM reference. When the GPU is runtime suspended, the queries that touch the hardware run against a powered down device: - AMDGPU_INFO_READ_MMR_REG reads all ones, - AMDGPU_INFO_TIMESTAMP returns ~0ull, - AMDGPU_INFO_DEV_INFO reports 0 for the engine and memory clock limits because the SMU is down, - AMDGPU_INFO_SENSOR fails with -ENOENT. On gfx11 with an idle GPU in BAMACO, GRBM_STATUS reads 0xffffffff instead of 0x0000302c, the timestamp is 0xffffffffffffffff, and the min/max sclk is reported as 0/0 kHz instead of 500000/1880000 kHz. Resume the GPU for these queries, the same way the CS ioctl does. Fixes: 3d3140b5b0d0 ("drm/amdgpu: get rid of amdgpu_drm_ioctl") Signed-off-by: Jesse Zhang Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 5ee0ab19c2c0e0c863a5b7595b263ef6f3886c24) --- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 28 ++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 8e15b204f117..59ea7af5b0e4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -780,9 +780,16 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) return copy_to_user(out, &count, min(size, 4u)) ? -EFAULT : 0; } - case AMDGPU_INFO_TIMESTAMP: + case AMDGPU_INFO_TIMESTAMP: { + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev->dev, pm); + + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + ui64 = amdgpu_gfx_get_gpu_clock_counter(adev); return copy_to_user(out, &ui64, min(size, 8u)) ? -EFAULT : 0; + } case AMDGPU_INFO_FW_VERSION: { struct drm_amdgpu_info_firmware fw_info; @@ -906,6 +913,12 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) if (info->read_mmr_reg.count > 128) return -EINVAL; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev->dev, pm); + + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + regs = kmalloc_array(info->read_mmr_reg.count, sizeof(*regs), GFP_KERNEL); if (!regs) @@ -940,6 +953,13 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) uint64_t vm_size; uint32_t pcie_gen_mask, pcie_width_mask; + /* The clock limits are only available while the SMU is up. */ + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev->dev, pm); + + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + dev_info = kzalloc_obj(*dev_info); if (!dev_info) return -ENOMEM; @@ -1167,6 +1187,12 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) } } case AMDGPU_INFO_SENSOR: { + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev->dev, pm); + + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + if (!adev->pm.dpm_enabled) return -ENOENT; From 332d5db9b75b01d5fff3ab1dffd4530a3a78a07a Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 8 Oct 2026 16:35:45 +0800 Subject: [PATCH 13/52] drm/amdgpu: fix the error path in amdgpu_vm_pt_alloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_vm_pt_alloc() drops the eviction lock while it creates the new page table. When the VM started to get evicted in the meantime, amdgpu_vm_begin_critical() returns -EBUSY and we jump to error_free_pt before pt_bo is assigned. That unreferences an uninitialized pointer, leaks the new page table (still pinned for NPA VMs) and returns 0 since r is the result of the successful page table creation. With vm->evicting set right after amdgpu_vm_pt_create() (what amdgpu_vm_evictable() does when TTM evicts one of the VM page tables in that window), the first GEM_VA map that needs a new page table oopses: BUG: kernel NULL pointer dereference, address: 0000000000000460 RIP: 0010:amdgpu_bo_unref+0x27/0x70 [amdgpu] Call Trace: amdgpu_vm_ptes_update+0xa4e/0xa60 [amdgpu] amdgpu_vm_update_range+0x2eb/0x970 [amdgpu] amdgpu_vm_bo_update+0x325/0x6d0 [amdgpu] amdgpu_gem_va_update_vm+0xdb/0x1c0 [amdgpu] amdgpu_gem_va_ioctl+0x86c/0x940 [amdgpu] Assign pt_bo first, unpin and free the new page table and return the error from amdgpu_vm_begin_critical(). Fixes: 0dc93a683a94 ("drm/amdgpu: rework eviction lock handling into critical section v3") Signed-off-by: Jesse Zhang Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 26b68a5702421d0a235ca5894e3797fc9cdc1ec2) --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c index c03327f1242d..599c0085cdd9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c @@ -531,13 +531,16 @@ static int amdgpu_vm_pt_alloc(struct amdgpu_vm_update_params *p, r2 = amdgpu_vm_begin_critical(p); if (r) return r; - if (r2) - goto error_free_pt; + + pt_bo = &pt->bo; + if (r2) { + r = r2; + goto error_unpin; + } /* Keep a reference to the root directory to avoid * freeing them up in the wrong order. */ - pt_bo = &pt->bo; pt_bo->parent = amdgpu_bo_ref(cursor->parent->bo); amdgpu_vm_bo_base_init(entry, p->vm, pt_bo); r = amdgpu_vm_pt_clear(p->adev, p->vm, pt, p->immediate); @@ -549,7 +552,6 @@ static int amdgpu_vm_pt_alloc(struct amdgpu_vm_update_params *p, error_unpin: if (p->vm->is_npa) amdgpu_bo_unpin(pt_bo); -error_free_pt: amdgpu_bo_unref(&pt_bo); return r; } From 28f3d169f162ca7e803c5a1f7e1bb55bdc915d1c Mon Sep 17 00:00:00 2001 From: Luigi Lauro Date: Wed, 14 Oct 2026 15:33:56 +0200 Subject: [PATCH 14/52] drm/amdgpu: require _PR3 for BOCO amdgpu_device_supports_boco() returns true for an AMD_IS_PX device on a system with a hybrid ATPX method even when the device has no ACPI power resource (_PR3). On an APU mini PC with a discrete GPU attached over OCuLink, the iGPU's ATPX method reports hybrid graphics, the dGPU is flagged AMD_IS_PX, and runtime PM in auto mode picks BOCO: amdgpu: ATPX Hybrid Graphics amdgpu 0000:c6:00.0: Using BOCO for runtime pm In BOCO mode the driver leaves powering the device off to the PCI core, which can only do it through _PR3 on the upstream bridge. No device on this dGPU's path has _PR3, so when it runtime suspends the card stays powered, with its fans at about 700 rpm. Require has_pr3 for BOCO, as the function's description ("dGPU with ACPI power resources") says. The same card then picks BACO, which powers it down: amdgpu 0000:c6:00.0: Using BACO for runtime pm This only changes systems with a hybrid ATPX method and no _PR3 on the dGPU's upstream bridge, where BOCO cannot cut the power today. Tested on one such system only, an RX 9070 XT (Navi 48) over OCuLink with a Ryzen AI 9 HX 470, with the change backported to 7.2.9: the stock module logs "Using BOCO", the patched one "Using BACO"; BACO runtime suspend powers the card off and it resumes cleanly. Not tested on a hybrid laptop or a Thunderbolt eGPU. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5980 Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Alex Deucher Signed-off-by: Luigi Lauro Signed-off-by: Alex Deucher (cherry picked from commit 1a039805354a44aab7abb2a6e685212d48b17a5e) 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 48279392d385..866e83de950d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -593,10 +593,8 @@ bool amdgpu_device_supports_boco(struct amdgpu_device *adev) if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) return false; - if (adev->has_pr3 || - ((adev->flags & AMD_IS_PX) && amdgpu_is_atpx_hybrid())) - return true; - return false; + /* BOCO needs a platform power resource (_PR3) to cut the power */ + return adev->has_pr3; } /** From 5b56ede880317bd912f914d5cd38ba1d357677fa Mon Sep 17 00:00:00 2001 From: Maciej Kucharek Date: Thu, 15 Oct 2026 11:57:11 +0200 Subject: [PATCH 15/52] drm/amdgpu: reset SI ASIC on MacBookPros with VRAM at 0 On MacBookPros with switchable graphics, the firmware puts VRAM at MC address 0 when the iGPU is enabled. The EFI stub enables the iGPU on these machines through apple_set_os(). On a MacBookPro11,5 (Radeon R9 M370X, 1002:6821, subsystem 106b:0149), the driver adopts this layout at init: MC_VM_FB_LOCATION=0x007f0000, HDP_NONSURFACE_BASE=0 VRAM at 0x0-0x7fffffff, GART table at 0x1400000 The driver posts the ASIC again on S3 resume and after a GPU reset. The VBIOS asic_init table then moves VRAM back to the VBIOS default: MC_VM_FB_LOCATION=0xf47ff400, HDP_NONSURFACE_BASE=0xf4000000 gmc_v6_0 does not reprogram these registers. The GART table is then no longer in VRAM, and the gfx ring test times out: amdgpu: ring gfx test failed (-110) amdgpu: resume of IP block failed -110 Reset and post the ASIC at driver load on Apple boards with VRAM at 0. The driver then uses the VBIOS layout (VRAM at 0xf400000000) from the start, and later posts keep it. After the reset, FB_BASE is not 0, so a module reload does not reset the ASIC again. The test is the same one that commit c54e4d105077 ("drm/amdgpu: make Mac FB workaround generic") uses: an Apple subsystem vendor and VRAM at 0. The VI quirk for the MacBookPro14,3 resets at load for the same reason: commit b6b1d9721851 ("drm/amdgpu: reset VI ASIC on MacBookPro14,3") Other Apple SI boards get the same reset if FB_BASE reads 0 at load. Tested on a MacBookPro11,5 with 7.2.5 and this patch: the panel comes up at boot, and 4 of 4 deep S3 cycles resume. Without the patch, S3 resume fails every time. The GPU reset path is not tested. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5978 Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Alex Deucher Signed-off-by: Maciej Kucharek Signed-off-by: Alex Deucher (cherry picked from commit 60dd16ae10f286cb9cce5b6027b2d5ee869637af) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/si.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/si.c b/drivers/gpu/drm/amd/amdgpu/si.c index 0adfd4b101aa..e5e590425f42 100644 --- a/drivers/gpu/drm/amd/amdgpu/si.c +++ b/drivers/gpu/drm/amd/amdgpu/si.c @@ -1512,6 +1512,16 @@ static void si_invalidate_hdp(struct amdgpu_device *adev, static bool si_need_reset_on_init(struct amdgpu_device *adev) { + /* + * MacBookPros with switchable graphics put VRAM at 0 when the iGPU + * is enabled. Any later post (S3 resume, GPU reset) moves it back to + * the vbios default, so reset and post on load to start with the + * vbios layout. + */ + if (adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE && + !(RREG32(mmMC_VM_FB_LOCATION) & MC_VM_FB_LOCATION__FB_BASE_MASK)) + return true; + return false; } From 095f634ea0aa5b9da19495520b5fc1a155b2c137 Mon Sep 17 00:00:00 2001 From: Maciej Kucharek Date: Thu, 15 Oct 2026 11:57:12 +0200 Subject: [PATCH 16/52] drm/amdgpu: reset CIK ASIC on Apple boards with VRAM at 0 On MacBookPros with switchable graphics, the firmware puts VRAM at MC address 0 when the iGPU is enabled. A later post (S3 resume, GPU reset) moves VRAM back to the VBIOS default. The SI fix resets and posts the ASIC at load, so the driver uses the VBIOS layout from the start: "drm/amdgpu: reset SI ASIC on MacBookPros with VRAM at 0" No MacBookPro has a CIK GPU, and no affected CIK board is known. Add the same test to cik_need_reset_on_init(), so that SI, CIK and VI handle this firmware layout in the same way. gmc_v7_0 reads MC_VM_FB_LOCATION once at init and does not reprogram it. A CIK board with VRAM at 0 therefore has the same problem as the SI boards. After the reset, FB_BASE is not 0, so the new test does not fire again on a module reload. Only compile-tested. Suggested-by: Alex Deucher Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Alex Deucher Signed-off-by: Maciej Kucharek Signed-off-by: Alex Deucher (cherry picked from commit 6bb4b79d6a6c461b40750fd67cb4e2a070e20b67) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/cik.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/cik.c b/drivers/gpu/drm/amd/amdgpu/cik.c index d29991a4af4c..e1029239b2c6 100644 --- a/drivers/gpu/drm/amd/amdgpu/cik.c +++ b/drivers/gpu/drm/amd/amdgpu/cik.c @@ -1927,6 +1927,16 @@ static bool cik_need_reset_on_init(struct amdgpu_device *adev) { u32 clock_cntl, pc; + /* + * MacBookPros with switchable graphics put VRAM at 0 when the iGPU + * is enabled. Any later post (S3 resume, GPU reset) moves it back to + * the vbios default, so reset and post on load to start with the + * vbios layout. + */ + if (adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE && + !(RREG32(mmMC_VM_FB_LOCATION) & MC_VM_FB_LOCATION__FB_BASE_MASK)) + return true; + if (adev->flags & AMD_IS_APU) return false; From 92a5184787b81e67f7b831ad860ca9b053505cf8 Mon Sep 17 00:00:00 2001 From: Maciej Kucharek Date: Thu, 15 Oct 2026 11:57:13 +0200 Subject: [PATCH 17/52] drm/amdgpu: reset VI ASIC on Apple boards with VRAM at 0 On MacBookPros with switchable graphics, the firmware puts VRAM at MC address 0 when the iGPU is enabled. A later post (S3 resume, GPU reset) moves VRAM back to the VBIOS default. gmc_v8_0 reads MC_VM_FB_LOCATION once at init and reprograms it only on SR-IOV. On bare metal, the driver then keeps using the old layout. vi_reset_quirks[] handles this for the MacBookPro14,3 by PCI ID: commit b6b1d9721851 ("drm/amdgpu: reset VI ASIC on MacBookPro14,3") Add the test that SI and CIK use: an Apple subsystem vendor and VRAM at 0. Other Polaris MacBookPros then get the same reset at load, for example the MacBookPro13,3 if its firmware leaves VRAM at 0. These boards are not tested. Keep vi_reset_quirks[]. Its MacBookPro15,1 entries fix a different failure, a module reload after S3: commit a5be7ad8f5f0 ("drm/amdgpu: reset VI ASIC on MacBookPro15,1") VRAM is not at 0 at that point, so the new test may not cover it. After the reset, FB_BASE is not 0, so the new test does not fire again on a module reload. Only compile-tested. Suggested-by: Alex Deucher Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Alex Deucher Signed-off-by: Maciej Kucharek Signed-off-by: Alex Deucher (cherry picked from commit c2335b685b1e1e387f2fea1c67f56b5b8a8b6dc6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/vi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index a0435fd5525b..cad6bbfcedf3 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -1405,6 +1405,16 @@ static bool vi_need_reset_on_init(struct amdgpu_device *adev) unsigned int i; u32 clock_cntl, pc; + /* + * MacBookPros with switchable graphics put VRAM at 0 when the iGPU + * is enabled. Any later post (S3 resume, GPU reset) moves it back to + * the vbios default, so reset and post on load to start with the + * vbios layout. + */ + if (adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE && + !(RREG32(mmMC_VM_FB_LOCATION) & MC_VM_FB_LOCATION__FB_BASE_MASK)) + return true; + for (i = 0; i < ARRAY_SIZE(vi_reset_quirks); i++) if (adev->pdev->device == vi_reset_quirks[i].device && adev->pdev->subsystem_vendor == vi_reset_quirks[i].subsystem_vendor && From bebad45050945a3d668d6701fb77ae2571c5d44b Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Sat, 10 Oct 2026 11:02:30 +0800 Subject: [PATCH 18/52] drm/amdkfd: fix doorbell allocation race creating duplicate allocations The check-and-allocate sequence on pdd->qpd.proc_doorbells is lockless at every call site of kfd_alloc_process_doorbells(): kfd_get_process_doorbells() (mmap of /dev/kfd), kfd_ioctl_create_queue() and the CRIU restore path each test the pointer under different locking (or none), so two racing threads can both observe NULL and each allocate a doorbell BO and bitmap, orphaning one allocation and handing the two mappings different backing pages. Move the serialization into kfd_alloc_process_doorbells() itself, using a new per-process-device mutex so all three call sites are covered regardless of their own locking. The device-global kfd->doorbell_mutex is not suitable here: it is acquired under dqm->lock on the kernel-doorbell path (start_cpsch() -> pm_init() -> kq_initialize() -> kfd_get_kernel_doorbell()), so holding it across the sleeping allocations above would create a lock-ordering inversion with dqm->lock via TTM eviction, and it would also serialize doorbell setup of unrelated processes behind a device-wide lock for per-process state. This issue was discovered by Tencent CodeBuddy Security. v3 (Felix Kuehling): - Removed unlocked check in kfd_get_process_doorbell flagged as problematic by Sashiko Bot. Fall back to the locked check inside kfd_alloc_process_doorbells - Added missing mutex_destroy v4 (Felix Kuehling): - Removed unlocked checks around other kfd_alloc_process_doorbells calls Fixes: 16f0013157bf ("drm/amdkfd: Allocate doorbells only when needed") Signed-off-by: Henry Martin Signed-off-by: Felix Kuehling Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 4e27ba5ad5f76932d4f6cbacd25c81c2dd40ec4a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 18 +++++++----------- drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c | 23 +++++++++++++++++------ drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 3 +++ drivers/gpu/drm/amd/amdkfd/kfd_process.c | 2 ++ 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 344da6c0e96a..3081d9aaf053 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -427,12 +427,10 @@ static int kfd_ioctl_create_queue(struct file *filep, struct kfd_process *p, } } - if (!pdd->qpd.proc_doorbells) { - err = kfd_alloc_process_doorbells(dev->kfd, pdd); - if (err) { - pr_debug("failed to allocate process doorbells\n"); - goto err_bind_process; - } + err = kfd_alloc_process_doorbells(dev->kfd, pdd); + if (err) { + pr_debug("failed to allocate process doorbells\n"); + goto err_bind_process; } err = kfd_queue_acquire_buffers(pdd, &q_properties); @@ -2455,11 +2453,9 @@ static int criu_restore_devices(struct kfd_process *p, goto exit; } - if (!pdd->qpd.proc_doorbells) { - ret = kfd_alloc_process_doorbells(dev->kfd, pdd); - if (ret) - goto exit; - } + ret = kfd_alloc_process_doorbells(dev->kfd, pdd); + if (ret) + goto exit; } /* diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c b/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c index fdcf7f2d1b5b..6af268a031f2 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_doorbell.c @@ -239,11 +239,9 @@ phys_addr_t kfd_get_process_doorbells(struct kfd_process_device *pdd) struct amdgpu_device *adev = pdd->dev->adev; uint32_t first_db_index; - if (!pdd->qpd.proc_doorbells) { - if (kfd_alloc_process_doorbells(pdd->dev->kfd, pdd)) - /* phys_addr_t 0 is error */ - return 0; - } + if (kfd_alloc_process_doorbells(pdd->dev->kfd, pdd)) + /* phys_addr_t 0 is error */ + return 0; first_db_index = amdgpu_doorbell_index_on_bar(adev, pdd->qpd.proc_doorbells, @@ -257,12 +255,21 @@ int kfd_alloc_process_doorbells(struct kfd_dev *kfd, struct kfd_process_device * int r; struct qcm_process_device *qpd = &pdd->qpd; + mutex_lock(&pdd->doorbell_mutex); + + /* Another thread may have won the check-and-allocate race */ + if (qpd->proc_doorbells) { + mutex_unlock(&pdd->doorbell_mutex); + return 0; + } + /* Allocate bitmap for dynamic doorbell allocation */ qpd->doorbell_bitmap = bitmap_zalloc(KFD_MAX_NUM_OF_QUEUES_PER_PROCESS, GFP_KERNEL); if (!qpd->doorbell_bitmap) { DRM_ERROR("Failed to allocate process doorbell bitmap\n"); - return -ENOMEM; + r = -ENOMEM; + goto unlock; } r = init_doorbell_bitmap(&pdd->qpd, kfd); @@ -284,11 +291,15 @@ int kfd_alloc_process_doorbells(struct kfd_dev *kfd, struct kfd_process_device * DRM_ERROR("Failed to allocate process doorbells\n"); goto err; } + + mutex_unlock(&pdd->doorbell_mutex); return 0; err: bitmap_free(qpd->doorbell_bitmap); qpd->doorbell_bitmap = NULL; +unlock: + mutex_unlock(&pdd->doorbell_mutex); return r; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index b9a31489b1c8..e102086ac49a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -784,6 +784,9 @@ struct kfd_process_device { /* per-process-per device QCM data structure */ struct qcm_process_device qpd; + /* Serializes process doorbell allocation for this device */ + struct mutex doorbell_mutex; + /*Apertures*/ uint64_t lds_base; uint64_t lds_limit; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index 226f52626e84..51b0df498494 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -1233,6 +1233,7 @@ static void kfd_process_destroy_pdds(struct kfd_process *p) } atomic_dec(&pdd->dev->kfd->kfd_processes_count); + mutex_destroy(&pdd->doorbell_mutex); kfree(pdd); p->pdds[i] = NULL; @@ -1802,6 +1803,7 @@ struct kfd_process_device *kfd_create_process_device_data(struct kfd_node *dev, pdd->dev = dev; INIT_LIST_HEAD(&pdd->qpd.queues_list); INIT_LIST_HEAD(&pdd->qpd.priv_queue_list); + mutex_init(&pdd->doorbell_mutex); pdd->qpd.dqm = dev->dqm; pdd->qpd.pqm = &p->pqm; pdd->qpd.evicted = 0; From f90e8d86d6add82459fa51ade2f076b5e368c131 Mon Sep 17 00:00:00 2001 From: Ilya Bakoulin Date: Fri, 25 Sep 2026 15:25:07 -0400 Subject: [PATCH 19/52] drm/amd/display: Fix HDMI FRL FFE compliance test [Why & How] The FFE electrical compliance test behavior is not correct. The driver doesn't properly iterate over the FFE levels and select PS/DE based on test request and/or cable-ID. Fix the initialization of the link state so that when the polling loop takes over, the test proceeds as described in the spec. Reviewed-by: Relja Vojvodic Signed-off-by: Ilya Bakoulin Signed-off-by: Ray Wu Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 1903c3389c81bc65f57f0edeea879359d674bb33) --- .../gpu/drm/amd/display/dc/dc_hdmi_types.h | 4 +- .../display/dc/link/hwss/link_hwss_hpo_frl.c | 3 + .../gpu/drm/amd/display/dc/link/link_dpms.c | 2 + .../display/dc/link/protocols/link_hdmi_frl.c | 127 +++++++++++++----- 4 files changed, 104 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h b/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h index eb6e7f4043fd..9e0fe41f28a5 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h @@ -235,7 +235,8 @@ enum link_result { LINK_RESULT_SUCCESS, LINK_RESULT_LOWER_LINKRATE, LINK_RESULT_TIMEOUT, - LINK_RESULT_FALLBACK + LINK_RESULT_FALLBACK, + LINK_RESULT_NO_TIMEOUT }; enum hdmi_frl_link_rate { @@ -274,6 +275,7 @@ struct dc_hdmi_frl_flags { bool force_frl_dsc; bool force_frl_max; bool apply_vsdb_rcc_wa; + bool no_timeout; }; struct dc_hdmi_frl_link_training_overrides { diff --git a/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c index a2c0fa21ee94..62288133cd55 100644 --- a/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c +++ b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c @@ -59,6 +59,9 @@ static void disable_hpo_frl_link_output(struct dc_link *link, enum signal_type signal) { (void)link_res; + if (link->frl_flags.no_timeout) + return; + if (dc_is_hdmi_frl_signal(signal)) link->hpo_frl_link_enc->funcs->disable_link_encoder(link->hpo_frl_link_enc); link->link_enc->funcs->disable_output(link->link_enc, signal); diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 0d3b65d40807..59a3f4b88a37 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -2038,6 +2038,8 @@ static enum dc_status enable_link_hdmi_frl(struct pipe_ctx *pipe_ctx) pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_enable( pipe_ctx->stream_res.hpo_frl_stream_enc, pipe_ctx->stream_res.tg->inst); + } else if (link_stat == LINK_RESULT_NO_TIMEOUT) { + return status; } else { status = DC_FAIL_HDMI_FRL_LINK_TRAINING; stream->link->frl_link_settings.frl_link_rate = 0; diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c index 5e0979ac3dcf..52a22b3e78af 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c @@ -451,11 +451,37 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s uint8_t FFE_Levels = hdmi_frl_get_max_ffe_level(ddc_service->link, link_settings); uint8_t current_FFE = 0; bool override_FFE = false; - bool flt_no_timeout = false; DC_LOGGER_INIT(ddc_service->link->ctx->logger); FRL_INFO("FRL LINK TRAINING: Starting FRL Link Training.\n"); + /*Test Condition - FLT_no_timeout avoid link training*/ + offset = HDMI_SCDC_SOURCE_TEST_REQ; + link_query_ddc_data(ddc_service, slave_address, &offset, + sizeof(offset), &test_req.byte, sizeof(test_req.byte)); + FRL_INFO("FRL TEST REQ: FLT_no_timeout = %d \n", test_req.fields.FLT_NO_TIMEOUT); + + if (test_req.fields.FLT_NO_TIMEOUT && ddc_service->link->frl_flags.no_timeout) { + bool de_emphasis_only = false; + bool pre_shoot_only = false; + bool no_ffe = false; + + read_idcc_data(ddc_service, HDMI_IDCC_SCOPE_RW_CA, + ddc_service->link->hdmi_cable_id.raw, 0, + sizeof(ddc_service->link->hdmi_cable_id.raw)); + + hdmi_return_preeshoot_and_deemphasis(ddc_service->link, &test_req, + &de_emphasis_only, &pre_shoot_only, &no_ffe); + + dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0, + de_emphasis_only, + pre_shoot_only, + no_ffe, + link_settings); + + return LINK_RESULT_NO_TIMEOUT; + } + hdmi_frl_LTS_clear_Link_Setting(ddc_service); offset = HDMI_SCDC_SINK_VERSION; @@ -511,13 +537,68 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s } } while (elapsed < poll_timeout_ns); - /*Test Condition - FLT_no_timeout avoid link training*/ - offset = HDMI_SCDC_SOURCE_TEST_REQ; - link_query_ddc_data(ddc_service, slave_address, &offset, - sizeof(offset), &test_req.byte, sizeof(test_req.byte)); - FRL_INFO("FRL TEST REQ: FLT_no_timeout = %d \n", test_req.fields.FLT_NO_TIMEOUT); - if (test_req.fields.FLT_NO_TIMEOUT) - flt_no_timeout = true; + if (test_req.fields.FLT_NO_TIMEOUT) { + ddc_service->link->frl_flags.no_timeout = true; + + /*Specify FRL rate*/ + write_buffer[0] = HDMI_SCDC_CONFIG_1; + /*FRL_RATE*/ + write_buffer[1] = (uint8_t)(link_settings->frl_link_rate | (FFE_Levels << 4)); + link_query_ddc_data(ddc_service, slave_address, + write_buffer, sizeof(write_buffer), NULL, 0); + FRL_INFO("FRL LINK TRAINING: Write link rate = %d. Max FFE_Levels = %d\n", + link_settings->frl_link_rate, FFE_Levels); + + offset = HDMI_SCDC_UPDATE_0; + /*Check FLT_update flag*/ + link_query_ddc_data(ddc_service, slave_address, + &offset, sizeof(offset), &scdc_update.byte[0], + sizeof(scdc_update.byte[0])); + + offset = HDMI_SCDC_LTP_REQ; + link_query_ddc_data(ddc_service, slave_address, + &offset, sizeof(offset), ltp_req.byte, + sizeof(ltp_req.byte)); + + ln0_pattern = ltp_req.fields.LN0_LTP_REQ; + ln1_pattern = ltp_req.fields.LN1_LTP_REQ; + ln2_pattern = ltp_req.fields.LN2_LTP_REQ; + ln3_pattern = ltp_req.fields.LN3_LTP_REQ; + + hpo_frl_link_enc->funcs->set_hdmi_training_pattern( + hpo_frl_link_enc, + ln0_pattern - 1, + ln1_pattern - 1, + ln2_pattern - 1, + ln3_pattern - 1); + + read_idcc_data(ddc_service, HDMI_IDCC_SCOPE_RW_CA, + ddc_service->link->hdmi_cable_id.raw, 0, + sizeof(ddc_service->link->hdmi_cable_id.raw)); + + bool de_emphasis_only = false; + bool pre_shoot_only = false; + bool no_ffe = false; + + hdmi_return_preeshoot_and_deemphasis(ddc_service->link, &test_req, + &de_emphasis_only, &pre_shoot_only, &no_ffe); + + dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0, + de_emphasis_only, + pre_shoot_only, + no_ffe, + link_settings); + + write_buffer[0] = HDMI_SCDC_UPDATE_0; + /*FLT_update - bit 5*/ + write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5); + link_query_ddc_data(ddc_service, slave_address, + write_buffer, sizeof(write_buffer), NULL, 0); + + return LINK_RESULT_NO_TIMEOUT; + } + + ddc_service->link->frl_flags.no_timeout = false; if (status_data.fields.FLT_READY) { /*Specify FRL rate*/ @@ -534,9 +615,7 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s /*Start FLT Timer = 200 ms, or 300ms if link rate >= 16Gbps*/ num_polls = 0; if (link_settings->frl_link_rate >= HDMI_FRL_LINK_RATE_16GBPS) - poll_timeout_ns = 300 * 1000 * 1000; - if (flt_no_timeout) - poll_timeout_ns = 1000 * 1000 * 1000; + poll_timeout_ns = 320 * 1000 * 1000; flt_poll_start = dm_get_timestamp(ddc_service->ctx); uint8_t prev_flt_update = 0; @@ -561,21 +640,9 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s prev_flt_update = scdc_update.fields.FLT_UPDATE; } - if (flt_no_timeout) { - offset = HDMI_SCDC_SOURCE_TEST_REQ; - link_query_ddc_data(ddc_service, slave_address, &offset, - sizeof(offset), &test_req.byte, sizeof(test_req.byte)); - if (test_req.fields.FLT_NO_TIMEOUT == 0) { - FRL_INFO("FRL TEST REQ: FLT_no_timeout = %d \n", test_req.fields.FLT_NO_TIMEOUT); - return LINK_RESULT_SUCCESS; - } - } - /*Set TxFFE = TxFFE0*/ /*Program FFE_Levels - scdc_config has this field at 0 */ if (override_FFE) { - if (flt_no_timeout) - current_FFE = 0; if (current_FFE == 0) dio_link_enc->funcs->prog_eq_setting(dio_link_enc, current_FFE, test_req.fields.TXFFE_DEEMPHASIS, @@ -626,8 +693,7 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s if (ln0_pattern == 0x03 || ln1_pattern == 0x03 || ln2_pattern == 0x03 || ln3_pattern == 0x03) - if (!flt_no_timeout) - continue; + continue; if (link_settings->frl_num_lanes == 3) { ln3_pattern = 1; @@ -651,8 +717,6 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s override_FFE = true; if (current_FFE > FFE_Levels) current_FFE = 0; - if (flt_no_timeout) - current_FFE = 0; } } else { if (!ln0_pattern && !ln1_pattern && !ln2_pattern && !ln3_pattern) { @@ -677,8 +741,6 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s override_FFE = true; if (current_FFE > FFE_Levels) current_FFE = 0; - if (flt_no_timeout) - current_FFE = 0; } } @@ -733,7 +795,7 @@ enum link_result hdmi_frl_perform_link_training_with_retries( /* FRL Link Training */ while (status != LINK_RESULT_FALLBACK) { - if (status == LINK_RESULT_SUCCESS) { + if (status == LINK_RESULT_SUCCESS || status == LINK_RESULT_NO_TIMEOUT) { break; } else if (status == LINK_RESULT_LOWER_LINKRATE) { FRL_INFO("FRL LINK TRAINING: Sink requested lower link rate during link enable. \n"); @@ -780,6 +842,9 @@ enum link_result hdmi_frl_perform_link_training_with_fallback( status = hdmi_frl_perform_link_training(link->ddc, &link->frl_link_settings); + if (status == LINK_RESULT_NO_TIMEOUT) + break; + link->dc->hwss.disable_link_output(link, link_res, SIGNAL_TYPE_HDMI_FRL); if (status == LINK_RESULT_SUCCESS) @@ -892,7 +957,7 @@ void hdmi_frl_verify_link_cap(struct dc_link *link, status = hdmi_frl_perform_link_training_with_fallback(link, &link_res, frl_phy_clock_source_id); - if (status == LINK_RESULT_SUCCESS) { + if (status == LINK_RESULT_SUCCESS || status == LINK_RESULT_NO_TIMEOUT) { cur->frl_link_rate = link->frl_link_settings.frl_link_rate; cur->frl_num_lanes = link->frl_link_settings.frl_num_lanes; success = true; From b18a7c6d75bcc883b39497863cc88c9ef4c6c85f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Sat, 26 Sep 2026 17:32:21 -0500 Subject: [PATCH 20/52] drm/amd/display: don't WARN when removing an unallocated MST payload [Why] On an MST teardown that runs through the reset path (dcn31_reset_hw_ctx_wrap -> link_set_dpms_off -> deallocate_mst_payload), fill_dc_mst_payload_table_from_drm() is asked to remove a payload whose vcpi is not present in link->mst_stream_alloc_table and trips: WARNING: .../amdgpu_dm_helpers.c:275 fill_dc_mst_payload_table_from_drm This is a legitimate case, not an error: deallocate_mst_payload() can be called for a stream whose MST payload was never programmed into DC's HW-maintained table (a new stream created for a modeset whose enable never completed, as already documented in deallocate_mst_payload()). There is simply nothing to remove. [How] Drop the ASSERT and fall through to the fill loop, which rebuilds the allocation table from the remaining entries. The table ends up unchanged, which is correct: the payload being removed was never in it. Note the loop must not early-return here - the caller passes a zeroed proposed_table, so returning early would zero the whole link's table and drop the other MST streams sharing the link. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5875 Reviewed-by: Wayne Lin Signed-off-by: Mario Limonciello Signed-off-by: Ray Wu Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 88a27f624dc0ebc14a7ab274a84d977ce8513cb6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3217aa82bea2..6febc15ebc2b 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 @@ -309,7 +309,7 @@ fill_dc_mst_payload_table_from_drm(struct dc_link *link, break; } } - ASSERT(i != copy_of_link_table.stream_count); + /* Payload absent from HW table when never allocated: nothing to remove. */ } /* Fill payload info*/ From d577b68a854d0f590f480777249b041507d0708c Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 13 Oct 2026 21:37:07 +0800 Subject: [PATCH 21/52] drm/amdgpu: reject ANONONLY userptrs that span multiple VMAs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For AMDGPU_GEM_USERPTR_ANONONLY, amdgpu_ttm_tt_get_user_pages() only checks the VMA at the start of the range for a backing file, and amdgpu_hmm_range_get_pages() then faults in the whole range, across any other VMAs. The check used to reject ranges ending beyond the first VMA (vma->vm_end < end), which got lost when the code moved to HMM. An ANONONLY userptr over one page of file-backed memory is rejected with -EPERM, but one over an anonymous page followed by the same file-backed page is accepted. Restore the check and reject ANONONLY userptrs that cross more than one VMA. The second userptr is now rejected too. v2: reject ranges spanning multiple VMAs instead of walking every VMA (Christian) v3: cast num_pages to u64 to avoid an overflow (Christian) Fixes: 6826cb3b92a3 ("drm/amdgpu: support userptr cross VMAs case with HMM") Cc: Christian König Cc: Philip Yang Cc: Felix Kuehling Signed-off-by: Jesse Zhang Reviewed-by: Christian König Acked-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 75d753ed4704d5efc72164d9e546b2a282e42ae2) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 6b3fbf79abc9..756ba0dad85d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -784,7 +784,8 @@ int amdgpu_ttm_tt_get_user_pages(struct amdgpu_bo *bo, goto out_unlock; } if (unlikely((gtt->userflags & AMDGPU_GEM_USERPTR_ANONONLY) && - vma->vm_file)) { + (vma->vm_file || + vma->vm_end < start + (u64)ttm->num_pages * PAGE_SIZE))) { r = -EPERM; goto out_unlock; } From ad6eb088a508d927641c8a76d1ca7911dfb7c80b Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 15 Oct 2026 15:38:26 +0800 Subject: [PATCH 22/52] drm/amd/pm: reapply umd profile state after recovery Resume and full GPU reset retain the selected UMD profile level in driver state. The GFX and PMFW state behind it is reinitialized. Add a generic DPM reapply path after CG and PG restoration. It restores the UMD GFX state and asks the PM backend to replay the retained level. v2: - Reapply only GFX CG to preserve the profile-owned GFX PG reference. - Fall back to AUTO when reapply fails without failing device recovery. - Propagate PowerPlay DPM errors before accepting AUTO fallback. - Limit workload-profile error reporting to the swSMU reapply path. This covers: - PCI runtime resume - Full GPU reset recovery - System resume, including S3 and s2idle Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit fd0699fc9f4266fc07d6ce6dbb484fac04601106) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 14 --- .../gpu/drm/amd/include/kgd_pp_interface.h | 2 + drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 85 ++++++++++++++++++ drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h | 1 + .../gpu/drm/amd/pm/powerplay/amd_powerplay.c | 33 +++++-- drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 87 +++++++++++++++++-- 7 files changed, 201 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 866e83de950d..f147c0c459da 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -2781,6 +2781,12 @@ static int amdgpu_device_ip_late_init(struct amdgpu_device *adev) amdgpu_device_set_cg_state(adev, AMD_CG_STATE_GATE); amdgpu_device_set_pg_state(adev, AMD_PG_STATE_GATE); + r = amdgpu_dpm_reapply_umd_profile(adev); + if (r) { + dev_err(adev->dev, "Failed to restore UMD profile state: %d\n", r); + return r; + } + amdgpu_device_fill_reset_magic(adev); r = amdgpu_device_enable_mgpu_fan_boost(); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ab6f9131ac11..3e5634de077f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2974,19 +2974,6 @@ static int amdgpu_pmops_runtime_suspend(struct device *dev) return 0; } -static void amdgpu_restore_umd_profile_pstate_after_runpm(struct amdgpu_device *adev) -{ - enum amd_dpm_forced_level level; - uint32_t profile_mode_mask = AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD | - AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK | - AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK | - AMD_DPM_FORCED_LEVEL_PROFILE_PEAK; - - level = amdgpu_dpm_get_performance_level(adev); - if (level & profile_mode_mask) - amdgpu_asic_update_umd_stable_pstate(adev, true); -} - static int amdgpu_pmops_runtime_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); @@ -3032,7 +3019,6 @@ static int amdgpu_pmops_runtime_resume(struct device *dev) if (adev->pm.rpm_mode == AMDGPU_RUNPM_PX) drm_dev->switch_power_state = DRM_SWITCH_POWER_ON; - amdgpu_restore_umd_profile_pstate_after_runpm(adev); adev->in_runpm = false; return 0; } diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h index ca818965afe1..7db44fa24042 100644 --- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h @@ -449,6 +449,8 @@ struct amd_pm_funcs { int (*print_clock_levels)(void *handle, enum pp_clock_type type, char *buf); int (*emit_clock_levels)(void *handle, enum pp_clock_type type, char *buf, int *offset); int (*force_performance_level)(void *handle, enum amd_dpm_forced_level level); + int (*reapply_performance_level)(void *handle, + enum amd_dpm_forced_level level); int (*get_sclk_od)(void *handle); int (*set_sclk_od)(void *handle, uint32_t value); int (*get_mclk_od)(void *handle); diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index 0394085a0328..75d8bb4868e8 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -1082,6 +1082,91 @@ static void amdgpu_dpm_exit_umd_state(struct amdgpu_device *adev) AMD_PG_STATE_GATE); } +/** + * amdgpu_dpm_reapply_umd_profile - Restore the active UMD profile + * @adev: amdgpu device + * + * Resume and reset retain the selected DPM level in software, but the + * underlying GFX and PMFW state is lost. Reapply a retained UMD profile + * after the common resume path has restored its default CG and PG state. + */ +int amdgpu_dpm_reapply_umd_profile(struct amdgpu_device *adev) +{ + const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; + enum amd_dpm_forced_level level; + int ret, fallback_ret; + + if (!pp_funcs || !pp_funcs->reapply_performance_level || + !pp_funcs->force_performance_level) + return 0; + + /* Serialize the retained level with the GFX and PMFW reprogramming. */ + mutex_lock(&adev->pm.mutex); + if (adev->pm.dpm.thermal_active) { + dev_warn(adev->dev, + "Cannot restore UMD profile while thermal control is active\n"); + ret = 0; + goto unlock; + } + + if (pp_funcs->get_performance_level) + level = pp_funcs->get_performance_level(adev->powerplay.pp_handle); + else + level = adev->pm.dpm.forced_level; + + switch (level) { + case AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD: + case AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK: + case AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK: + case AMD_DPM_FORCED_LEVEL_PROFILE_PEAK: + break; + default: + ret = 0; + goto unlock; + } + + /* The active profile already owns the GFX PG reference. */ + ret = amdgpu_device_ip_set_clockgating_state(adev, AMD_IP_BLOCK_TYPE_GFX, + AMD_CG_STATE_UNGATE); + if (ret) + goto fallback_auto; + + ret = amdgpu_asic_update_umd_stable_pstate(adev, true); + if (ret) + goto fallback_auto; + + ret = pp_funcs->reapply_performance_level(adev->powerplay.pp_handle, + level); + if (ret) + goto fallback_auto; + +unlock: + mutex_unlock(&adev->pm.mutex); + + return ret; + +fallback_auto: + dev_warn(adev->dev, "Failed to restore UMD profile state: %d\n", ret); + + /* Restore normal CG before releasing the profile's PG reference. */ + amdgpu_asic_update_umd_stable_pstate(adev, false); + amdgpu_device_ip_set_clockgating_state(adev, AMD_IP_BLOCK_TYPE_GFX, + AMD_CG_STATE_GATE); + mutex_unlock(&adev->pm.mutex); + + mutex_lock(&adev->pm.stable_pstate_ctx_lock); + fallback_ret = amdgpu_dpm_force_performance_level(adev, + AMD_DPM_FORCED_LEVEL_AUTO); + if (fallback_ret) + dev_warn(adev->dev, "Failed to fall back to AUTO performance level: %d\n", + fallback_ret); + else + adev->pm.stable_pstate_ctx = NULL; + mutex_unlock(&adev->pm.stable_pstate_ctx_lock); + + return 0; +} + int amdgpu_dpm_force_performance_level(struct amdgpu_device *adev, enum amd_dpm_forced_level level) { diff --git a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h index 72b13ece22ef..ba8624d28f28 100644 --- a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h +++ b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h @@ -484,6 +484,7 @@ void amdgpu_dpm_set_power_state(struct amdgpu_device *adev, enum amd_dpm_forced_level amdgpu_dpm_get_performance_level(struct amdgpu_device *adev); int amdgpu_dpm_force_performance_level(struct amdgpu_device *adev, enum amd_dpm_forced_level level); +int amdgpu_dpm_reapply_umd_profile(struct amdgpu_device *adev); int amdgpu_dpm_get_pp_num_states(struct amdgpu_device *adev, struct pp_states_info *states); int amdgpu_dpm_dispatch_task(struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c index 239c64b8edca..7b0ff5a0993e 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c +++ b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c @@ -358,24 +358,46 @@ static void pp_dpm_en_umd_pstate(struct pp_hwmgr *hwmgr, } } -static int pp_dpm_force_performance_level(void *handle, - enum amd_dpm_forced_level level) +static int pp_dpm_apply_performance_level(struct pp_hwmgr *hwmgr, + enum amd_dpm_forced_level level, + bool reapply) { - struct pp_hwmgr *hwmgr = handle; + int ret; if (!hwmgr || !hwmgr->pm_en) return -EINVAL; - if (level == hwmgr->dpm_level) + if (!reapply && level == hwmgr->dpm_level) return 0; pp_dpm_en_umd_pstate(hwmgr, &level); hwmgr->request_dpm_level = level; - hwmgr_handle_task(hwmgr, AMD_PP_TASK_READJUST_POWER_STATE, NULL); + ret = hwmgr_handle_task(hwmgr, AMD_PP_TASK_READJUST_POWER_STATE, NULL); + if (ret) + return ret; + + ret = phm_force_dpm_levels(hwmgr, level); + if (ret) + return ret; + + hwmgr->dpm_level = level; return 0; } +static int pp_dpm_force_performance_level(void *handle, + enum amd_dpm_forced_level level) +{ + return pp_dpm_apply_performance_level(handle, level, false); +} + +static int +pp_dpm_reapply_performance_level(void *handle, + enum amd_dpm_forced_level level) +{ + return pp_dpm_apply_performance_level(handle, level, true); +} + static enum amd_dpm_forced_level pp_dpm_get_performance_level( void *handle) { @@ -1536,6 +1558,7 @@ static const struct amd_pm_funcs pp_dpm_funcs = { .load_firmware = pp_dpm_load_fw, .wait_for_fw_loading_complete = pp_dpm_fw_loading_complete, .force_performance_level = pp_dpm_force_performance_level, + .reapply_performance_level = pp_dpm_reapply_performance_level, .get_performance_level = pp_dpm_get_performance_level, .get_current_power_state = pp_dpm_get_current_power_state, .dispatch_tasks = pp_dpm_dispatch_tasks, diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 3bb36668eb39..c9fe323fa624 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -2366,6 +2366,13 @@ static int smu_set_powergating_state(struct amdgpu_ip_block *ip_block, return 0; } +static void smu_enter_umd_pstate(struct smu_context *smu) +{ + smu_gpo_control(smu, false); + smu_gfx_ulv_control(smu, false); + smu_deep_sleep_control(smu, false); +} + static int smu_enable_umd_pstate(void *handle, enum amd_dpm_forced_level *level) { @@ -2384,9 +2391,7 @@ static int smu_enable_umd_pstate(void *handle, /* enter umd pstate, save current level, disable gfx cg*/ if (*level & profile_mode_mask) { smu_dpm_ctx->saved_dpm_level = smu_dpm_ctx->dpm_level; - smu_gpo_control(smu, false); - smu_gfx_ulv_control(smu, false); - smu_deep_sleep_control(smu, false); + smu_enter_umd_pstate(smu); amdgpu_asic_update_umd_stable_pstate(smu->adev, true); } } else { @@ -2445,7 +2450,8 @@ static void smu_power_profile_mode_put(struct smu_context *smu, static int smu_adjust_power_state_dynamic(struct smu_context *smu, enum amd_dpm_forced_level level, - bool skip_display_settings) + bool skip_display_settings, + bool force_performance_level) { int ret = 0; struct smu_dpm_context *smu_dpm_ctx = &(smu->smu_dpm); @@ -2472,7 +2478,7 @@ static int smu_adjust_power_state_dynamic(struct smu_context *smu, } } - if (smu_dpm_ctx->dpm_level != level) { + if (force_performance_level || smu_dpm_ctx->dpm_level != level) { ret = smu_asic_set_performance_level(smu, level); if (ret) { if (ret == -EOPNOTSUPP) @@ -2495,6 +2501,39 @@ static int smu_adjust_power_state_dynamic(struct smu_context *smu, return ret; } +static int +smu_reapply_power_state_dynamic(struct smu_context *smu, + enum amd_dpm_forced_level level) +{ + struct smu_dpm_context *smu_dpm_ctx = &smu->smu_dpm; + int ret; + + ret = smu_apply_clocks_adjust_rules(smu); + if (ret) { + dev_err(smu->adev->dev, "Failed to apply clocks adjust rules!"); + return ret; + } + + ret = smu_asic_set_performance_level(smu, level); + if (ret) { + if (ret == -EOPNOTSUPP) + dev_info(smu->adev->dev, "set performance level %d not supported", + level); + else + dev_err(smu->adev->dev, "Failed to set performance level %d", + level); + return ret; + } + + smu_dpm_ctx->dpm_level = level; + + if (smu_dpm_ctx->dpm_level == AMD_DPM_FORCED_LEVEL_MANUAL || + smu_dpm_ctx->dpm_level == AMD_DPM_FORCED_LEVEL_PERF_DETERMINISM) + return 0; + + return smu_bump_power_profile_mode(smu, NULL, 0); +} + static int smu_handle_task(struct smu_context *smu, enum amd_dpm_forced_level level, enum amd_pp_task task_id) @@ -2509,13 +2548,13 @@ static int smu_handle_task(struct smu_context *smu, ret = smu_pre_display_config_changed(smu); if (ret) return ret; - ret = smu_adjust_power_state_dynamic(smu, level, false); + ret = smu_adjust_power_state_dynamic(smu, level, false, false); break; case AMD_PP_TASK_COMPLETE_INIT: - ret = smu_adjust_power_state_dynamic(smu, level, true); + ret = smu_adjust_power_state_dynamic(smu, level, true, false); break; case AMD_PP_TASK_READJUST_POWER_STATE: - ret = smu_adjust_power_state_dynamic(smu, level, true); + ret = smu_adjust_power_state_dynamic(smu, level, true, false); break; default: break; @@ -2644,6 +2683,37 @@ static int smu_force_performance_level(void *handle, return ret; } +static int +smu_reapply_performance_level(void *handle, + enum amd_dpm_forced_level level) +{ + struct smu_context *smu = handle; + struct smu_dpm_context *smu_dpm_ctx = &smu->smu_dpm; + + if (!smu->pm_enabled || !smu->adev->pm.dpm_enabled) + return -EOPNOTSUPP; + + if (!smu->is_apu && !smu_dpm_ctx->dpm_context) + return -EINVAL; + + switch (level) { + case AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD: + case AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK: + case AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK: + case AMD_DPM_FORCED_LEVEL_PROFILE_PEAK: + break; + default: + return -EINVAL; + } + + if (smu_dpm_ctx->dpm_level != level) + return -EINVAL; + + smu_enter_umd_pstate(smu); + + return smu_reapply_power_state_dynamic(smu, level); +} + static int smu_set_display_count(void *handle, uint32_t count) { struct smu_context *smu = handle; @@ -4030,6 +4100,7 @@ static const struct amd_pm_funcs swsmu_pm_funcs = { .force_clock_level = smu_force_ppclk_levels, .emit_clock_levels = smu_emit_ppclk_levels, .force_performance_level = smu_force_performance_level, + .reapply_performance_level = smu_reapply_performance_level, .read_sensor = smu_read_sensor, .get_apu_thermal_limit = smu_get_apu_thermal_limit, .set_apu_thermal_limit = smu_set_apu_thermal_limit, From ec71e978da77f61d5672268ccf6f4be37b83129c Mon Sep 17 00:00:00 2001 From: Ilya Bakoulin Date: Tue, 6 Oct 2026 12:04:55 -0400 Subject: [PATCH 23/52] drm/amd/display: Adjust HDMI FRL LT timeout [Why & How] Extend LT timeout for link rates <16G to avoid intermittent compliance failures. Reviewed-by: Relja Vojvodic Reviewed-by: Chris Park Signed-off-by: Ilya Bakoulin Signed-off-by: Ray Wu Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 378e096423269a466d23a5d5ce65051e8ef1e474) --- drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c index 52a22b3e78af..960f18fe275b 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c @@ -442,7 +442,7 @@ static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_s union hdmi_scdc_status_flags_data status_data = {0}; union hdmi_scdc_source_test_req test_req = {0}; union hdmi_scdc_LTP_req_data ltp_req = {0}; - unsigned long long poll_timeout_ns = 200 * 1000 * 1000; + unsigned long long poll_timeout_ns = 220 * 1000 * 1000; unsigned long long wait_time_ns = 2 * 1000 * 1000; unsigned long long num_polls = 0; struct hpo_frl_link_encoder *hpo_frl_link_enc = ddc_service->link->hpo_frl_link_enc; From 030d47cc6720e0f07b502876cdbd266e309f9461 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Sun, 11 Oct 2026 20:15:34 +0800 Subject: [PATCH 24/52] drm/amdkfd: don't drop an SVM CWSR reference that was never taken When kfd_queue_acquire_buffers() fails, its error path calls kfd_queue_release_buffers(), which also drops a queue_refcount on the SVM range at ctx_save_restore_area_address. The SVM range is the last buffer acquired, and kfd_queue_buffer_svm_get() takes no reference when it fails, so on every error path this reference was never taken. If another queue of the process uses that CWSR range, its reference is dropped instead, and unmapping the range no longer evicts that queue while its CWSR area is unmapped from the GPU. On navi31, with queue A using an SVM CWSR range: munmap() of the range logs Freeing queue vital buffer 0x..., queue evicted but after a CREATE_QUEUE that reuses A's CWSR address with an unmapped write pointer (-EINVAL), the same munmap() leaves A running without the message. Only put the BOs on the acquire error path. The warning and the eviction are back after the failed CREATE_QUEUE. Fixes: b049504e211e ("drm/amdkfd: Validate user queue svm memory residency") Signed-off-by: Jesse Zhang Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 9a08122e6361ef73f4b512d5db9596d561305519) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index ae3e7c54014a..6010a5d7a7eb 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -230,6 +230,15 @@ void kfd_queue_buffer_put(struct amdgpu_bo **bo) amdgpu_bo_unref(bo); } +static void kfd_queue_put_bos(struct queue_properties *properties) +{ + kfd_queue_buffer_put(&properties->wptr_bo); + kfd_queue_buffer_put(&properties->rptr_bo); + kfd_queue_buffer_put(&properties->ring_bo); + kfd_queue_buffer_put(&properties->eop_buf_bo); + kfd_queue_buffer_put(&properties->cwsr_bo); +} + int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_properties *properties) { struct kfd_topology_device *topo_dev; @@ -343,7 +352,13 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope * dropping the VM reservation. */ kfd_queue_unref_bo_vas(pdd, properties); - kfd_queue_release_buffers(pdd, properties); + /* + * The SVM CWSR range is the last thing acquired and a failed + * kfd_queue_buffer_svm_get() takes nothing, so only the BOs are held + * here. Dropping the SVM reference would drop the one of another queue + * using the same CWSR address. + */ + kfd_queue_put_bos(properties); return err; } @@ -352,11 +367,7 @@ int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_prope struct kfd_topology_device *topo_dev; u64 total_cwsr_size; - kfd_queue_buffer_put(&properties->wptr_bo); - kfd_queue_buffer_put(&properties->rptr_bo); - kfd_queue_buffer_put(&properties->ring_bo); - kfd_queue_buffer_put(&properties->eop_buf_bo); - kfd_queue_buffer_put(&properties->cwsr_bo); + kfd_queue_put_bos(properties); topo_dev = kfd_topology_device_by_id(pdd->dev->id); if (!topo_dev) From 0fe236afa2b9b28540fa1f8c789ef5d499c5d0ee Mon Sep 17 00:00:00 2001 From: Harish Kasiviswanathan Date: Mon, 31 Aug 2026 18:09:12 -0400 Subject: [PATCH 25/52] drm/amdkfd: fix NULL deref in SDMA HQD debugfs dump on GC 12.1 Reading the KFD HQD debugfs node (dqm_debugfs_hqds) on a gc_12_1_0 part oopses with a NULL pointer dereference: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: hqd_sdma_dump_v12_1+0x6a [amdgpu] Call Trace: dqm_debugfs_hqds+0x2da/0x360 [amdgpu] kfd_debugfs_hqds_by_device+0x74/0xa0 [amdgpu] seq_read ... __x64_sys_read get_sdma_rlc_reg_offset() assumes an interleaved layout where the second SDMA per XCC lives in a separate SDMA1_HWIP register aperture. On gc_12_1_0 that aperture does not exist. Rewrite get_sdma_rlc_reg_offset() to mirror sdma_v7_1_get_reg_offset(): base the offset on adev->reg_offset[GC_HWIP][xcc_id][0] and add the per-instance SDMA1 offset for the odd instance. Fixes: 7bd5d763b8e1 ("drm/amdkfd: Add gfx_v12_1_kfd2kgd interface for GFX12_1") Signed-off-by: Harish Kasiviswanathan Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher (cherry picked from commit 94b2c5b06d37adc0e9a23138252758657b64b58a) Cc: stable@vger.kernel.org --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c | 44 +++++++++++-------- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 9 ++++ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c index 0694bff871d2..5b526add8645 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c @@ -77,25 +77,21 @@ static uint32_t get_sdma_rlc_reg_offset(struct amdgpu_device *adev, unsigned int engine_id, unsigned int queue_id) { - uint32_t sdma_engine_reg_base = 0; - uint32_t sdma_rlc_reg_offset; uint32_t dev_inst = GET_INST(SDMA0, engine_id); + int xcc_id = adev->sdma.instance[engine_id].xcc_id; + int xcc_inst = dev_inst % adev->sdma.num_inst_per_xcc; + uint32_t sdma_engine_reg_base; + uint32_t sdma_rlc_reg_offset; - switch (dev_inst % adev->sdma.num_inst_per_xcc) { - case 0: - sdma_engine_reg_base = SOC15_REG_OFFSET(SDMA0, - dev_inst / adev->sdma.num_inst_per_xcc, - regSDMA0_SDMA_QUEUE0_RB_CNTL) - regSDMA0_SDMA_QUEUE0_RB_CNTL; - break; - case 1: - sdma_engine_reg_base = SOC15_REG_OFFSET(SDMA1, - dev_inst / adev->sdma.num_inst_per_xcc, - regSDMA1_SDMA_QUEUE0_RB_CNTL) - regSDMA0_SDMA_QUEUE0_RB_CNTL; - break; - default: - WARN(1, "Invalid SDMA engine id %d\n", engine_id); - break; - } + /* + * On SDMA v7 (gc_12_x) all SDMA queue registers live in the GC_HWIP + * aperture. The second SDMA per XCC is reached by adding a fixed + * offset (regSDMA1_* - regSDMA0_* == SDMA1_REG_OFFSET + */ + sdma_engine_reg_base = adev->reg_offset[GC_HWIP][xcc_id][0]; + if (xcc_inst != 0) + sdma_engine_reg_base += + regSDMA1_SDMA_QUEUE0_RB_CNTL - regSDMA0_SDMA_QUEUE0_RB_CNTL; sdma_rlc_reg_offset = sdma_engine_reg_base + queue_id * (regSDMA0_SDMA_QUEUE1_RB_CNTL - regSDMA0_SDMA_QUEUE0_RB_CNTL); @@ -141,8 +137,7 @@ static int hqd_sdma_dump_v12_1(struct amdgpu_device *adev, uint32_t engine_id, uint32_t queue_id, uint32_t (**dump)[2], uint32_t *n_regs) { - uint32_t sdma_rlc_reg_offset = get_sdma_rlc_reg_offset(adev, - engine_id, queue_id); + uint32_t sdma_rlc_reg_offset; uint32_t i = 0, reg; const uint32_t first_reg = regSDMA0_SDMA_QUEUE0_RB_CNTL; @@ -150,6 +145,17 @@ static int hqd_sdma_dump_v12_1(struct amdgpu_device *adev, #undef HQD_N_REGS #define HQD_N_REGS (last_reg - first_reg + 1) + /* + * dqm_debugfs_hqds() iterates engine_id over + * node_id * get_num_all_sdma_engines()..+get_num_all_sdma_engines(). + * On partitioned devices that range can address a logical SDMA engine + * beyond the instances that actually exist on this node; reject it + */ + if (engine_id >= adev->sdma.num_instances) + return -EINVAL; + + sdma_rlc_reg_offset = get_sdma_rlc_reg_offset(adev, engine_id, queue_id); + *dump = kmalloc(HQD_N_REGS*2*sizeof(uint32_t), GFP_KERNEL); if (*dump == NULL) return -ENOMEM; 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 5e57576fba21..ce67e56efa8c 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -4215,6 +4215,15 @@ int dqm_debugfs_hqds(struct seq_file *m, void *data) queue++) { r = dqm->dev->kfd2kgd->hqd_sdma_dump( dqm->dev->adev, pipe, queue, &dump, &n_regs); + if (r == -EINVAL) { + /* + * SDMA engine not present/mapped on this + * device; skip it instead of aborting the + * whole dump. + */ + r = 0; + break; + } if (r) break; From 232213c9bd7f703836164f23160b76657094a7db Mon Sep 17 00:00:00 2001 From: Elena Sakhnovitch Date: Wed, 14 Oct 2026 14:29:35 -0400 Subject: [PATCH 26/52] drm/amdgpu/gmc9: add extra type-2 TLB flush for gfx942/gfx950 Add extra type-2 TLB flush for GC 9.5.0, similar race condition was observed as Vega20+XGMI. Add to GC 9.4.3 and 9.4.4 too, as they share the same multi-XCD TLB topology. Reported-by: Shahzeb Ahmed Reported-by: Manjunath Gaonkar Signed-off-by: Elena Sakhnovitch Reviewed-by: Apurv Mishra Signed-off-by: Alex Deucher (cherry picked from commit 138cfb165ae6aa98c0767b431e718d1a8199db28) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c index 85ba228f002d..822681635a65 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c @@ -2179,10 +2179,22 @@ static int gmc_v9_0_hw_init(struct amdgpu_ip_block *ip_block) * (type 2), which flushes both. Due to a race condition with * concurrent memory accesses using the same TLB cache line, we still * need a second TLB flush after this. + * + * GC 9.4.3, 9.4.4 and 9.5.0 hit a similar race. */ - adev->gmc.flush_tlb_needs_extra_type_2 = - amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(9, 4, 0) && - adev->gmc.xgmi.num_physical_nodes; + switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { + case IP_VERSION(9, 4, 0): + adev->gmc.flush_tlb_needs_extra_type_2 = + adev->gmc.xgmi.num_physical_nodes; + break; + case IP_VERSION(9, 4, 3): + case IP_VERSION(9, 4, 4): + case IP_VERSION(9, 5, 0): + adev->gmc.flush_tlb_needs_extra_type_2 = true; + break; + default: + break; + } /* The sequence of these two function calls matters.*/ gmc_v9_0_init_golden_registers(adev); From 7b23bf82ccbdb4cf0752f2b8686e7319a7ce497f Mon Sep 17 00:00:00 2001 From: Keita Morisaki Date: Wed, 23 Sep 2026 11:24:51 +0900 Subject: [PATCH 27/52] drm/amd/display: Fix swap in median3() for DSC rate control median3() is meant to return the median of three values, but its last compare-and-swap step swaps b and c instead of a and b. When c is smaller than both a and b, it returns max(a, b) instead of min(a, b). For example, median3(2, 3, 1) returns 3 instead of 2. _do_calc_rc_params() always passes 0 as the first argument, and the second argument is never below 12. So whenever the third argument is negative, first_line_bpg_offset (and second_line_bpg_offset for 4:2:0) is set to the second argument instead of 0. Fix this by swapping a and b in the last step, as intended. Note that this change does not affect valid DSC configurations. The third argument is negative only when bits per pixel exceeds the uncompressed rate of the colour format: - 3 * bpc for 4:4:4, 3 * bpc + 2 for RGB - 2 * bpc for 4:2:2 - 1.5 * bpc for 4:2:0 Fixes: 97bda0322b8a ("drm/amd/display: Add DSC support for Navi (v2)") Signed-off-by: Keita Morisaki Tested-by: Dan Wheeler Reviewed-by: Roman Li Signed-off-by: Alex Deucher (cherry picked from commit 1ddc4bc97a62fc89d9d3681413d9f977e1fa1a83) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml/dsc/rc_calc_fpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/dsc/rc_calc_fpu.c b/drivers/gpu/drm/amd/display/dc/dml/dsc/rc_calc_fpu.c index 71eedb3301e4..62673dc8b04a 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/dsc/rc_calc_fpu.c +++ b/drivers/gpu/drm/amd/display/dc/dml/dsc/rc_calc_fpu.c @@ -45,7 +45,7 @@ static int median3(int a, int b, int c) if (b > c) swap(b, c); if (a > b) - swap(b, c); + swap(a, b); return b; } From 47d799a718439764432d8ef3a0952af45f025f76 Mon Sep 17 00:00:00 2001 From: William Palacek Date: Fri, 16 Oct 2026 10:44:50 -0400 Subject: [PATCH 28/52] drm/amdkfd: wait for a GPU reset before freeing a BO When a GPU reset takes the reset domain, KFD eviction cannot unmap user queues: execute_queues_cpsch() fails its trylock with -EIO, so the queues keep running until the ASIC is reset. The TLB flush is also skipped while the reset is in progress. If the process unmaps and frees a BO in that window, its pages are released while a queue can still access them through stale translations. On MI300X, munmap() then crashes in free_swap_cache() on a small bogus page pointer in its mmu_gather batch page: BUG: unable to handle page fault for address: 0000000000002900 RIP: 0010:free_swap_cache+0x5/0xb0 Call Trace: free_pages_and_swap_cache+0x3a/0x60 tlb_batch_pages_flush+0x43/0x80 tlb_flush_mmu+0x3d/0x110 zap_pte_range+0x80d/0xcd0 ... do_vmi_align_munmap+0x3e8/0x5b0 do_vmi_munmap+0xdf/0x190 __vm_munmap+0xad/0x180 __x64_sys_munmap+0x1b/0x30 In a traced crash the bogus pointer was the read pointer value of an SDMA queue the process had just destroyed, at the read pointer's page offset: the queue's writeback landed in its freed rptr page after the page was reused as the batch page. commit f0b4440cdc18 ("drm/amdkfd: Fix mode1 reset crash issue") fixed this race on process exit by waiting for the reset to complete before freeing the process's BOs. Free through the ioctl has the same race. Wait for the reset there too, using the same helper. Fixes: 9c33e5fd4fb6 ("drm/amdgpu: fix locking scope when flushing tlb") Reviewed-by: Felix Kuehling Signed-off-by: William Palacek Signed-off-by: Alex Deucher (cherry picked from commit 06ff8ce77726b15a98fcc285dd6005cb3f48558b) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 3 +++ drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 1 + drivers/gpu/drm/amd/amdkfd/kfd_process.c | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 3081d9aaf053..3831451240e9 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1303,6 +1303,9 @@ static int kfd_ioctl_free_memory_of_gpu(struct file *filep, goto err_unlock; } + /* Queues a reset could not stop may still access the buffer */ + kfd_process_wait_gpu_reset_complete(p); + ret = amdgpu_amdkfd_gpuvm_free_memory_of_gpu(pdd->dev->adev, (struct kgd_mem *)mem, pdd->drm_priv, &size); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index e102086ac49a..288731acc928 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -1133,6 +1133,7 @@ void kfd_unref_process(struct kfd_process *p); int kfd_process_evict_queues(struct kfd_process *p, uint32_t trigger); int kfd_process_restore_queues(struct kfd_process *p); void kfd_suspend_all_processes(void); +void kfd_process_wait_gpu_reset_complete(struct kfd_process *p); int kfd_resume_all_processes(void); struct kfd_process_device *kfd_process_device_data_by_id(struct kfd_process *process, diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index 51b0df498494..6663718fb83c 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -1294,7 +1294,7 @@ static void kfd_process_remove_sysfs(struct kfd_process *p) /* * If any GPU is ongoing reset, wait for reset complete. */ -static void kfd_process_wait_gpu_reset_complete(struct kfd_process *p) +void kfd_process_wait_gpu_reset_complete(struct kfd_process *p) { int i; From 53d9ef2f518add783bb48a69d77bbc8aea99e911 Mon Sep 17 00:00:00 2001 From: John Fisher Date: Fri, 9 Oct 2026 09:45:17 +1300 Subject: [PATCH 29/52] drm/amdgpu: wait for pending page table updates before switching a VM to CPU updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_vm_make_compute() switches a VM from SDMA to CPU page table updates when a KFD process acquires it. It syncs "with the last SDMA update/clear" through amdgpu_bo_sync_wait() on the root PD, but that walks the reservation with DMA_RESV_USAGE_READ, and the clears of the VM's page tables (the root PD from amdgpu_vm_init(), and the PDs/PTs the seq64 mapping allocates at the render node's open) are added there by amdgpu_vm_sdma_commit() with DMA_RESV_USAGE_BOOKKEEP only: amdgpu_vm_pt_clear() commits them with a NULL fence, so they do not reach vm->last_update. The CPU path's own waits (amdgpu_vm_cpu_update(), amdgpu_bo_kmap()) use DMA_RESV_USAGE_KERNEL. None of them waits for a clear that is still queued. When one is, the CPU writes the first entries of the new compute VM (the KFD trap handler's TBA/TMA page at AMDGPU_VA_RESERVED_TRAP_START, mapped during ACQUIRE_VM) into a page directory the SDMA then clears. The entries are gone, the handler's page is unmapped, and the first wave save of any queue of the process faults in the SQC instruction fetch at the handler's address (GCVM_L2_PROTECTION_FAULT_STATUS 0x008012B1, PERMISSION_FAULTS 0xb). The MES then fails REMOVE_QUEUE and SUSPEND, and the device takes a MODE1 reset with VRAM lost on 7.2.9; on 7.3-rc6 the queue is reset instead. The window is the time between the render node's open and ACQUIRE_VM. ROCr spends milliseconds there and is practically never hit; a minimal KFD client that opens the render node and acquires the VM at once is hit most of the time. On a Radeon AI PRO R9700 (gfx1201, Resizable BAR, so vm_update_mode defaults to CPU updates for compute VMs), a wave reading the trap handler's page right after queue creation found it unmapped in 8 of 12 runs; a 5 ms sleep between the open and ACQUIRE_VM made that 0 of 9, and the same sleep after queue creation instead 3 of 3. The wait covered these jobs until commit 926c79ad6ecd ("drm/amdgpu: promote the implicit sync to the dependent read fences") narrowed amdgpu_sync_resv() from DMA_RESV_USAGE_BOOKKEEP to DMA_RESV_USAGE_READ; the VM's page table jobs have been added with BOOKKEEP since commit 4247084057cf ("drm/amdgpu: use DMA_RESV_USAGE_BOOKKEEP v2"), so from v6.16 on the sync in amdgpu_vm_make_compute() waits for nothing of them. Record the clears in vm->last_update, as amdgpu_vm_update_pdes() does with its updates, and wait for it, uninterruptibly, before switching the update functions. The clears all go through vm->delayed, so the last one covers the earlier ones; immediate clears are not recorded, as before. With this, on 7.3-rc6 and on 7.2.9, the same client reads the handler 10 of 10 times, 8 busy queues on the 4 compute slots complete in 5 of 5 runs on 7.3-rc6 and 3 of 3 on 7.2.9 (1 of 6 before), 16 and 32 queues complete on 7.3-rc6, and two processes with 8 queues each complete together on both, with no fault. Fixes: 926c79ad6ecd ("drm/amdgpu: promote the implicit sync to the dependent read fences") Cc: Prike Liang Reviewed-by: Christian König Signed-off-by: John Fisher Signed-off-by: Alex Deucher (cherry picked from commit 9ec91b35baba45209c3fcd00f086224a158dd512) Cc: stable@vger.kernel.org # v6.16+ --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 5 +---- drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 2bd07007def4..7e82a7a0b458 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2737,10 +2737,7 @@ int amdgpu_vm_make_compute(struct amdgpu_device *adev, struct amdgpu_vm *vm) if (vm->use_cpu_for_update) { /* Sync with last SDMA update/clear before switching to CPU */ - r = amdgpu_bo_sync_wait(vm->root.bo, - AMDGPU_FENCE_OWNER_UNDEFINED, true); - if (r) - goto unreserve_bo; + dma_fence_wait(vm->last_update, false); vm->update_funcs = &amdgpu_vm_cpu_funcs; r = amdgpu_vm_pt_map_tables(adev, vm); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c index 599c0085cdd9..c73eb416c0e2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_pt.c @@ -427,7 +427,7 @@ int amdgpu_vm_pt_clear(struct amdgpu_device *adev, struct amdgpu_vm *vm, if (r) goto exit; - r = vm->update_funcs->commit(¶ms, NULL); + r = vm->update_funcs->commit(¶ms, &vm->last_update); exit: drm_dev_exit(idx); return r; From 44d0d3031e47973ed65fd327669d5e2eaaf6c565 Mon Sep 17 00:00:00 2001 From: Kenneth Feng Date: Fri, 16 Oct 2026 15:17:43 +0800 Subject: [PATCH 30/52] drm/amd/pm:restate all customized OD features on every table upload for smu 13.0.0/smu 14.0.2 PMFW interprets OverDriveTable.FeatureCtrlMask as the complete set of overdrive features that are currently overridden, not as a delta describing the operation at hand. On import it resets every feature whose bit is clear back to its default value - for the VF curve that means zeroing VoltageOffsetPerZoneBoundary[] outright. The driver assumed the opposite. smu_v14_0_2_od_edit_dpm_table() clears the mask after a successful commit, so a subsequent above-default power1_cap write goes through smu_v14_0_2_set_ppt_limit() and uploads a table carrying only PP_OD_FEATURE_PPT_BIT. PMFW then drops the user's voltage offset and reverts to the stock V/F curve, while the driver's cached user table still reports the offset through pp_od_clk_voltage. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5935 Signed-off-by: Kenneth Feng Reviewed-by: Kevin Wang Signed-off-by: Alex Deucher (cherry picked from commit 1b63bb397db208a8f84778a9d9cafc93da9373eb) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 2 + .../drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 35 +++++++---------- .../drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 32 +++++++--------- .../drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 38 +++++++++++-------- 4 files changed, 52 insertions(+), 55 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 f516d0d3bb92..9d47d2ab1b49 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -433,6 +433,8 @@ struct smu_table_context { void *boot_overdrive_table; void *user_overdrive_table; + uint32_t user_od_feature_mask; + struct smu_driver_table driver_tables[SMU_DRIVER_TABLE_COUNT]; }; 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 6beafabf1306..468aaeb79ffb 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 @@ -1569,7 +1569,6 @@ static int smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu, struct amdgpu_device *adev = smu->adev; uint32_t offset_of_voltageoffset; int32_t minimum, maximum; - uint32_t feature_ctrlmask; int i, ret = 0; switch (type) { @@ -1891,21 +1890,16 @@ static int smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu, if (ret) return ret; } else { - feature_ctrlmask = od_table->OverDriveTable.FeatureCtrlMask; memcpy(od_table, table_context->boot_overdrive_table, sizeof(OverDriveTableExternal_t)); - od_table->OverDriveTable.FeatureCtrlMask = feature_ctrlmask; + od_table->OverDriveTable.FeatureCtrlMask = 0; + table_context->user_od_feature_mask = 0; } fallthrough; case PP_OD_COMMIT_DPM_TABLE: - /* - * The member below instructs PMFW the settings focused in - * this single operation. - * `uint32_t FeatureCtrlMask;` - * It does not contain actual informations about user's custom - * settings. Thus we do not cache it. - */ + table_context->user_od_feature_mask = + od_table->OverDriveTable.FeatureCtrlMask; offset_of_voltageoffset = offsetof(OverDriveTable_t, VoltageOffsetPerZoneBoundary); if (memcmp((u8 *)od_table + offset_of_voltageoffset, table_context->user_overdrive_table + offset_of_voltageoffset, @@ -1918,7 +1912,6 @@ static int smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu, return ret; } - od_table->OverDriveTable.FeatureCtrlMask = 0; memcpy(table_context->user_overdrive_table + offset_of_voltageoffset, (u8 *)od_table + offset_of_voltageoffset, sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset); @@ -2201,6 +2194,7 @@ static int smu_v13_0_0_set_default_od_settings(struct smu_context *smu) boot_od_table, sizeof(OverDriveTableExternal_t)); smu->user_dpm_profile.user_od = false; + smu->smu_table.user_od_feature_mask = 0; } else if (smu->user_dpm_profile.user_od) { memcpy(&user_od_table_bak, user_od_table, @@ -2250,12 +2244,8 @@ static int smu_v13_0_0_restore_user_od_settings(struct smu_context *smu) OverDriveTableExternal_t *od_table = table_context->overdrive_table; OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; int res; - - user_od_table->OverDriveTable.FeatureCtrlMask = BIT(PP_OD_FEATURE_GFXCLK_BIT) | - BIT(PP_OD_FEATURE_UCLK_BIT) | - BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT) | - BIT(PP_OD_FEATURE_FAN_CURVE_BIT) | - BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + user_od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; res = smu_v13_0_0_upload_overdrive_table(smu, user_od_table); user_od_table->OverDriveTable.FeatureCtrlMask = 0; if (res == 0) @@ -3069,8 +3059,10 @@ static int smu_v13_0_0_set_ppt_limit(struct smu_context *smu, if (current_od_table.OverDriveTable.Ppt) { od_table->OverDriveTable.Ppt = 0; - od_table->OverDriveTable.FeatureCtrlMask |= 1U << PP_OD_FEATURE_PPT_BIT; - + table_context->user_od_feature_mask &= + ~(1U << PP_OD_FEATURE_PPT_BIT); + od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; ret = smu_v13_0_0_upload_overdrive_table(smu, od_table); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); @@ -3084,8 +3076,9 @@ static int smu_v13_0_0_set_ppt_limit(struct smu_context *smu, return ret; od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit - 100; - od_table->OverDriveTable.FeatureCtrlMask |= 1U << PP_OD_FEATURE_PPT_BIT; - + table_context->user_od_feature_mask |= 1U << PP_OD_FEATURE_PPT_BIT; + od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; ret = smu_v13_0_0_upload_overdrive_table(smu, od_table); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); 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 5fe409a23772..51e419e6380c 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 @@ -1578,7 +1578,6 @@ static int smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu, struct amdgpu_device *adev = smu->adev; uint32_t offset_of_voltageoffset; int32_t minimum, maximum; - uint32_t feature_ctrlmask; int i, ret = 0; switch (type) { @@ -1900,22 +1899,17 @@ static int smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu, if (ret) return ret; } else { - feature_ctrlmask = od_table->OverDriveTable.FeatureCtrlMask; memcpy(od_table, table_context->boot_overdrive_table, sizeof(OverDriveTableExternal_t)); - od_table->OverDriveTable.FeatureCtrlMask = feature_ctrlmask; + od_table->OverDriveTable.FeatureCtrlMask = 0; + table_context->user_od_feature_mask = 0; } fallthrough; case PP_OD_COMMIT_DPM_TABLE: - /* - * The member below instructs PMFW the settings focused in - * this single operation. - * `uint32_t FeatureCtrlMask;` - * It does not contain actual informations about user's custom - * settings. Thus we do not cache it. - */ + table_context->user_od_feature_mask = + od_table->OverDriveTable.FeatureCtrlMask; offset_of_voltageoffset = offsetof(OverDriveTable_t, VoltageOffsetPerZoneBoundary); if (memcmp((u8 *)od_table + offset_of_voltageoffset, table_context->user_overdrive_table + offset_of_voltageoffset, @@ -1928,7 +1922,6 @@ static int smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu, return ret; } - od_table->OverDriveTable.FeatureCtrlMask = 0; memcpy(table_context->user_overdrive_table + offset_of_voltageoffset, (u8 *)od_table + offset_of_voltageoffset, sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset); @@ -2203,6 +2196,7 @@ static int smu_v13_0_7_set_default_od_settings(struct smu_context *smu) boot_od_table, sizeof(OverDriveTableExternal_t)); smu->user_dpm_profile.user_od = false; + smu->smu_table.user_od_feature_mask = 0; } else if (smu->user_dpm_profile.user_od) { memcpy(&user_od_table_bak, user_od_table, @@ -2253,11 +2247,8 @@ static int smu_v13_0_7_restore_user_od_settings(struct smu_context *smu) OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; int res; - user_od_table->OverDriveTable.FeatureCtrlMask = BIT(PP_OD_FEATURE_GFXCLK_BIT) | - BIT(PP_OD_FEATURE_UCLK_BIT) | - BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT) | - BIT(PP_OD_FEATURE_FAN_CURVE_BIT) | - BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + user_od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; res = smu_v13_0_7_upload_overdrive_table(smu, user_od_table); user_od_table->OverDriveTable.FeatureCtrlMask = 0; if (res == 0) @@ -2689,7 +2680,10 @@ static int smu_v13_0_7_set_ppt_limit(struct smu_context *smu, if (current_od_table.OverDriveTable.Ppt) { od_table->OverDriveTable.Ppt = 0; - od_table->OverDriveTable.FeatureCtrlMask |= 1U << PP_OD_FEATURE_PPT_BIT; + table_context->user_od_feature_mask &= + ~(1U << PP_OD_FEATURE_PPT_BIT); + od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; ret = smu_v13_0_7_upload_overdrive_table(smu, od_table); if (ret) { @@ -2704,7 +2698,9 @@ static int smu_v13_0_7_set_ppt_limit(struct smu_context *smu, return ret; od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit - 100; - od_table->OverDriveTable.FeatureCtrlMask |= 1U << PP_OD_FEATURE_PPT_BIT; + table_context->user_od_feature_mask |= 1U << PP_OD_FEATURE_PPT_BIT; + od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; ret = smu_v13_0_7_upload_overdrive_table(smu, od_table); if (ret) { 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 ed99e61f18e1..549716349117 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 @@ -2360,6 +2360,7 @@ static int smu_v14_0_2_set_default_od_settings(struct smu_context *smu) boot_od_table, sizeof(OverDriveTableExternal_t)); smu->user_dpm_profile.user_od = false; + smu->smu_table.user_od_feature_mask = 0; } else if (smu->user_dpm_profile.user_od) { memcpy(&user_od_table_bak, user_od_table, @@ -2406,11 +2407,8 @@ static int smu_v14_0_2_restore_user_od_settings(struct smu_context *smu) OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; int res; - user_od_table->OverDriveTable.FeatureCtrlMask = BIT(PP_OD_FEATURE_GFXCLK_BIT) | - BIT(PP_OD_FEATURE_UCLK_BIT) | - BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT) | - BIT(PP_OD_FEATURE_FAN_CURVE_BIT) | - BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + user_od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; res = smu_v14_0_2_upload_overdrive_table(smu, user_od_table); user_od_table->OverDriveTable.FeatureCtrlMask = 0; if (res == 0) @@ -2490,7 +2488,6 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, struct amdgpu_device *adev = smu->adev; uint32_t offset_of_voltageoffset; int32_t minimum, maximum; - uint32_t feature_ctrlmask; int i, ret = 0; switch (type) { @@ -2758,21 +2755,26 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, if (ret) return ret; } else { - feature_ctrlmask = od_table->OverDriveTable.FeatureCtrlMask; memcpy(od_table, table_context->boot_overdrive_table, sizeof(OverDriveTableExternal_t)); - od_table->OverDriveTable.FeatureCtrlMask = feature_ctrlmask; + od_table->OverDriveTable.FeatureCtrlMask = 0; + table_context->user_od_feature_mask = 0; } fallthrough; case PP_OD_COMMIT_DPM_TABLE: /* - * The member below instructs PMFW the settings focused in - * this single operation. - * `uint32_t FeatureCtrlMask;` - * It does not contain actual informations about user's custom - * settings. Thus we do not cache it. + * PMFW interprets FeatureCtrlMask as the complete set of OD + * features that are currently overridden, not as a delta + * describing this single operation. Any feature whose bit is + * clear gets reset to its default on import. od_table's mask + * has accumulated every feature the user touched since the + * last full restore, so record it and keep restating it on + * subsequent uploads. */ + table_context->user_od_feature_mask = + od_table->OverDriveTable.FeatureCtrlMask; + offset_of_voltageoffset = offsetof(OverDriveTable_t, VoltageOffsetPerZoneBoundary); if (memcmp((u8 *)od_table + offset_of_voltageoffset, table_context->user_overdrive_table + offset_of_voltageoffset, @@ -2785,7 +2787,6 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, return ret; } - od_table->OverDriveTable.FeatureCtrlMask = 0; memcpy(table_context->user_overdrive_table + offset_of_voltageoffset, (u8 *)od_table + offset_of_voltageoffset, sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset); @@ -2829,7 +2830,10 @@ static int smu_v14_0_2_set_ppt_limit(struct smu_context *smu, if (current_od_table.OverDriveTable.Ppt) { od_table->OverDriveTable.Ppt = 0; - od_table->OverDriveTable.FeatureCtrlMask |= 1U << PP_OD_FEATURE_PPT_BIT; + table_context->user_od_feature_mask &= + ~(1U << PP_OD_FEATURE_PPT_BIT); + od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; ret = smu_v14_0_2_upload_overdrive_table(smu, od_table); if (ret) { @@ -2844,7 +2848,9 @@ static int smu_v14_0_2_set_ppt_limit(struct smu_context *smu, return ret; od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit - 100; - od_table->OverDriveTable.FeatureCtrlMask |= 1U << PP_OD_FEATURE_PPT_BIT; + table_context->user_od_feature_mask |= 1U << PP_OD_FEATURE_PPT_BIT; + od_table->OverDriveTable.FeatureCtrlMask = + table_context->user_od_feature_mask; ret = smu_v14_0_2_upload_overdrive_table(smu, od_table); if (ret) { From 16df12d7158fb55f20d6e985abd1996cbabab9f8 Mon Sep 17 00:00:00 2001 From: Daniel Phillips Date: Sun, 5 Mar 2023 22:54:35 -0800 Subject: [PATCH 31/52] amdkfd: Fix memory availability double accounting of kfd pinned objects Pinned objects that are not kfd objects reduce the total vram available to kfd, so we subtract the total size of pinned objects from kdf vram availability. However this double counts objects pinned by kfd itself, because they are counted both as used and pinned. So track the total size of objects pinned by kfd and add it back to kfd availability to remove the double accounting. Signed-off-by: Daniel Phillips Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit b8257bdb26ca5d9a75f4b81f629422f17afa221b) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index e3da382ceb3c..a53d7e4a1eb5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -106,6 +106,7 @@ struct amdgpu_kfd_dev { struct kfd_dev *dev; int64_t vram_used[MAX_XCP]; uint64_t vram_used_aligned[MAX_XCP]; + atomic64_t vram_pinned; bool init_complete; struct work_struct reset_work; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 9a7e14a3c719..129acb88dc11 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -1536,6 +1536,11 @@ static int amdgpu_amdkfd_gpuvm_pin_bo(struct amdgpu_bo *bo, u32 domain) pr_err("Error in Pinning BO to domain: %d\n", domain); amdgpu_bo_sync_wait(bo, AMDGPU_FENCE_OWNER_KFD, false); + + if (!ret && bo->tbo.resource->mem_type == TTM_PL_VRAM) + atomic64_add(amdgpu_bo_size(bo), + &amdgpu_ttm_adev(bo->tbo.bdev)->kfd.vram_pinned); + out: amdgpu_bo_unreserve(bo); return ret; @@ -1558,6 +1563,11 @@ static void amdgpu_amdkfd_gpuvm_unpin_bo(struct amdgpu_bo *bo) return; amdgpu_bo_unpin(bo); + + if (bo->tbo.resource->mem_type == TTM_PL_VRAM) + atomic64_sub(amdgpu_bo_size(bo), + &amdgpu_ttm_adev(bo->tbo.bdev)->kfd.vram_pinned); + amdgpu_bo_unreserve(bo); } @@ -1679,6 +1689,7 @@ size_t amdgpu_amdkfd_get_available_memory(struct amdgpu_device *adev, vram_available = KFD_XCP_MEMORY_SIZE(adev, xcp_id) - adev->kfd.vram_used_aligned[xcp_id] - atomic64_read(&adev->vram_pin_size) + + atomic64_read(&adev->kfd.vram_pinned) - reserved_for_pt - reserved_for_ras; From 4e356be7da54f26aa65a7ac641238cbe5e02d439 Mon Sep 17 00:00:00 2001 From: Horatio Zhang Date: Thu, 17 Sep 2026 17:33:59 +0800 Subject: [PATCH 32/52] drm/amdgpu: skip the PASID TLB fence for the UALink NPA VM amdgpu_vm_make_npa() builds the UALink NPA VM on top of amdgpu_vm_make_compute(), which unconditionally sets need_tlb_fence. That flag makes amdgpu_vm_tlb_flush() queue an asynchronous PASID based TLB invalidation after every page table update. The NPA VM is created by the driver itself in amdgpu_ualink_manager_start() and never gets a PASID assigned, so vm->pasid stays 0, which MES cannot resolve on the MMHUB. The invalidation never completes and MES stays unresponsive for the rest of the probe: amdgpu: MES(0, 0) failed to respond to msg=INVALIDATE_TLBS The invalidation is not needed in the first place. An NPA VM is bound to the reserved NPA VMID and amdgpu_ualink_flush_tlb() already invalidates that VMID on the MMHUB after every mapping change. Clear need_tlb_fence for NPA VMs. Fixes: e05a54573df7 ("drm/amdgpu: Pin page tables in NPA VMs") Signed-off-by: Horatio Zhang Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit e419e52cad769df45f496b7621841b25b63235da) --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 7e82a7a0b458..c41e9c0916b7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2794,6 +2794,7 @@ int amdgpu_vm_make_npa(struct amdgpu_device *adev, struct amdgpu_vm *vm) return r; vm->is_npa = true; + vm->need_tlb_fence = false; return 0; } From 9131cc85d0dedd8ba4cae60dbe5985d081551f30 Mon Sep 17 00:00:00 2001 From: Zhen Han Date: Sun, 20 Sep 2026 02:31:31 +0000 Subject: [PATCH 33/52] drm/amdkfd: correct vram_available calculation reserve_mem_limit() computes available VRAM as vram_size - reserved - vram_pin_size - kfd.vram_used However, kfd.vram_used already includes BOs that are later pinned. Pinning does not consume extra VRAM. Subtracting vram_pin_size counts those bytes twice and causes fake-OOM while VRAM is still free. Reuse get_available_memory() in reserve_mem_limit(). Move ALIGN_DOWN to the ioctl so the userspace ABI is unchanged. The VRAM counter is a parameter: the ioctl passes vram_used_aligned[], the gate passes vram_used[] so a 4K BO is not charged 2MB. kfd.vram_pinned is only updated in gpuvm_pin_bo(). dma-buf pin calls amdgpu_bo_pin() only, so the counter stays zero. Update it from amdgpu_bo_pin() through a KFD helper, on first pin and last unpin, matching vram_pin_size. Track per-XCP pins in vram_pin_size_xcp[] and kfd.vram_pinned[]. Pins that are not owned by a single partition go to vram_pin_size_common, which every partition subtracts. A KFD VRAM BO always carries a partition, so the KFD credit is only correct for xcp >= 0. Call the helper for both branches and let it warn once and skip if that ever stops holding: without the credit the BO would be subtracted through vram_pin_size_common and never added back, which is the double counting this patch removes, and kfd.vram_pinned[] would be indexed out of bounds. Suggested-by: Philip Yang Signed-off-by: Zhen Han Reviewed-by: Mukul Joshi Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 894cbb31c50e3cf49657130396bbf6c0475304e4) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 11 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 20 +++- .../gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 94 +++++++++++-------- drivers/gpu/drm/amd/amdgpu/amdgpu_object.c | 33 +++++++ drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 12 ++- 5 files changed, 127 insertions(+), 43 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index ce671731b700..c7577b608f3e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -844,8 +844,17 @@ struct amdgpu_device { struct mutex mn_lock; DECLARE_HASHTABLE(mn_hash, 7); - /* tracking pinned memory */ + /* tracking pinned memory + * + * vram_pin_size = sum(vram_pin_size_xcp[]) + vram_pin_size_common + * + * vram_pin_size total pin size in device + * vram_pin_size_xcp[i] pins owned by partition i (bo->xcp_id = i, i >= 0) + * vram_pin_size_common pins not owned by any partition, bo->xcp_id = -1 + */ atomic64_t vram_pin_size; + atomic64_t vram_pin_size_xcp[MAX_XCP]; + atomic64_t vram_pin_size_common; atomic64_t visible_pin_size; atomic64_t gart_pin_size; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index a53d7e4a1eb5..77c0504cc158 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -42,6 +42,12 @@ extern uint64_t amdgpu_amdkfd_total_mem_size; +/* + * Align VRAM availability to 2MB to avoid fragmentation caused by 4K + * allocations in the tail 2MB BO chunk. + */ +#define VRAM_AVAILABLITY_ALIGN (1 << 21) + enum TLB_FLUSH_TYPE { TLB_FLUSH_LEGACY = 0, TLB_FLUSH_LIGHTWEIGHT, @@ -106,7 +112,7 @@ struct amdgpu_kfd_dev { struct kfd_dev *dev; int64_t vram_used[MAX_XCP]; uint64_t vram_used_aligned[MAX_XCP]; - atomic64_t vram_pinned; + atomic64_t vram_pinned[MAX_XCP]; bool init_complete; struct work_struct reset_work; @@ -390,6 +396,8 @@ void amdgpu_amdkfd_gpuvm_destroy_cb(struct amdgpu_device *adev, * Allows KFD to release its resources associated with the GEM object. */ void amdgpu_amdkfd_release_notify(struct amdgpu_bo *bo); +void amdgpu_amdkfd_account_vram_pin(struct amdgpu_bo *bo, int xcp_id); +void amdgpu_amdkfd_account_vram_unpin(struct amdgpu_bo *bo, int xcp_id); void amdgpu_amdkfd_reserve_system_mem(uint64_t size); #else static inline @@ -407,6 +415,16 @@ static inline void amdgpu_amdkfd_release_notify(struct amdgpu_bo *bo) { } + +static inline +void amdgpu_amdkfd_account_vram_pin(struct amdgpu_bo *bo, int xcp_id) +{ +} + +static inline +void amdgpu_amdkfd_account_vram_unpin(struct amdgpu_bo *bo, int xcp_id) +{ +} #endif #if IS_ENABLED(CONFIG_HSA_AMD_SVM) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 129acb88dc11..ff77e8ae9fe6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -46,12 +46,6 @@ #define AMDGPU_USERPTR_RESTORE_DELAY_MS 1 #define AMDGPU_RESERVE_MEM_LIMIT (3UL << 29) -/* - * Align VRAM availability to 2MB to avoid fragmentation caused by 4K allocations in the tail 2MB - * BO chunk - */ -#define VRAM_AVAILABLITY_ALIGN (1 << 21) - /* Impose limit on how much memory KFD can use */ static struct { uint64_t max_system_mem_limit; @@ -73,6 +67,8 @@ static const char * const domain_bit_to_string[] = { #define domain_string(domain) domain_bit_to_string[ffs(domain)-1] static void amdgpu_amdkfd_restore_userptr_worker(struct work_struct *work); +static size_t __get_available_memory(struct amdgpu_device *adev, + uint8_t xcp_id, int64_t vram_used); static bool kfd_mem_is_attached(struct amdgpu_vm *avm, struct kgd_mem *mem) @@ -169,13 +165,8 @@ void amdgpu_amdkfd_reserve_system_mem(uint64_t size) int amdgpu_amdkfd_reserve_mem_limit(struct amdgpu_device *adev, uint64_t size, u32 alloc_flag, int8_t xcp_id) { - uint64_t reserved_for_pt = - ESTIMATE_PT_SIZE(amdgpu_amdkfd_total_mem_size); - struct amdgpu_ras *con = amdgpu_ras_get_context(adev); - uint64_t reserved_for_ras = (con ? con->reserved_pages_in_bytes : 0); size_t system_mem_needed, ttm_mem_needed, vram_needed; int ret = 0; - uint64_t vram_size = 0; system_mem_needed = 0; ttm_mem_needed = 0; @@ -196,7 +187,6 @@ int amdgpu_amdkfd_reserve_mem_limit(struct amdgpu_device *adev, if (WARN_ONCE(xcp_id < 0, "invalid XCP ID %d", xcp_id)) return -EINVAL; - vram_size = KFD_XCP_MEMORY_SIZE(adev, xcp_id); if (adev->apu_prefer_gtt) { system_mem_needed = size; ttm_mem_needed = size; @@ -232,14 +222,11 @@ int amdgpu_amdkfd_reserve_mem_limit(struct amdgpu_device *adev, * VRAM check since ttm_mem_limit check already cover this allocation */ - if (adev && xcp_id >= 0 && (!adev->apu_prefer_gtt || adev->gmc.is_app_apu)) { - uint64_t vram_available = - vram_size - reserved_for_pt - reserved_for_ras - - atomic64_read(&adev->vram_pin_size); - if (adev->kfd.vram_used[xcp_id] + vram_needed > vram_available) { - ret = -ENOMEM; - goto release; - } + if (adev && xcp_id >= 0 && (!adev->apu_prefer_gtt || adev->gmc.is_app_apu) && + vram_needed > __get_available_memory(adev, xcp_id, + adev->kfd.vram_used[xcp_id])) { + ret = -ENOMEM; + goto release; } /* Update memory accounting by decreasing available system @@ -318,6 +305,30 @@ void amdgpu_amdkfd_release_notify(struct amdgpu_bo *bo) kfree(bo->kfd_bo); } +void amdgpu_amdkfd_account_vram_pin(struct amdgpu_bo *bo, int xcp_id) +{ + if (!bo->kfd_bo) + return; + + if (WARN_ONCE(xcp_id < 0, "KFD BO pinned in VRAM with no partition")) + return; + + atomic64_add(amdgpu_bo_size(bo), + &amdgpu_ttm_adev(bo->tbo.bdev)->kfd.vram_pinned[xcp_id]); +} + +void amdgpu_amdkfd_account_vram_unpin(struct amdgpu_bo *bo, int xcp_id) +{ + if (!bo->kfd_bo) + return; + + if (WARN_ONCE(xcp_id < 0, "KFD BO unpinned in VRAM with no partition")) + return; + + atomic64_sub(amdgpu_bo_size(bo), + &amdgpu_ttm_adev(bo->tbo.bdev)->kfd.vram_pinned[xcp_id]); +} + /** * create_dmamap_sg_bo() - Creates a amdgpu_bo object to reflect information * about USERPTR or DOOREBELL or MMIO BO. @@ -1537,10 +1548,6 @@ static int amdgpu_amdkfd_gpuvm_pin_bo(struct amdgpu_bo *bo, u32 domain) amdgpu_bo_sync_wait(bo, AMDGPU_FENCE_OWNER_KFD, false); - if (!ret && bo->tbo.resource->mem_type == TTM_PL_VRAM) - atomic64_add(amdgpu_bo_size(bo), - &amdgpu_ttm_adev(bo->tbo.bdev)->kfd.vram_pinned); - out: amdgpu_bo_unreserve(bo); return ret; @@ -1564,10 +1571,6 @@ static void amdgpu_amdkfd_gpuvm_unpin_bo(struct amdgpu_bo *bo) amdgpu_bo_unpin(bo); - if (bo->tbo.resource->mem_type == TTM_PL_VRAM) - atomic64_sub(amdgpu_bo_size(bo), - &amdgpu_ttm_adev(bo->tbo.bdev)->kfd.vram_pinned); - amdgpu_bo_unreserve(bo); } @@ -1671,8 +1674,9 @@ int amdgpu_amdkfd_criu_resume(void *p) return ret; } -size_t amdgpu_amdkfd_get_available_memory(struct amdgpu_device *adev, - uint8_t xcp_id) +/* Caller must hold kfd_mem_limit.mem_limit_lock. */ +static size_t __get_available_memory(struct amdgpu_device *adev, + uint8_t xcp_id, int64_t vram_used) { uint64_t reserved_for_pt = ESTIMATE_PT_SIZE(amdgpu_amdkfd_total_mem_size); @@ -1681,15 +1685,17 @@ size_t amdgpu_amdkfd_get_available_memory(struct amdgpu_device *adev, ssize_t available; uint64_t vram_available, system_mem_available, ttm_mem_available; - spin_lock(&kfd_mem_limit.mem_limit_lock); + lockdep_assert_held(&kfd_mem_limit.mem_limit_lock); + if (adev->apu_prefer_gtt && !adev->gmc.is_app_apu) vram_available = KFD_XCP_MEMORY_SIZE(adev, xcp_id) - - adev->kfd.vram_used_aligned[xcp_id]; + - vram_used; else vram_available = KFD_XCP_MEMORY_SIZE(adev, xcp_id) - - adev->kfd.vram_used_aligned[xcp_id] - - atomic64_read(&adev->vram_pin_size) - + atomic64_read(&adev->kfd.vram_pinned) + - vram_used + - atomic64_read(&adev->vram_pin_size_xcp[xcp_id]) + - atomic64_read(&adev->vram_pin_size_common) + + atomic64_read(&adev->kfd.vram_pinned[xcp_id]) - reserved_for_pt - reserved_for_ras; @@ -1704,19 +1710,29 @@ size_t amdgpu_amdkfd_get_available_memory(struct amdgpu_device *adev, available = min3(system_mem_available, ttm_mem_available, vram_available); - available = ALIGN_DOWN(available, PAGE_SIZE); } else { - available = ALIGN_DOWN(vram_available, VRAM_AVAILABLITY_ALIGN); + available = vram_available; } - spin_unlock(&kfd_mem_limit.mem_limit_lock); - if (available < 0) available = 0; return available; } +size_t amdgpu_amdkfd_get_available_memory(struct amdgpu_device *adev, + uint8_t xcp_id) +{ + size_t available; + + spin_lock(&kfd_mem_limit.mem_limit_lock); + available = __get_available_memory(adev, xcp_id, + adev->kfd.vram_used_aligned[xcp_id]); + spin_unlock(&kfd_mem_limit.mem_limit_lock); + + return available; +} + int amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu( struct amdgpu_device *adev, uint64_t va, uint64_t size, void *drm_priv, struct kgd_mem **mem, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index 2a6a5bc01eb4..6aa895f63aea 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -923,6 +923,19 @@ void amdgpu_bo_unref(struct amdgpu_bo **bo) *bo = NULL; } +/* Map a BO onto vram_pin_size_xcp[]. bo->xcp_id < 0 means "any partition": + * such pins are not owned by a single partition and go to + * vram_pin_size_common, which every partition subtracts. + */ +static int amdgpu_bo_get_xcp(struct amdgpu_bo *bo) +{ + s8 xcp = bo->xcp_id; + + if (xcp >= 0 && xcp < MAX_XCP) + return xcp; + return -1; +} + /** * amdgpu_bo_pin - pin an &amdgpu_bo buffer object * @bo: &amdgpu_bo buffer object to be pinned @@ -1003,7 +1016,17 @@ int amdgpu_bo_pin(struct amdgpu_bo *bo, u32 domain) ttm_bo_pin(&bo->tbo); if (bo->tbo.resource->mem_type == TTM_PL_VRAM) { + int xcp = amdgpu_bo_get_xcp(bo); + atomic64_add(amdgpu_bo_size(bo), &adev->vram_pin_size); + if (xcp >= 0) { + atomic64_add(amdgpu_bo_size(bo), + &adev->vram_pin_size_xcp[xcp]); + } else { + atomic64_add(amdgpu_bo_size(bo), + &adev->vram_pin_size_common); + } + amdgpu_amdkfd_account_vram_pin(bo, xcp); atomic64_add(amdgpu_vram_mgr_bo_visible_size(bo), &adev->visible_pin_size); } else if (bo->tbo.resource->mem_type == TTM_PL_TT) { @@ -1036,7 +1059,17 @@ void amdgpu_bo_unpin(struct amdgpu_bo *bo) dma_buf_unpin(bo->tbo.base.import_attach); if (bo->tbo.resource->mem_type == TTM_PL_VRAM) { + int xcp = amdgpu_bo_get_xcp(bo); + atomic64_sub(amdgpu_bo_size(bo), &adev->vram_pin_size); + if (xcp >= 0) { + atomic64_sub(amdgpu_bo_size(bo), + &adev->vram_pin_size_xcp[xcp]); + } else { + atomic64_sub(amdgpu_bo_size(bo), + &adev->vram_pin_size_common); + } + amdgpu_amdkfd_account_vram_unpin(bo, xcp); atomic64_sub(amdgpu_vram_mgr_bo_visible_size(bo), &adev->visible_pin_size); } else if (bo->tbo.resource->mem_type == TTM_PL_TT) { diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 3831451240e9..cad60ccdb216 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1110,11 +1110,19 @@ static int kfd_ioctl_get_available_memory(struct file *filep, { struct kfd_ioctl_get_available_memory_args *args = data; struct kfd_process_device *pdd = kfd_lock_pdd_by_id(p, args->gpu_id); + struct amdgpu_device *adev; + size_t available; if (!pdd) return -EINVAL; - args->available = amdgpu_amdkfd_get_available_memory(pdd->dev->adev, - pdd->dev->node_id); + + adev = pdd->dev->adev; + available = amdgpu_amdkfd_get_available_memory(adev, pdd->dev->node_id); + if (adev->apu_prefer_gtt) + args->available = ALIGN_DOWN(available, PAGE_SIZE); + else + args->available = ALIGN_DOWN(available, VRAM_AVAILABLITY_ALIGN); + kfd_unlock_pdd(pdd); return 0; } From 90d91a192751cd9a7d9d2286299bc72ec1c28179 Mon Sep 17 00:00:00 2001 From: William Palacek Date: Mon, 19 Oct 2026 15:41:15 -0400 Subject: [PATCH 34/52] drm/amdkfd: Validate the control stack of a restored queue kfd_criu_restore_queue() takes the control stack size from the checkpoint image without the topology check create-queue does in kfd_queue_acquire_buffers(), which restore stopped calling in commit 1cafa8b29e02 ("drm/amdkfd: Don't acquire buffers during CRIU queue restore."). An image size of 0x300000 is accepted and sizes the MQD allocation, and 0xFFFFFFFF trips a page allocator warning in kmalloc(). restore_mqd() also keeps the image's cp_hqd_cntl_stack_size, cp_hqd_cntl_stack_offset and cp_hqd_wg_state_offset. With offset > size or wave state offset < size, get_wave_state() returns wrapped sizes. Take the per-XCC size from topology, 0 for queues without a control stack, and reject larger images. In restore_mqd(), set the size to it and bound both offsets as init_mqd() does. Fixes: 626f7b3190b4 ("drm/amdkfd: CRIU add queues support") Fixes: 42c6c48214b7 ("drm/amdkfd: CRIU checkpoint and restore queue mqds") Signed-off-by: William Palacek Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 92249023caee17bfc6f29c71d6aaa5f2298cf507) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h | 9 ++++++ .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 2 ++ .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 2 ++ .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 2 ++ .../drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 2 ++ .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 2 ++ .../gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 2 ++ .../amd/amdkfd/kfd_process_queue_manager.c | 30 +++++++++++++------ 8 files changed, 42 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h index 38b46b696243..d63f7e9fae30 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h @@ -138,6 +138,15 @@ struct mqd_manager { uint32_t ctl_stack_size; }; +/* Keep offset <= size <= wave state offset, as init_mqd() and CP do */ +static inline void kfd_mqd_restore_ctl_stack(u32 *size, u32 *offset, u32 *wg_offset, + u32 ctl_stack_size) +{ + *size = ctl_stack_size; + *offset = min(*offset, ctl_stack_size); + *wg_offset = max(*wg_offset, ctl_stack_size); +} + struct mqd_user_context_save_area_header { /* Byte offset from start of user context * save area to the last saved top (lowest 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 4f8a8a1a6186..6c9bafa3962e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c @@ -305,6 +305,8 @@ static void restore_mqd(struct mqd_manager *mm, void **mqd, addr = mqd_mem_obj->gpu_addr; memcpy(m, mqd_src, sizeof(*m)); + kfd_mqd_restore_ctl_stack(&m->cp_hqd_cntl_stack_size, &m->cp_hqd_cntl_stack_offset, + &m->cp_hqd_wg_state_offset, qp->ctl_stack_size); *mqd = m; if (gart_addr) 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 bf015dc5b868..5709949de2eb 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c @@ -340,6 +340,8 @@ static void restore_mqd(struct mqd_manager *mm, void **mqd, addr = mqd_mem_obj->gpu_addr; memcpy(m, mqd_src, sizeof(*m)); + kfd_mqd_restore_ctl_stack(&m->cp_hqd_cntl_stack_size, &m->cp_hqd_cntl_stack_offset, + &m->cp_hqd_wg_state_offset, qp->ctl_stack_size); *mqd = m; if (gart_addr) 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 6ea09b031caf..aea80266d254 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -393,6 +393,8 @@ static void restore_mqd(struct mqd_manager *mm, void **mqd, memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); memcpy(m, mqd_src, sizeof(*m)); + kfd_mqd_restore_ctl_stack(&m->cp_hqd_cntl_stack_size, &m->cp_hqd_cntl_stack_offset, + &m->cp_hqd_wg_state_offset, qp->ctl_stack_size); /* Update MQD base address to the newly allocated location */ m->cp_mqd_base_addr_lo = lower_32_bits(addr); 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 d508ac5a18f8..33f0ecbe6b0a 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 @@ -690,6 +690,8 @@ static void restore_mqd_v12_1(struct mqd_manager *mm, void **mqd, memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size) * NUM_XCC(mm->dev->xcc_mask)); memcpy(m, mqd_src, sizeof(*m)); + kfd_mqd_restore_ctl_stack(&m->cp_hqd_cntl_stack_size, &m->cp_hqd_cntl_stack_offset, + &m->cp_hqd_wg_state_offset, qp->ctl_stack_size); /* Update MQD base address to the newly allocated location */ m->cp_mqd_base_addr_lo = lower_32_bits(addr); 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 6e6bc1ec0b64..fca43017ee7e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -462,6 +462,8 @@ static void restore_mqd(struct mqd_manager *mm, void **mqd, addr = mqd_mem_obj->gpu_addr; memcpy(m, mqd_src, sizeof(*m)); + kfd_mqd_restore_ctl_stack(&m->cp_hqd_cntl_stack_size, &m->cp_hqd_cntl_stack_offset, + &m->cp_hqd_wg_state_offset, qp->ctl_stack_size); *mqd = m; if (gart_addr) 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 029572548c14..5ead0d5613b6 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c @@ -317,6 +317,8 @@ static void restore_mqd(struct mqd_manager *mm, void **mqd, addr = mqd_mem_obj->gpu_addr; memcpy(m, mqd_src, sizeof(*m)); + kfd_mqd_restore_ctl_stack(&m->cp_hqd_cntl_stack_size, &m->cp_hqd_cntl_stack_offset, + &m->cp_hqd_wg_state_offset, qp->ctl_stack_size); *mqd = m; if (gart_addr) 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 a15a826519ad..78ab759ad854 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c @@ -26,6 +26,7 @@ #include #include "kfd_device_queue_manager.h" #include "kfd_priv.h" +#include "kfd_topology.h" #include "kfd_kernel_queue.h" #include "amdgpu_amdkfd.h" #include "amdgpu_reset.h" @@ -983,7 +984,7 @@ int kfd_criu_checkpoint_queues(struct kfd_process *p, } static void set_queue_properties_from_criu(struct queue_properties *qp, - struct kfd_criu_queue_priv_data *q_data, uint32_t num_xcc) + struct kfd_criu_queue_priv_data *q_data) { qp->is_interop = false; qp->queue_percent = q_data->q_percent; @@ -996,11 +997,6 @@ static void set_queue_properties_from_criu(struct queue_properties *qp, qp->eop_ring_buffer_size = q_data->eop_ring_buffer_size; qp->ctx_save_restore_area_address = q_data->ctx_save_restore_area_address; qp->ctx_save_restore_area_size = q_data->ctx_save_restore_area_size; - if (q_data->type == KFD_QUEUE_TYPE_COMPUTE) - qp->ctl_stack_size = q_data->ctl_stack_size / num_xcc; - else - qp->ctl_stack_size = q_data->ctl_stack_size; - qp->type = q_data->type; qp->format = q_data->format; } @@ -1012,6 +1008,7 @@ int kfd_criu_restore_queue(struct kfd_process *p, { uint8_t *mqd, *ctl_stack, *q_extra_data = NULL; struct kfd_criu_queue_priv_data *q_data; + struct kfd_topology_device *topo_dev; struct kfd_process_device *pdd; uint64_t q_extra_data_size; struct queue_properties qp; @@ -1048,6 +1045,24 @@ int kfd_criu_restore_queue(struct kfd_process *p, goto exit; } + memset(&qp, 0, sizeof(qp)); + set_queue_properties_from_criu(&qp, q_data); + + /* Per-XCC control stack size from topology, as create-queue requires */ + if (qp.type == KFD_QUEUE_TYPE_COMPUTE && pdd->dev->kfd->cwsr_enabled) { + topo_dev = kfd_topology_device_by_id(pdd->dev->id); + if (!topo_dev) { + ret = -EINVAL; + goto exit; + } + qp.ctl_stack_size = topo_dev->node_props.ctl_stack_size; + } + + if (q_data->ctl_stack_size > (u64)qp.ctl_stack_size * NUM_XCC(pdd->dev->xcc_mask)) { + ret = -EINVAL; + goto exit; + } + *priv_data_offset += sizeof(*q_data); q_extra_data_size = (uint64_t)q_data->ctl_stack_size + q_data->mqd_size; @@ -1077,9 +1092,6 @@ int kfd_criu_restore_queue(struct kfd_process *p, mqd = q_extra_data; ctl_stack = mqd + q_data->mqd_size; - memset(&qp, 0, sizeof(qp)); - set_queue_properties_from_criu(&qp, q_data, NUM_XCC(pdd->dev->xcc_mask)); - print_queue_properties(&qp); ret = pqm_create_queue(&p->pqm, pdd->dev, &qp, &queue_id, q_data, mqd, ctl_stack, NULL); From 1233dd17f99c75787a31035656ecfeff80522421 Mon Sep 17 00:00:00 2001 From: Olivier Kaloudoff Date: Tue, 13 Oct 2026 20:53:11 +0200 Subject: [PATCH 35/52] drm/amdkfd: tear down KFD processes before early fini_sw on disconnect On surprise removal, amdgpu_device_fini_hw() calls amdgpu_amdkfd_device_fini_sw() when pci_dev_is_disconnected() is true. fini_sw frees kfd.dev, but KFD processes may still be alive and hold references to it: kfd_process_destroy() dereferences kfd.dev via kfd_processes_remove(), and svm_range_list_fini() / kfd_process_free_outstanding_kfd_bos() touch per-device SVM state. The kfd_processes_count wait only happens later in kgd2kfd_teardown_processes(), called from the top-half remove path well after fini_sw. In the observed failure the killed KFD process (SIGBUS on unplug) stuck in D state on its exit path, so kfd_processes_count never drained and the PCIe removal thread wedged permanently in cond_resched(), producing soft-lockup and hung-task splats. Tear KFD processes down before freeing kfd.dev on the disconnect path, and guard kgd2kfd_teardown_processes() against kfd.dev already being gone when it runs a second time. Cc: Xiaogang Chen Signed-off-by: Olivier Kaloudoff Signed-off-by: Felix Kuehling Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 836f44ba87ed7b1d83e2b7bfc788f8c6437b03a7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 5 ++++- drivers/gpu/drm/amd/amdkfd/kfd_device.c | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index f147c0c459da..c65bc575a57a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4331,8 +4331,11 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) * before ip_fini_early to prevent kfd locking refcount issues by calling * amdgpu_amdkfd_suspend() */ - if (pci_dev_is_disconnected(adev->pdev)) + if (pci_dev_is_disconnected(adev->pdev)) { + /* Drain KFD processes while kfd.dev is still valid */ + amdgpu_amdkfd_teardown_processes(adev); amdgpu_amdkfd_device_fini_sw(adev); + } amdgpu_coredump_fini(adev); amdgpu_device_ip_fini_early(adev); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device.c b/drivers/gpu/drm/amd/amdkfd/kfd_device.c index bb4c6695f3cf..0f96c9e3fa4d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device.c @@ -1814,7 +1814,14 @@ void kgd2kfd_teardown_processes(struct amdgpu_device *adev) mutex_unlock(&kfd_processes_mutex); - /* wait all kfd processes use adev terminate */ + /* kfd.dev may already be freed on surprise removal */ + if (!adev->kfd.dev) + return; + + /* + * Wait for all KFD processes using adev to terminate. Must be + * unbounded: device state is freed once they are gone. + */ while (!!atomic_read(&adev->kfd.dev->kfd_processes_count)) cond_resched(); } From 3254ed0b15f2fe656c9cb36ccb59b92422440bf8 Mon Sep 17 00:00:00 2001 From: Olivier Kaloudoff Date: Tue, 13 Oct 2026 20:53:12 +0200 Subject: [PATCH 36/52] drm/amdkfd: stop restore retries on device unplug amdgpu_amdkfd_restore_userptr_worker() and svm_range_restore_work() reschedule themselves when validation fails with evicted objects pending. After a surprise removal the restore paths fail with -ENODEV permanently, so rescheduling just spins the worker uselessly until cancel_delayed_work_sync() in fini finally stops it - which can take a while, since process teardown itself is delayed by the dead device. Treat -ENODEV as terminal in both workers. Cc: Xiaogang Chen Signed-off-by: Olivier Kaloudoff Signed-off-by: Felix Kuehling Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 266fab83139600e58db7e900e930c722e619b78c) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 18 +++++++++++++----- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 8 +++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index ff77e8ae9fe6..3a4b3424b5b2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -2854,6 +2854,7 @@ static void amdgpu_amdkfd_restore_userptr_worker(struct work_struct *work) struct task_struct *usertask; struct mm_struct *mm; uint32_t evicted_bos; + int ret; mutex_lock(&process_info->notifier_lock); evicted_bos = process_info->evicted_bos; @@ -2873,14 +2874,16 @@ static void amdgpu_amdkfd_restore_userptr_worker(struct work_struct *work) mutex_lock(&process_info->lock); - if (update_invalid_user_pages(process_info, mm)) + ret = update_invalid_user_pages(process_info, mm); + if (ret) goto unlock_out; /* userptr_inval_list can be empty if all evicted userptr BOs * have been freed. In that case there is nothing to validate * and we can just restart the queues. */ if (!list_empty(&process_info->userptr_inval_list)) { - if (validate_invalid_user_pages(process_info)) + ret = validate_invalid_user_pages(process_info); + if (ret) goto unlock_out; } /* Final check for concurrent evicton and atomic update. If @@ -2889,11 +2892,14 @@ static void amdgpu_amdkfd_restore_userptr_worker(struct work_struct *work) * reference counting inside KFD will handle this case. */ mutex_lock(&process_info->notifier_lock); - if (process_info->evicted_bos != evicted_bos) + if (process_info->evicted_bos != evicted_bos) { + ret = -EAGAIN; goto unlock_notifier_out; + } if (confirm_valid_user_pages_locked(process_info)) { WARN(1, "User pages unexpectedly invalid"); + ret = -EINVAL; goto unlock_notifier_out; } @@ -2911,8 +2917,10 @@ static void amdgpu_amdkfd_restore_userptr_worker(struct work_struct *work) unlock_out: mutex_unlock(&process_info->lock); - /* If validation failed, reschedule another attempt */ - if (evicted_bos) { + /* If validation failed, reschedule another attempt, unless the + * device is gone for good + */ + if (evicted_bos && ret != -ENODEV) { queue_delayed_work(system_freezable_wq, &process_info->restore_userptr_work, msecs_to_jiffies(AMDGPU_USERPTR_RESTORE_DELAY_MS)); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 1f5d73f0ebce..a4ce54b06975 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1963,7 +1963,7 @@ static void svm_range_restore_work(struct work_struct *work) struct mm_struct *mm; int evicted_ranges; int invalid; - int r; + int r = 0; svms = container_of(dwork, struct svm_range_list, restore_work); evicted_ranges = atomic_read(&svms->evicted_ranges); @@ -2037,8 +2037,10 @@ static void svm_range_restore_work(struct work_struct *work) mmap_write_unlock(mm); mutex_unlock(&process_info->lock); - /* If validation failed, reschedule another attempt */ - if (evicted_ranges) { + /* If validation failed, reschedule another attempt, unless the + * device is gone for good + */ + if (evicted_ranges && r != -ENODEV) { pr_debug("reschedule to restore svm range\n"); queue_delayed_work(system_freezable_wq, &svms->restore_work, msecs_to_jiffies(AMDGPU_SVM_RANGE_RESTORE_DELAY_MS)); From c3fed6272bb4771a849c33c781af99afa90e90ad Mon Sep 17 00:00:00 2001 From: Olivier Kaloudoff Date: Thu, 15 Oct 2026 21:30:37 +0200 Subject: [PATCH 37/52] drm/amdgpu: guard VCE/UVD sched entity teardown on failed probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 037b98a2312e ("drm/amdgpu: move UVD and VCE sched entity init after sched init"), drm_sched_entity_init() for the VCE and UVD kernel entities runs in the scheduler-init phase, well after the IP-block sw_init phase has allocated the vcpu_bos. If probe fails between the two phases, amdgpu_device_fini_sw() still runs the per-IP sw_fini callbacks for blocks that did complete sw_init. Both amdgpu_vce_sw_fini() and amdgpu_uvd_sw_fini() then call drm_sched_entity_destroy() on an entity that was never initialized: entity->rq is NULL and drm_sched_entity_flush() dereferences it, oopsing in whatever context drove the failed probe. The hole was latent-but-safe until commit 2df5efb45425 ("drm/sched: Remove redundant entity->rq initialization and checks") removed the if (!entity->rq) early return that commit 1decbf6bb0b4 ("drm/sched: Fix entities with 0 rqs.") had put in drm_sched_entity_flush(), on the grounds that entity->rq can never be NULL. That holds only for entities that completed drm_sched_entity_init() — not for the never-initialized entity this teardown path reaches. Observed on real hardware: MI50 (Vega20, VCE 4.0 / UVD 7.2) behind a Thunderbolt link — a pciehp presence/link-change event re-probes the device; when the re-probe fails (-EINVAL here), the cleanup path NULL-derefs inside drm_sched_entity_flush() in the pciehp IRQ thread and takes the whole machine down (panic_on_oops, EFI pstore capture). Reproduced twice on 7.3-rc5, 2026-10-06 and 2026-10-09, identical signature; the faulting instruction is `mov (%rax),%r15` on rax = entity->rq == NULL, i.e. `sched = entity->rq->sched`: BUG: kernel NULL pointer dereference RIP: drm_sched_entity_flush+0x34/0x2d0 [gpu_sched] drm_sched_entity_destroy amdgpu_vce_sw_fini vce_v4_0_sw_fini amdgpu_device_fini_sw ... pciehp_configure_device pciehp_handle_presence_or_link_change Guard the entity teardown on entity->rq, which is set only by drm_sched_entity_init(). amdgpu_ring_fini() already self-guards via the rings[] registration, and the vcpu_bo/firmware cleanup still runs as before. v2: retarget Fixes: at commit 2df5efb45425 ("drm/sched: Remove redundant entity->rq initialization and checks") — the !entity->rq check from commit 1decbf6bb0b4 ("drm/sched: Fix entities with 0 rqs.") that Alex pointed to was removed by it in the 7.3 window, so this is a regression there, not a pre-existing hole. Fixes: 2df5efb45425 ("drm/sched: Remove redundant entity->rq initialization and checks") Cc: Tvrtko Ursulin Cc: Philipp Stanner Cc: Alex Deucher Cc: Christian König Signed-off-by: Olivier Kaloudoff Reviewed-by: Tvrtko Ursulin Signed-off-by: Alex Deucher (cherry picked from commit 3ad55d18f468fa81be725301d1e138ecd5148e6a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 3 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index ecd7caa95d4b..cd35ff7ca864 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -377,7 +377,8 @@ int amdgpu_uvd_sw_fini(struct amdgpu_device *adev) void *addr = amdgpu_bo_kptr(adev->uvd.ib_bo); int i, j; - drm_sched_entity_destroy(&adev->uvd.entity); + if (adev->uvd.entity.rq) + drm_sched_entity_destroy(&adev->uvd.entity); for (j = 0; j < adev->uvd.num_uvd_inst; ++j) { if (adev->uvd.harvest_config & (1 << j)) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c index 1f7a98cdce5a..3952af9ae115 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c @@ -253,7 +253,8 @@ int amdgpu_vce_sw_fini(struct amdgpu_device *adev) if (adev->vce.vcpu_bo == NULL) return 0; - drm_sched_entity_destroy(&adev->vce.entity); + if (adev->vce.entity.rq) + drm_sched_entity_destroy(&adev->vce.entity); for (i = 0; i < adev->vce.num_rings; i++) amdgpu_ring_fini(&adev->vce.ring[i]); From b456f591f925fbd6b33770d0eec1b51c2f9d41c1 Mon Sep 17 00:00:00 2001 From: Luigi Lauro Date: Fri, 16 Oct 2026 05:16:36 +0200 Subject: [PATCH 38/52] drm/amd/display: take a runtime PM reference only when a CRTC turns on amdgpu_dm_commit_streams() takes a runtime PM reference for every active CRTC that needs a modeset and has a new stream, but amdgpu_dm_atomic_commit_tail() drops one only for each CRTC that goes from active to inactive. A modeset of a CRTC that is already active (a mode change, or anything else that needs a full modeset) therefore leaks one reference. Once that has happened, the usage count no longer returns to zero with every CRTC off, and the device cannot runtime suspend again until reboot. The two also disagree the other way when a CRTC turns on without a stream ("Failed to create new stream"): no reference is taken, but one is dropped when it turns off. Take the reference in the loop that counts the disabled CRTCs, on the mirror condition: when the CRTC goes from inactive to active, which is what the commit that added it intended ("every time we enable a previously disabled CRTC"). Each active CRTC then holds exactly one reference. Tested on an RX 9070 XT (Navi 48) over OCuLink, with the change backported to 7.2.9: an unplugged connector forced on, then mode A on a CRTC, mode B on the same active CRTC, and the CRTC off, three rounds in a row. The usage count rose by 3 with the stock module, by 0 with this patch, and by 3 again after reloading the stock module. Fixes: 97028037a38a ("drm/amdgpu: Grab/put runtime PM references in atomic_commit_tail()") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5951 Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Alex Deucher Signed-off-by: Luigi Lauro Signed-off-by: Alex Deucher (cherry picked from commit 59ef9a435499777b8e1c4e0eff52f813c64fb920) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 11 +++++++---- 1 file changed, 7 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 5b634ae07c0d..caa5edcf1bc3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4709,8 +4709,6 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state, if (dm_old_crtc_state->stream) remove_stream(adev, acrtc, dm_old_crtc_state->stream); - pm_runtime_get_noresume(dev->dev); - acrtc->enabled = true; acrtc->hw_mode = new_crtc_state->mode; crtc->hwmode = new_crtc_state->mode; @@ -5294,8 +5292,13 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) #ifdef CONFIG_DEBUG_FS enum amdgpu_dm_pipe_crc_source cur_crc_src; #endif - /* Count number of newly disabled CRTCs for dropping PM refs later. */ - if (old_crtc_state->active && !new_crtc_state->active) + /* + * One runtime PM reference per active CRTC: take it when the + * CRTC turns on, count it when it turns off for dropping later. + */ + if (!old_crtc_state->active && new_crtc_state->active) + pm_runtime_get_noresume(dev->dev); + else if (old_crtc_state->active && !new_crtc_state->active) crtc_disable_count++; dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); From ba4c3c202919405f461a393e823e4b72c8518494 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Sun, 18 Oct 2026 19:17:57 +0800 Subject: [PATCH 39/52] drm/amdgpu: reject CS chunks too large for kvmalloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CS ioctl copies every chunk with vmemdup_array_user() before it looks at it, for whatever length_dw userspace gives. kvmalloc() warns about allocations above INT_MAX when called without __GFP_NOWARN, so any process with the render node can trigger a warning. On navi31, a CS with a chunk of length_dw 0x20000000 returns -ENOMEM after: WARNING: mm/slub.c:6841 at __kvmalloc_node_noprof+0x5a5/0x7d0 Reject such chunks with -EINVAL before trying to copy them. Fixes: 2098105ec65c ("drm: drop drm_[cm]alloc* helpers") Signed-off-by: Jesse Zhang Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit dab5f5280406088591645f499f048dcfefab8d7d) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 65e13ad445bb..70bc53923d11 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -218,6 +218,12 @@ static int amdgpu_cs_pass1(struct amdgpu_cs_parser *p, size = p->chunks[i].length_dw; + if (size > INT_MAX / sizeof(uint32_t)) { + ret = -EINVAL; + i--; + goto free_partial_kdata; + } + p->chunks[i].kdata = vmemdup_array_user(u64_to_user_ptr(user_chunk.chunk_data), size, sizeof(uint32_t)); From 52957e813f2f025c86e4aa6e86cb4963ef94fd00 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 13 Oct 2026 13:17:53 +0800 Subject: [PATCH 40/52] drm/amdkfd: split vram_pages of SVM ranges prefetched without a GPU mapping When an SVM range is split, svm_range_split_pages() gives the new range the number of VRAM pages counted in its part of the dma_addr arrays and subtracts it from the old range. Pages migrated to VRAM by a prefetch have no dma_addr entries until the range is mapped on a GPU, e.g. with no ACCESS attribute, or with XNACK off before the delayed restore worker mapped it, so the new range gets 0 and the old one keeps the count for both. Migrating the parts back to system memory then gets the accounting wrong: the old part keeps a VRAM page count after all its pages are gone, so its svm_bo is never freed, and the new part underflows. On navi31, as an unprivileged process: prefetch 4 MiB to the GPU, prefetch the upper 2 MiB and then the lower 2 MiB back to system memory: Recorded vram pages(0x0) should not be less than migration pages(0x200). WARNING: ../amdkfd/kfd_migrate.c:922 at svm_migrate_vram_to_ram+0xbdc/0xcd0 [amdgpu] and /sys/class/kfd/kfd/proc//vram_ stays at 4194304 until the process exits. Without dma_addr arrays, the split is still exact when the old range is completely in VRAM, which is what a prefetch of the whole range leaves. Give the new range all its pages in that case. The warning is gone and the VRAM usage drops back to 0. This doesn't cover a range that is only partly in VRAM without dma_addr arrays, e.g. after a partial migration, since svm_migrate_ram_to_vram() only counts the pages it actually migrated. That would need VRAM residency to be tracked per page independently of dma_addr. v2: - Rename counted to has_dma_addr and compare against old->npages (Philip) - Mention the XNACK off case and the partial migration limitation (Philip) Fixes: 006ad514a50c ("drm/amdkfd: Use partial hmm page walk during buffer validation in SVM") Reviewed-by: Philip Yang Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 7ca6f3359b557a267c7b07a2fd748b1473395e10) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index a4ce54b06975..ed5e3318877b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1020,9 +1020,12 @@ svm_range_split_pages(struct svm_range *new, struct svm_range *old, uint64_t start, uint64_t last) { uint64_t npages = last - start + 1; + bool has_dma_addr = false; int i, r; for (i = 0; i < MAX_GPU_INSTANCE; i++) { + if (old->dma_addr[i]) + has_dma_addr = true; r = svm_range_split_array(&new->dma_addr[i], &old->dma_addr[i], sizeof(*old->dma_addr[i]), old->start, npages, new->start, new->npages, @@ -1030,8 +1033,16 @@ svm_range_split_pages(struct svm_range *new, struct svm_range *old, if (r) return r; } - if (old->actual_loc) + if (old->actual_loc) { + /* + * Pages migrated by prefetch have no dma_addr until they are + * mapped on a GPU. A fully resident range still splits exactly, + * old->npages isn't adjusted yet here. + */ + if (!has_dma_addr && old->vram_pages == old->npages) + new->vram_pages = new->npages; old->vram_pages -= new->vram_pages; + } return 0; } From 167df3b94680b8670c4cacc08af232e3bfcab5ff Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Sun, 11 Oct 2026 10:45:10 +0300 Subject: [PATCH 41/52] drm/amd/display: Initialize missing TD-LUT programming inputs dml_core_mode_programming() memsets (clears) its scratch structure before calculating TD-LUT settings, but does not initialize tdlut_mpc_width_flag or is_gfx11. Both inputs therefore remain false regardless of the plane configuration which can result in incorrect calculations. Initialize both fields using the same sources as the mode support path (now both paths are aligned). Fixes: 70839da63605 ("drm/amd/display: Add new DCN401 sources") Signed-off-by: Avi Weiss Signed-off-by: Ray Wu Reviewed-by: Harry Wentland Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit ad8d216e7d9fc050cdf39278e5fae17f61c36931) Cc: stable@vger.kernel.org --- .../dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 2 ++ 1 file changed, 2 insertions(+) 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 2d43a35c4c42..7a288b352079 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 @@ -11052,6 +11052,8 @@ static bool dml_core_mode_programming(struct dml2_core_calcs_mode_programming_ex calculate_tdlut_setting_params->cursor_buffer_size = mode_lib->ip.cursor_buffer_size; calculate_tdlut_setting_params->gpuvm_enable = display_cfg->gpuvm_enable; calculate_tdlut_setting_params->gpuvm_page_size_kbytes = display_cfg->plane_descriptors[k].overrides.gpuvm_min_page_size_kbytes; + calculate_tdlut_setting_params->tdlut_mpc_width_flag = display_cfg->plane_descriptors[k].tdlut.tdlut_mpc_width_flag; + calculate_tdlut_setting_params->is_gfx11 = dml_get_gfx_version(display_cfg->plane_descriptors[k].surface.tiling); // output calculate_tdlut_setting_params->tdlut_pte_bytes_per_frame = &s->tdlut_pte_bytes_per_frame[k]; From 6027db7db773714f2e05df82093276f2a52a1fba Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 15 Oct 2026 14:02:09 +0800 Subject: [PATCH 42/52] drm/amd/pm: preserve committed OD features for SMU 13.0.0/13.0.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PMFW treats FeatureCtrlMask as the complete set of active OD features. Uploads containing only newly edited bits can drop earlier settings. Fix the OD state handling to: - preserve committed features across later commits - keep staged OD settings out of power-cap writes - update cached state only after successful uploads - clear restored features only after they return to defaults - restore PPT for the current AC/DC source after resume Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/3625 Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Tested-by: José Miguel Sarasola Signed-off-by: Alex Deucher (cherry picked from commit adf63026e1fa706de48d986ac6c1a7cb64cbf927) Cc: stable@vger.kernel.org --- .../drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 193 +++++++++++++----- .../drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 193 +++++++++++++----- 2 files changed, 293 insertions(+), 93 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 468aaeb79ffb..e80087e35e19 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 @@ -63,6 +63,9 @@ static void smu_v13_0_0_get_od_setting_limits(struct smu_context *smu, int od_feature_bit, int32_t *min, int32_t *max); static int smu_v13_0_0_init_ppt_limits(struct smu_context *smu); +static int +smu_v13_0_0_upload_overdrive_table(struct smu_context *smu, + OverDriveTableExternal_t *od_table); static const struct smu_feature_bits smu_v13_0_0_dpm_features = { .bits = { @@ -1079,6 +1082,93 @@ static bool smu_v13_0_0_is_od_feature_supported(struct smu_context *smu, return feature_enabled; } +static u32 +smu_v13_0_0_filter_od_feature_mask(struct smu_context *smu, u32 feature_mask) +{ + PPTable_t *pptable = smu->smu_table.driver_pptable; + u32 supported_mask = + pptable->SkuTable.OverDriveLimitsBasicMax.FeatureCtrlMask; + + return feature_mask & supported_mask; +} + +static bool smu_v13_0_0_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return !memcmp(od->FanLinearPwmPoints, boot->FanLinearPwmPoints, + sizeof(od->FanLinearPwmPoints)) && + !memcmp(od->FanLinearTempPoints, boot->FanLinearTempPoints, + sizeof(od->FanLinearTempPoints)) && + od->FanMinimumPwm == boot->FanMinimumPwm && + od->AcousticTargetRpmThreshold == boot->AcousticTargetRpmThreshold && + od->AcousticLimitRpmThreshold == boot->AcousticLimitRpmThreshold && + od->FanTargetTemperature == boot->FanTargetTemperature && + od->FanMode == boot->FanMode; +} + +static bool smu_v13_0_0_zero_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return od->FanZeroRpmEnable == boot->FanZeroRpmEnable && + od->FanZeroRpmStopTemp == boot->FanZeroRpmStopTemp; +} + +static u32 smu_v13_0_0_normalize_feature_mask(const OverDriveTable_t *od, + const OverDriveTable_t *boot, + u32 feature_mask) +{ + if (od->GfxclkFmin == boot->GfxclkFmin && + od->GfxclkFmax == boot->GfxclkFmax) + feature_mask &= ~BIT(PP_OD_FEATURE_GFXCLK_BIT); + if (od->UclkFmin == boot->UclkFmin && + od->UclkFmax == boot->UclkFmax) + feature_mask &= ~BIT(PP_OD_FEATURE_UCLK_BIT); + if (!memcmp(od->VoltageOffsetPerZoneBoundary, + boot->VoltageOffsetPerZoneBoundary, + sizeof(od->VoltageOffsetPerZoneBoundary))) + feature_mask &= ~BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT); + if (smu_v13_0_0_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_FAN_CURVE_BIT); + if (smu_v13_0_0_zero_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + if (!od->Ppt) + feature_mask &= ~BIT(PP_OD_FEATURE_PPT_BIT); + + return feature_mask; +} + +static int +smu_v13_0_0_upload_ppt_overdrive_table(struct smu_context *smu, s16 ppt) +{ + struct smu_table_context *table_context = &smu->smu_table; + OverDriveTableExternal_t *od_table = table_context->overdrive_table; + OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; + OverDriveTableExternal_t upload_table; + u32 next_mask = table_context->user_od_feature_mask; + int ret; + + /* Do not commit OD settings staged after the last c operation. */ + memcpy(&upload_table, user_od_table, sizeof(upload_table)); + upload_table.OverDriveTable.Ppt = ppt; + if (ppt) + next_mask |= BIT(PP_OD_FEATURE_PPT_BIT); + else + next_mask &= ~BIT(PP_OD_FEATURE_PPT_BIT); + upload_table.OverDriveTable.FeatureCtrlMask = + smu_v13_0_0_filter_od_feature_mask(smu, next_mask); + + ret = smu_v13_0_0_upload_overdrive_table(smu, &upload_table); + if (ret) + return ret; + + table_context->user_od_feature_mask = + upload_table.OverDriveTable.FeatureCtrlMask; + od_table->OverDriveTable.Ppt = ppt; + user_od_table->OverDriveTable.Ppt = ppt; + + return 0; +} + static void smu_v13_0_0_get_od_setting_limits(struct smu_context *smu, int od_feature_bit, int32_t *min, @@ -1514,41 +1604,30 @@ static int smu_v13_0_0_od_restore_table_single(struct smu_context *smu, long inp boot_overdrive_table->OverDriveTable.FanLinearPwmPoints[i]; } od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_ACOUSTIC_LIMIT: od_table->OverDriveTable.AcousticLimitRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticLimitRpmThreshold; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_ACOUSTIC_TARGET: od_table->OverDriveTable.AcousticTargetRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticTargetRpmThreshold; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_TARGET_TEMPERATURE: od_table->OverDriveTable.FanTargetTemperature = boot_overdrive_table->OverDriveTable.FanTargetTemperature; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_MINIMUM_PWM: od_table->OverDriveTable.FanMinimumPwm = boot_overdrive_table->OverDriveTable.FanMinimumPwm; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_ZERO_RPM_ENABLE: od_table->OverDriveTable.FanZeroRpmEnable = boot_overdrive_table->OverDriveTable.FanZeroRpmEnable; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; case PP_OD_EDIT_FAN_ZERO_RPM_STOP_TEMP: od_table->OverDriveTable.FanZeroRpmStopTemp = boot_overdrive_table->OverDriveTable.FanZeroRpmStopTemp; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; default: dev_info(adev->dev, "Invalid table index: %ld\n", input); @@ -1566,9 +1645,17 @@ static int smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu, struct smu_table_context *table_context = &smu->smu_table; OverDriveTableExternal_t *od_table = (OverDriveTableExternal_t *)table_context->overdrive_table; + OverDriveTableExternal_t *user_od_table = + (OverDriveTableExternal_t *)table_context->user_overdrive_table; + OverDriveTableExternal_t *boot_od_table = + (OverDriveTableExternal_t *)table_context->boot_overdrive_table; + OverDriveTableExternal_t upload_table; + OverDriveTable_t *od = &od_table->OverDriveTable; + OverDriveTable_t *boot = &boot_od_table->OverDriveTable; struct amdgpu_device *adev = smu->adev; uint32_t offset_of_voltageoffset; int32_t minimum, maximum; + u32 next_mask; int i, ret = 0; switch (type) { @@ -1892,36 +1979,41 @@ static int smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu, } else { memcpy(od_table, table_context->boot_overdrive_table, - sizeof(OverDriveTableExternal_t)); + sizeof(OverDriveTableExternal_t)); od_table->OverDriveTable.FeatureCtrlMask = 0; - table_context->user_od_feature_mask = 0; } fallthrough; case PP_OD_COMMIT_DPM_TABLE: - table_context->user_od_feature_mask = - od_table->OverDriveTable.FeatureCtrlMask; offset_of_voltageoffset = offsetof(OverDriveTable_t, VoltageOffsetPerZoneBoundary); - if (memcmp((u8 *)od_table + offset_of_voltageoffset, - table_context->user_overdrive_table + offset_of_voltageoffset, - sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset)) { - smu_v13_0_0_dump_od_table(smu, od_table); + next_mask = smu_v13_0_0_filter_od_feature_mask(smu, + table_context->user_od_feature_mask | + od->FeatureCtrlMask); + next_mask = smu_v13_0_0_normalize_feature_mask(od, boot, + next_mask); - ret = smu_v13_0_0_upload_overdrive_table(smu, od_table); + if (memcmp((u8 *)od_table + offset_of_voltageoffset, + (u8 *)user_od_table + offset_of_voltageoffset, + sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset) || + next_mask != table_context->user_od_feature_mask) { + memcpy(&upload_table, od_table, sizeof(upload_table)); + upload_table.OverDriveTable.FeatureCtrlMask = next_mask; + smu_v13_0_0_dump_od_table(smu, &upload_table); + + ret = smu_v13_0_0_upload_overdrive_table(smu, &upload_table); if (ret) { dev_err(adev->dev, "Failed to upload overdrive table!\n"); return ret; } - memcpy(table_context->user_overdrive_table + offset_of_voltageoffset, + table_context->user_od_feature_mask = next_mask; + od_table->OverDriveTable.FeatureCtrlMask = 0; + memcpy((u8 *)user_od_table + offset_of_voltageoffset, (u8 *)od_table + offset_of_voltageoffset, sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset); + user_od_table->OverDriveTable.FeatureCtrlMask = 0; - if (!memcmp(table_context->user_overdrive_table, - table_context->boot_overdrive_table, - sizeof(OverDriveTableExternal_t))) - smu->user_dpm_profile.user_od = false; - else - smu->user_dpm_profile.user_od = true; + smu->user_dpm_profile.user_od = + !!(next_mask & ~BIT(PP_OD_FEATURE_PPT_BIT)); } break; @@ -2184,6 +2276,7 @@ static int smu_v13_0_0_set_default_od_settings(struct smu_context *smu) memcpy(od_table, boot_od_table, sizeof(OverDriveTableExternal_t)); + od_table->OverDriveTable.FeatureCtrlMask = 0; /* * For S3/S4/Runpm resume, we need to setup those overdrive tables again, @@ -2193,6 +2286,7 @@ static int smu_v13_0_0_set_default_od_settings(struct smu_context *smu) memcpy(user_od_table, boot_od_table, sizeof(OverDriveTableExternal_t)); + user_od_table->OverDriveTable.FeatureCtrlMask = 0; smu->user_dpm_profile.user_od = false; smu->smu_table.user_od_feature_mask = 0; } else if (smu->user_dpm_profile.user_od) { @@ -2231,6 +2325,14 @@ static int smu_v13_0_0_set_default_od_settings(struct smu_context *smu) user_od_table_bak.OverDriveTable.FanZeroRpmEnable; user_od_table->OverDriveTable.FanZeroRpmStopTemp = user_od_table_bak.OverDriveTable.FanZeroRpmStopTemp; + user_od_table->OverDriveTable.FanMode = + user_od_table_bak.OverDriveTable.FanMode; + user_od_table->OverDriveTable.FeatureCtrlMask = 0; + } + if (smu->adev->in_suspend) { + user_od_table->OverDriveTable.Ppt = 0; + smu->smu_table.user_od_feature_mask &= + ~BIT(PP_OD_FEATURE_PPT_BIT); } smu_v13_0_0_set_supported_od_feature_mask(smu); @@ -2243,13 +2345,22 @@ static int smu_v13_0_0_restore_user_od_settings(struct smu_context *smu) struct smu_table_context *table_context = &smu->smu_table; OverDriveTableExternal_t *od_table = table_context->overdrive_table; OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; + OverDriveTableExternal_t upload_table; + u32 next_mask; int res; - user_od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - res = smu_v13_0_0_upload_overdrive_table(smu, user_od_table); - user_od_table->OverDriveTable.FeatureCtrlMask = 0; - if (res == 0) + + next_mask = smu_v13_0_0_filter_od_feature_mask(smu, + table_context->user_od_feature_mask); + memcpy(&upload_table, user_od_table, sizeof(upload_table)); + upload_table.OverDriveTable.FeatureCtrlMask = next_mask; + res = smu_v13_0_0_upload_overdrive_table(smu, &upload_table); + if (res == 0) { + table_context->user_od_feature_mask = next_mask; + smu->user_dpm_profile.user_od = + !!(next_mask & ~BIT(PP_OD_FEATURE_PPT_BIT)); memcpy(od_table, user_od_table, sizeof(OverDriveTableExternal_t)); + od_table->OverDriveTable.FeatureCtrlMask = 0; + } return res; } @@ -3043,10 +3154,8 @@ static int smu_v13_0_0_set_ppt_limit(struct smu_context *smu, SkuTable_t *skutable = &pptable->SkuTable; uint32_t msg_limit = skutable->MsgLimits.Power [PPT_THROTTLER_PPT0][POWER_SOURCE_AC]; - struct smu_table_context *table_context = &smu->smu_table; - OverDriveTableExternal_t *od_table = - (OverDriveTableExternal_t *)table_context->overdrive_table; OverDriveTableExternal_t current_od_table; + s16 ppt; int ret = 0; if (limit_type != SMU_PPT_LIMIT_PPT0) @@ -3058,12 +3167,7 @@ static int smu_v13_0_0_set_ppt_limit(struct smu_context *smu, return ret; if (current_od_table.OverDriveTable.Ppt) { - od_table->OverDriveTable.Ppt = 0; - table_context->user_od_feature_mask &= - ~(1U << PP_OD_FEATURE_PPT_BIT); - od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - ret = smu_v13_0_0_upload_overdrive_table(smu, od_table); + ret = smu_v13_0_0_upload_ppt_overdrive_table(smu, 0); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); return ret; @@ -3075,11 +3179,8 @@ static int smu_v13_0_0_set_ppt_limit(struct smu_context *smu, if (ret) return ret; - od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit - 100; - table_context->user_od_feature_mask |= 1U << PP_OD_FEATURE_PPT_BIT; - od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - ret = smu_v13_0_0_upload_overdrive_table(smu, od_table); + ppt = (limit * 100) / msg_limit - 100; + ret = smu_v13_0_0_upload_ppt_overdrive_table(smu, ppt); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); return ret; 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 51e419e6380c..8285d4a91589 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 @@ -63,6 +63,9 @@ static void smu_v13_0_7_get_od_setting_limits(struct smu_context *smu, int od_feature_bit, int32_t *min, int32_t *max); static int smu_v13_0_7_init_ppt_limits(struct smu_context *smu); +static int +smu_v13_0_7_upload_overdrive_table(struct smu_context *smu, + OverDriveTableExternal_t *od_table); static const struct smu_feature_bits smu_v13_0_7_dpm_features = { .bits = { @@ -94,6 +97,7 @@ static const struct smu_feature_bits smu_v13_0_7_dpm_features = { #define PP_OD_FEATURE_GFXCLK_FMAX 1 #define PP_OD_FEATURE_UCLK_FMIN 2 #define PP_OD_FEATURE_UCLK_FMAX 3 + #define PP_OD_FEATURE_GFX_VF_CURVE 4 #define PP_OD_FEATURE_FAN_CURVE_TEMP 5 #define PP_OD_FEATURE_FAN_CURVE_PWM 6 @@ -1089,6 +1093,93 @@ static bool smu_v13_0_7_is_od_feature_supported(struct smu_context *smu, return feature_enabled; } +static u32 +smu_v13_0_7_filter_od_feature_mask(struct smu_context *smu, u32 feature_mask) +{ + PPTable_t *pptable = smu->smu_table.driver_pptable; + u32 supported_mask = + pptable->SkuTable.OverDriveLimitsBasicMax.FeatureCtrlMask; + + return feature_mask & supported_mask; +} + +static bool smu_v13_0_7_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return !memcmp(od->FanLinearPwmPoints, boot->FanLinearPwmPoints, + sizeof(od->FanLinearPwmPoints)) && + !memcmp(od->FanLinearTempPoints, boot->FanLinearTempPoints, + sizeof(od->FanLinearTempPoints)) && + od->FanMinimumPwm == boot->FanMinimumPwm && + od->AcousticTargetRpmThreshold == boot->AcousticTargetRpmThreshold && + od->AcousticLimitRpmThreshold == boot->AcousticLimitRpmThreshold && + od->FanTargetTemperature == boot->FanTargetTemperature && + od->FanMode == boot->FanMode; +} + +static bool smu_v13_0_7_zero_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return od->FanZeroRpmEnable == boot->FanZeroRpmEnable && + od->FanZeroRpmStopTemp == boot->FanZeroRpmStopTemp; +} + +static u32 smu_v13_0_7_normalize_feature_mask(const OverDriveTable_t *od, + const OverDriveTable_t *boot, + u32 feature_mask) +{ + if (od->GfxclkFmin == boot->GfxclkFmin && + od->GfxclkFmax == boot->GfxclkFmax) + feature_mask &= ~BIT(PP_OD_FEATURE_GFXCLK_BIT); + if (od->UclkFmin == boot->UclkFmin && + od->UclkFmax == boot->UclkFmax) + feature_mask &= ~BIT(PP_OD_FEATURE_UCLK_BIT); + if (!memcmp(od->VoltageOffsetPerZoneBoundary, + boot->VoltageOffsetPerZoneBoundary, + sizeof(od->VoltageOffsetPerZoneBoundary))) + feature_mask &= ~BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT); + if (smu_v13_0_7_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_FAN_CURVE_BIT); + if (smu_v13_0_7_zero_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + if (!od->Ppt) + feature_mask &= ~BIT(PP_OD_FEATURE_PPT_BIT); + + return feature_mask; +} + +static int +smu_v13_0_7_upload_ppt_overdrive_table(struct smu_context *smu, s16 ppt) +{ + struct smu_table_context *table_context = &smu->smu_table; + OverDriveTableExternal_t *od_table = table_context->overdrive_table; + OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; + OverDriveTableExternal_t upload_table; + u32 next_mask = table_context->user_od_feature_mask; + int ret; + + /* Do not commit OD settings staged after the last c operation. */ + memcpy(&upload_table, user_od_table, sizeof(upload_table)); + upload_table.OverDriveTable.Ppt = ppt; + if (ppt) + next_mask |= BIT(PP_OD_FEATURE_PPT_BIT); + else + next_mask &= ~BIT(PP_OD_FEATURE_PPT_BIT); + upload_table.OverDriveTable.FeatureCtrlMask = + smu_v13_0_7_filter_od_feature_mask(smu, next_mask); + + ret = smu_v13_0_7_upload_overdrive_table(smu, &upload_table); + if (ret) + return ret; + + table_context->user_od_feature_mask = + upload_table.OverDriveTable.FeatureCtrlMask; + od_table->OverDriveTable.Ppt = ppt; + user_od_table->OverDriveTable.Ppt = ppt; + + return 0; +} + static void smu_v13_0_7_get_od_setting_limits(struct smu_context *smu, int od_feature_bit, int32_t *min, @@ -1523,41 +1614,30 @@ static int smu_v13_0_7_od_restore_table_single(struct smu_context *smu, long inp boot_overdrive_table->OverDriveTable.FanLinearPwmPoints[i]; } od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_ACOUSTIC_LIMIT: od_table->OverDriveTable.AcousticLimitRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticLimitRpmThreshold; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_ACOUSTIC_TARGET: od_table->OverDriveTable.AcousticTargetRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticTargetRpmThreshold; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_TARGET_TEMPERATURE: od_table->OverDriveTable.FanTargetTemperature = boot_overdrive_table->OverDriveTable.FanTargetTemperature; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_MINIMUM_PWM: od_table->OverDriveTable.FanMinimumPwm = boot_overdrive_table->OverDriveTable.FanMinimumPwm; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_ZERO_RPM_ENABLE: od_table->OverDriveTable.FanZeroRpmEnable = boot_overdrive_table->OverDriveTable.FanZeroRpmEnable; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; case PP_OD_EDIT_FAN_ZERO_RPM_STOP_TEMP: od_table->OverDriveTable.FanZeroRpmStopTemp = boot_overdrive_table->OverDriveTable.FanZeroRpmStopTemp; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; default: dev_info(adev->dev, "Invalid table index: %ld\n", input); @@ -1575,9 +1655,17 @@ static int smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu, struct smu_table_context *table_context = &smu->smu_table; OverDriveTableExternal_t *od_table = (OverDriveTableExternal_t *)table_context->overdrive_table; + OverDriveTableExternal_t *user_od_table = + (OverDriveTableExternal_t *)table_context->user_overdrive_table; + OverDriveTableExternal_t *boot_od_table = + (OverDriveTableExternal_t *)table_context->boot_overdrive_table; + OverDriveTableExternal_t upload_table; + OverDriveTable_t *od = &od_table->OverDriveTable; + OverDriveTable_t *boot = &boot_od_table->OverDriveTable; struct amdgpu_device *adev = smu->adev; uint32_t offset_of_voltageoffset; int32_t minimum, maximum; + u32 next_mask; int i, ret = 0; switch (type) { @@ -1903,35 +1991,40 @@ static int smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu, table_context->boot_overdrive_table, sizeof(OverDriveTableExternal_t)); od_table->OverDriveTable.FeatureCtrlMask = 0; - table_context->user_od_feature_mask = 0; } fallthrough; case PP_OD_COMMIT_DPM_TABLE: - table_context->user_od_feature_mask = - od_table->OverDriveTable.FeatureCtrlMask; offset_of_voltageoffset = offsetof(OverDriveTable_t, VoltageOffsetPerZoneBoundary); - if (memcmp((u8 *)od_table + offset_of_voltageoffset, - table_context->user_overdrive_table + offset_of_voltageoffset, - sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset)) { - smu_v13_0_7_dump_od_table(smu, od_table); + next_mask = smu_v13_0_7_filter_od_feature_mask(smu, + table_context->user_od_feature_mask | + od->FeatureCtrlMask); + next_mask = smu_v13_0_7_normalize_feature_mask(od, boot, + next_mask); - ret = smu_v13_0_7_upload_overdrive_table(smu, od_table); + if (memcmp((u8 *)od_table + offset_of_voltageoffset, + (u8 *)user_od_table + offset_of_voltageoffset, + sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset) || + next_mask != table_context->user_od_feature_mask) { + memcpy(&upload_table, od_table, sizeof(upload_table)); + upload_table.OverDriveTable.FeatureCtrlMask = next_mask; + smu_v13_0_7_dump_od_table(smu, &upload_table); + + ret = smu_v13_0_7_upload_overdrive_table(smu, &upload_table); if (ret) { dev_err(adev->dev, "Failed to upload overdrive table!\n"); return ret; } - memcpy(table_context->user_overdrive_table + offset_of_voltageoffset, + table_context->user_od_feature_mask = next_mask; + od_table->OverDriveTable.FeatureCtrlMask = 0; + memcpy((u8 *)user_od_table + offset_of_voltageoffset, (u8 *)od_table + offset_of_voltageoffset, sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset); + user_od_table->OverDriveTable.FeatureCtrlMask = 0; - if (!memcmp(table_context->user_overdrive_table, - table_context->boot_overdrive_table, - sizeof(OverDriveTableExternal_t))) - smu->user_dpm_profile.user_od = false; - else - smu->user_dpm_profile.user_od = true; + smu->user_dpm_profile.user_od = + !!(next_mask & ~BIT(PP_OD_FEATURE_PPT_BIT)); } break; @@ -2186,6 +2279,7 @@ static int smu_v13_0_7_set_default_od_settings(struct smu_context *smu) memcpy(od_table, boot_od_table, sizeof(OverDriveTableExternal_t)); + od_table->OverDriveTable.FeatureCtrlMask = 0; /* * For S3/S4/Runpm resume, we need to setup those overdrive tables again, @@ -2195,6 +2289,7 @@ static int smu_v13_0_7_set_default_od_settings(struct smu_context *smu) memcpy(user_od_table, boot_od_table, sizeof(OverDriveTableExternal_t)); + user_od_table->OverDriveTable.FeatureCtrlMask = 0; smu->user_dpm_profile.user_od = false; smu->smu_table.user_od_feature_mask = 0; } else if (smu->user_dpm_profile.user_od) { @@ -2233,6 +2328,14 @@ static int smu_v13_0_7_set_default_od_settings(struct smu_context *smu) user_od_table_bak.OverDriveTable.FanZeroRpmEnable; user_od_table->OverDriveTable.FanZeroRpmStopTemp = user_od_table_bak.OverDriveTable.FanZeroRpmStopTemp; + user_od_table->OverDriveTable.FanMode = + user_od_table_bak.OverDriveTable.FanMode; + user_od_table->OverDriveTable.FeatureCtrlMask = 0; + } + if (smu->adev->in_suspend) { + user_od_table->OverDriveTable.Ppt = 0; + smu->smu_table.user_od_feature_mask &= + ~BIT(PP_OD_FEATURE_PPT_BIT); } smu_v13_0_7_set_supported_od_feature_mask(smu); @@ -2245,14 +2348,22 @@ static int smu_v13_0_7_restore_user_od_settings(struct smu_context *smu) struct smu_table_context *table_context = &smu->smu_table; OverDriveTableExternal_t *od_table = table_context->overdrive_table; OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; + OverDriveTableExternal_t upload_table; + u32 next_mask; int res; - user_od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - res = smu_v13_0_7_upload_overdrive_table(smu, user_od_table); - user_od_table->OverDriveTable.FeatureCtrlMask = 0; - if (res == 0) + next_mask = smu_v13_0_7_filter_od_feature_mask(smu, + table_context->user_od_feature_mask); + memcpy(&upload_table, user_od_table, sizeof(upload_table)); + upload_table.OverDriveTable.FeatureCtrlMask = next_mask; + res = smu_v13_0_7_upload_overdrive_table(smu, &upload_table); + if (res == 0) { + table_context->user_od_feature_mask = next_mask; + smu->user_dpm_profile.user_od = + !!(next_mask & ~BIT(PP_OD_FEATURE_PPT_BIT)); memcpy(od_table, user_od_table, sizeof(OverDriveTableExternal_t)); + od_table->OverDriveTable.FeatureCtrlMask = 0; + } return res; } @@ -2664,10 +2775,8 @@ static int smu_v13_0_7_set_ppt_limit(struct smu_context *smu, SkuTable_t *skutable = &pptable->SkuTable; uint32_t msg_limit = skutable->MsgLimits.Power [PPT_THROTTLER_PPT0][POWER_SOURCE_AC]; - struct smu_table_context *table_context = &smu->smu_table; - OverDriveTableExternal_t *od_table = - (OverDriveTableExternal_t *)table_context->overdrive_table; OverDriveTableExternal_t current_od_table; + s16 ppt; int ret = 0; if (limit_type != SMU_PPT_LIMIT_PPT0) @@ -2679,13 +2788,7 @@ static int smu_v13_0_7_set_ppt_limit(struct smu_context *smu, return ret; if (current_od_table.OverDriveTable.Ppt) { - od_table->OverDriveTable.Ppt = 0; - table_context->user_od_feature_mask &= - ~(1U << PP_OD_FEATURE_PPT_BIT); - od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - - ret = smu_v13_0_7_upload_overdrive_table(smu, od_table); + ret = smu_v13_0_7_upload_ppt_overdrive_table(smu, 0); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); return ret; @@ -2697,12 +2800,8 @@ static int smu_v13_0_7_set_ppt_limit(struct smu_context *smu, if (ret) return ret; - od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit - 100; - table_context->user_od_feature_mask |= 1U << PP_OD_FEATURE_PPT_BIT; - od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - - ret = smu_v13_0_7_upload_overdrive_table(smu, od_table); + ppt = (limit * 100) / msg_limit - 100; + ret = smu_v13_0_7_upload_ppt_overdrive_table(smu, ppt); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); return ret; From af52c8fd45fbd5e3b7a329858733ac674abe539c Mon Sep 17 00:00:00 2001 From: Kenneth Feng Date: Thu, 8 Oct 2026 20:28:09 +0800 Subject: [PATCH 43/52] drm/amd/pm: add zero fan stop temperature support on smu 14.0.2 add zero fan stop temperature support on smu 14.0.2 Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5905 Signed-off-by: Kenneth Feng Reviewed-by: Kevin Wang Signed-off-by: Alex Deucher (cherry picked from commit f990c94a2633d0ead477eb1b174e254a6d292e66) Cc: stable@vger.kernel.org --- .../drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) 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 549716349117..566b7f2b00f0 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 @@ -89,6 +89,7 @@ static const struct smu_feature_bits smu_v14_0_2_dpm_features = { #define PP_OD_FEATURE_FAN_TARGET_TEMPERATURE 9 #define PP_OD_FEATURE_FAN_MINIMUM_PWM 10 #define PP_OD_FEATURE_FAN_ZERO_RPM_ENABLE 11 +#define PP_OD_FEATURE_FAN_ZERO_RPM_STOP_TEMP 12 static struct cmn2asic_msg_mapping smu_v14_0_2_message_map[SMU_MSG_MAX_COUNT] = { MSG_MAP(TestMessage, PPSMC_MSG_TestMessage, 1), @@ -1020,6 +1021,10 @@ static void smu_v14_0_2_get_od_setting_limits(struct smu_context *smu, od_min_setting = overdrive_lowerlimits->FanZeroRpmEnable; od_max_setting = overdrive_upperlimits->FanZeroRpmEnable; break; + case PP_OD_FEATURE_FAN_ZERO_RPM_STOP_TEMP: + od_min_setting = pptable->SkuTable.MsgLimits.FanStopTempMin; + od_max_setting = pptable->SkuTable.MsgLimits.FanStopTempMax; + break; default: od_min_setting = od_max_setting = INT_MAX; break; @@ -1244,6 +1249,24 @@ static int smu_v14_0_2_emit_clk_levels(struct smu_context *smu, min_value, max_value); break; + case SMU_OD_FAN_ZERO_RPM_STOP_TEMP: + if (!smu_v14_0_2_is_od_feature_supported(smu, + PP_OD_FEATURE_ZERO_FAN_BIT)) + break; + + size += sysfs_emit_at(buf, size, "FAN_ZERO_RPM_STOP_TEMPERATURE:\n"); + size += sysfs_emit_at(buf, size, "%d\n", + (int)od_table->OverDriveTable.FanZeroRpmStopTemp); + + size += sysfs_emit_at(buf, size, "%s:\n", "OD_RANGE"); + smu_v14_0_2_get_od_setting_limits(smu, + PP_OD_FEATURE_FAN_ZERO_RPM_STOP_TEMP, + &min_value, + &max_value); + size += sysfs_emit_at(buf, size, "ZERO_RPM_STOP_TEMPERATURE: %u %u\n", + min_value, max_value); + break; + case SMU_OD_RANGE: if (!smu_v14_0_2_is_od_feature_supported(smu, PP_OD_FEATURE_GFXCLK_BIT) && !smu_v14_0_2_is_od_feature_supported(smu, PP_OD_FEATURE_UCLK_BIT) && @@ -2311,7 +2334,9 @@ static void smu_v14_0_2_set_supported_od_feature_mask(struct smu_context *smu) OD_OPS_SUPPORT_FAN_MINIMUM_PWM_RETRIEVE | OD_OPS_SUPPORT_FAN_MINIMUM_PWM_SET | OD_OPS_SUPPORT_FAN_ZERO_RPM_ENABLE_RETRIEVE | - OD_OPS_SUPPORT_FAN_ZERO_RPM_ENABLE_SET; + OD_OPS_SUPPORT_FAN_ZERO_RPM_ENABLE_SET | + OD_OPS_SUPPORT_FAN_ZERO_RPM_STOP_TEMP_RETRIEVE | + OD_OPS_SUPPORT_FAN_ZERO_RPM_STOP_TEMP_SET; } static int smu_v14_0_2_get_overdrive_table(struct smu_context *smu, @@ -2393,6 +2418,8 @@ static int smu_v14_0_2_set_default_od_settings(struct smu_context *smu) user_od_table_bak.OverDriveTable.FanMinimumPwm; user_od_table->OverDriveTable.FanZeroRpmEnable = user_od_table_bak.OverDriveTable.FanZeroRpmEnable; + user_od_table->OverDriveTable.FanZeroRpmStopTemp = + user_od_table_bak.OverDriveTable.FanZeroRpmStopTemp; } smu_v14_0_2_set_supported_od_feature_mask(smu); @@ -2444,6 +2471,11 @@ static int smu_v14_0_2_od_restore_table_single(struct smu_context *smu, long inp boot_overdrive_table->OverDriveTable.FanZeroRpmEnable; od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; + case PP_OD_EDIT_FAN_ZERO_RPM_STOP_TEMP: + od_table->OverDriveTable.FanZeroRpmStopTemp = + boot_overdrive_table->OverDriveTable.FanZeroRpmStopTemp; + od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + break; case PP_OD_EDIT_ACOUSTIC_LIMIT: od_table->OverDriveTable.AcousticLimitRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticLimitRpmThreshold; @@ -2749,6 +2781,27 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; + case PP_OD_EDIT_FAN_ZERO_RPM_STOP_TEMP: + if (!smu_v14_0_2_is_od_feature_supported(smu, PP_OD_FEATURE_ZERO_FAN_BIT)) { + dev_warn(adev->dev, "Zero RPM setting not supported!\n"); + return -ENOTSUPP; + } + + smu_v14_0_2_get_od_setting_limits(smu, + PP_OD_FEATURE_FAN_ZERO_RPM_STOP_TEMP, + &minimum, + &maximum); + if (input[0] < minimum || + input[0] > maximum) { + dev_info(adev->dev, "zero RPM stop temperature setting(%ld) must be within [%d, %d]!\n", + input[0], minimum, maximum); + return -EINVAL; + } + + od_table->OverDriveTable.FanZeroRpmStopTemp = input[0]; + od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + break; + case PP_OD_RESTORE_DEFAULT_TABLE: if (size == 1) { ret = smu_v14_0_2_od_restore_table_single(smu, input[0]); From 9b0890e72d19b0c430fe62bad290c7c79baa78e4 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 15 Oct 2026 14:02:31 +0800 Subject: [PATCH 44/52] drm/amd/pm: preserve committed OD features for SMU 14.0.2 PMFW treats FeatureCtrlMask as the complete set of active OD features. Uploads containing only newly edited bits can drop earlier settings. Fix the OD state handling to: - preserve committed features across later commits - keep fan-curve and legacy-fan bits mutually exclusive - keep staged OD settings out of power-cap writes - update cached state only after successful uploads - clear restored features only after they return to defaults - restore PPT for the current AC/DC source after resume Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/3625 Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 8d4ce79aff1cd1ff4feea4967c30e04eb6be8c2d) Cc: stable@vger.kernel.org --- .../drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 230 +++++++++++++----- 1 file changed, 171 insertions(+), 59 deletions(-) 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 566b7f2b00f0..6a12d6f8c9dd 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 @@ -91,6 +91,10 @@ static const struct smu_feature_bits smu_v14_0_2_dpm_features = { #define PP_OD_FEATURE_FAN_ZERO_RPM_ENABLE 11 #define PP_OD_FEATURE_FAN_ZERO_RPM_STOP_TEMP 12 +#define SMU_14_0_2_OD_FAN_FEATURE_MASK \ + (BIT(PP_OD_FEATURE_FAN_CURVE_BIT) | \ + BIT(PP_OD_FEATURE_FAN_LEGACY_BIT)) + static struct cmn2asic_msg_mapping smu_v14_0_2_message_map[SMU_MSG_MAX_COUNT] = { MSG_MAP(TestMessage, PPSMC_MSG_TestMessage, 1), MSG_MAP(GetSmuVersion, PPSMC_MSG_GetSmuVersion, 1), @@ -963,6 +967,84 @@ static bool smu_v14_0_2_is_od_feature_supported(struct smu_context *smu, return feature_enabled; } +static u32 +smu_v14_0_2_filter_od_feature_mask(struct smu_context *smu, u32 feature_mask) +{ + PPTable_t *pptable = smu->smu_table.driver_pptable; + u32 supported_mask = + pptable->SkuTable.OverDriveLimitsBasicMax.FeatureCtrlMask; + + return feature_mask & supported_mask; +} + +static u32 +smu_v14_0_2_get_od_feature_mask(struct smu_context *smu, u32 pending_mask) +{ + u32 supported_mask = smu_v14_0_2_filter_od_feature_mask(smu, ~0U); + u32 fan_mask = supported_mask & SMU_14_0_2_OD_FAN_FEATURE_MASK; + u32 mask = smu->smu_table.user_od_feature_mask; + + if (pending_mask & fan_mask) + mask &= ~fan_mask; + mask |= pending_mask; + + return smu_v14_0_2_filter_od_feature_mask(smu, mask); +} + +static bool smu_v14_0_2_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return !memcmp(od->FanLinearPwmPoints, boot->FanLinearPwmPoints, + sizeof(od->FanLinearPwmPoints)) && + !memcmp(od->FanLinearTempPoints, boot->FanLinearTempPoints, + sizeof(od->FanLinearTempPoints)) && + od->AcousticTargetRpmThreshold == boot->AcousticTargetRpmThreshold && + od->AcousticLimitRpmThreshold == boot->AcousticLimitRpmThreshold && + od->FanTargetTemperature == boot->FanTargetTemperature && + od->FanMode == boot->FanMode; +} + +static bool smu_v14_0_2_legacy_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return od->FanMinimumPwm == boot->FanMinimumPwm && + od->FanMode == boot->FanMode; +} + +static bool smu_v14_0_2_zero_fan_is_default(const OverDriveTable_t *od, + const OverDriveTable_t *boot) +{ + return od->FanZeroRpmEnable == boot->FanZeroRpmEnable && + od->FanZeroRpmStopTemp == boot->FanZeroRpmStopTemp; +} + +static u32 smu_v14_0_2_normalize_feature_mask(const OverDriveTable_t *od, + const OverDriveTable_t *boot, + u32 feature_mask) +{ + if (od->GfxclkFoffset == boot->GfxclkFoffset) + feature_mask &= ~BIT(PP_OD_FEATURE_GFXCLK_BIT); + if (od->UclkFmin == boot->UclkFmin && + od->UclkFmax == boot->UclkFmax) + feature_mask &= ~BIT(PP_OD_FEATURE_UCLK_BIT); + if (!memcmp(od->VoltageOffsetPerZoneBoundary, + boot->VoltageOffsetPerZoneBoundary, + sizeof(od->VoltageOffsetPerZoneBoundary))) + feature_mask &= ~BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT); + if ((feature_mask & BIT(PP_OD_FEATURE_FAN_CURVE_BIT)) && + smu_v14_0_2_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_FAN_CURVE_BIT); + if ((feature_mask & BIT(PP_OD_FEATURE_FAN_LEGACY_BIT)) && + smu_v14_0_2_legacy_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_FAN_LEGACY_BIT); + if (smu_v14_0_2_zero_fan_is_default(od, boot)) + feature_mask &= ~BIT(PP_OD_FEATURE_ZERO_FAN_BIT); + if (!od->Ppt) + feature_mask &= ~BIT(PP_OD_FEATURE_PPT_BIT); + + return feature_mask; +} + static void smu_v14_0_2_get_od_setting_limits(struct smu_context *smu, int od_feature_bit, int32_t *min, @@ -2317,6 +2399,38 @@ static int smu_v14_0_2_upload_overdrive_table(struct smu_context *smu, return ret; } +static int +smu_v14_0_2_upload_ppt_overdrive_table(struct smu_context *smu, s16 ppt) +{ + struct smu_table_context *table_context = &smu->smu_table; + OverDriveTableExternal_t *od_table = table_context->overdrive_table; + OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; + OverDriveTableExternal_t upload_table; + u32 next_mask = table_context->user_od_feature_mask; + int ret; + + /* Do not commit OD settings staged after the last c operation. */ + memcpy(&upload_table, user_od_table, sizeof(upload_table)); + upload_table.OverDriveTable.Ppt = ppt; + if (ppt) + next_mask |= BIT(PP_OD_FEATURE_PPT_BIT); + else + next_mask &= ~BIT(PP_OD_FEATURE_PPT_BIT); + upload_table.OverDriveTable.FeatureCtrlMask = + smu_v14_0_2_filter_od_feature_mask(smu, next_mask); + + ret = smu_v14_0_2_upload_overdrive_table(smu, &upload_table); + if (ret) + return ret; + + table_context->user_od_feature_mask = + upload_table.OverDriveTable.FeatureCtrlMask; + od_table->OverDriveTable.Ppt = ppt; + user_od_table->OverDriveTable.Ppt = ppt; + + return 0; +} + static void smu_v14_0_2_set_supported_od_feature_mask(struct smu_context *smu) { struct amdgpu_device *adev = smu->adev; @@ -2375,6 +2489,7 @@ static int smu_v14_0_2_set_default_od_settings(struct smu_context *smu) memcpy(od_table, boot_od_table, sizeof(OverDriveTableExternal_t)); + od_table->OverDriveTable.FeatureCtrlMask = 0; /* * For S3/S4/Runpm resume, we need to setup those overdrive tables again, @@ -2384,6 +2499,7 @@ static int smu_v14_0_2_set_default_od_settings(struct smu_context *smu) memcpy(user_od_table, boot_od_table, sizeof(OverDriveTableExternal_t)); + user_od_table->OverDriveTable.FeatureCtrlMask = 0; smu->user_dpm_profile.user_od = false; smu->smu_table.user_od_feature_mask = 0; } else if (smu->user_dpm_profile.user_od) { @@ -2420,6 +2536,14 @@ static int smu_v14_0_2_set_default_od_settings(struct smu_context *smu) user_od_table_bak.OverDriveTable.FanZeroRpmEnable; user_od_table->OverDriveTable.FanZeroRpmStopTemp = user_od_table_bak.OverDriveTable.FanZeroRpmStopTemp; + user_od_table->OverDriveTable.FanMode = + user_od_table_bak.OverDriveTable.FanMode; + user_od_table->OverDriveTable.FeatureCtrlMask = 0; + } + if (smu->adev->in_suspend) { + user_od_table->OverDriveTable.Ppt = 0; + smu->smu_table.user_od_feature_mask &= + ~BIT(PP_OD_FEATURE_PPT_BIT); } smu_v14_0_2_set_supported_od_feature_mask(smu); @@ -2432,14 +2556,22 @@ static int smu_v14_0_2_restore_user_od_settings(struct smu_context *smu) struct smu_table_context *table_context = &smu->smu_table; OverDriveTableExternal_t *od_table = table_context->overdrive_table; OverDriveTableExternal_t *user_od_table = table_context->user_overdrive_table; + OverDriveTableExternal_t upload_table; + u32 next_mask; int res; - user_od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - res = smu_v14_0_2_upload_overdrive_table(smu, user_od_table); - user_od_table->OverDriveTable.FeatureCtrlMask = 0; - if (res == 0) + next_mask = smu_v14_0_2_filter_od_feature_mask(smu, + table_context->user_od_feature_mask); + memcpy(&upload_table, user_od_table, sizeof(upload_table)); + upload_table.OverDriveTable.FeatureCtrlMask = next_mask; + res = smu_v14_0_2_upload_overdrive_table(smu, &upload_table); + if (res == 0) { + table_context->user_od_feature_mask = next_mask; + smu->user_dpm_profile.user_od = + !!(next_mask & ~BIT(PP_OD_FEATURE_PPT_BIT)); memcpy(od_table, user_od_table, sizeof(OverDriveTableExternal_t)); + od_table->OverDriveTable.FeatureCtrlMask = 0; + } return res; } @@ -2463,43 +2595,30 @@ static int smu_v14_0_2_od_restore_table_single(struct smu_context *smu, long inp boot_overdrive_table->OverDriveTable.FanLinearPwmPoints[i]; } od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); - od_table->OverDriveTable.FeatureCtrlMask &= ~BIT(PP_OD_FEATURE_FAN_LEGACY_BIT); break; case PP_OD_EDIT_FAN_ZERO_RPM_ENABLE: od_table->OverDriveTable.FanZeroRpmEnable = boot_overdrive_table->OverDriveTable.FanZeroRpmEnable; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; case PP_OD_EDIT_FAN_ZERO_RPM_STOP_TEMP: od_table->OverDriveTable.FanZeroRpmStopTemp = boot_overdrive_table->OverDriveTable.FanZeroRpmStopTemp; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_ZERO_FAN_BIT); break; case PP_OD_EDIT_ACOUSTIC_LIMIT: od_table->OverDriveTable.AcousticLimitRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticLimitRpmThreshold; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_ACOUSTIC_TARGET: od_table->OverDriveTable.AcousticTargetRpmThreshold = boot_overdrive_table->OverDriveTable.AcousticTargetRpmThreshold; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_TARGET_TEMPERATURE: od_table->OverDriveTable.FanTargetTemperature = boot_overdrive_table->OverDriveTable.FanTargetTemperature; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; case PP_OD_EDIT_FAN_MINIMUM_PWM: od_table->OverDriveTable.FanMinimumPwm = boot_overdrive_table->OverDriveTable.FanMinimumPwm; - od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; - od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_LEGACY_BIT); - od_table->OverDriveTable.FeatureCtrlMask &= ~BIT(PP_OD_FEATURE_FAN_CURVE_BIT); break; default: dev_info(adev->dev, "Invalid table index: %ld\n", input); @@ -2517,9 +2636,17 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, struct smu_table_context *table_context = &smu->smu_table; OverDriveTableExternal_t *od_table = (OverDriveTableExternal_t *)table_context->overdrive_table; + OverDriveTableExternal_t *user_od_table = + (OverDriveTableExternal_t *)table_context->user_overdrive_table; + OverDriveTableExternal_t *boot_od_table = + (OverDriveTableExternal_t *)table_context->boot_overdrive_table; + OverDriveTableExternal_t upload_table; + OverDriveTable_t *od = &od_table->OverDriveTable; + OverDriveTable_t *boot = &boot_od_table->OverDriveTable; struct amdgpu_device *adev = smu->adev; uint32_t offset_of_voltageoffset; int32_t minimum, maximum; + u32 next_mask; int i, ret = 0; switch (type) { @@ -2691,6 +2818,7 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, od_table->OverDriveTable.AcousticLimitRpmThreshold = input[0]; od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); + od_table->OverDriveTable.FeatureCtrlMask &= ~BIT(PP_OD_FEATURE_FAN_LEGACY_BIT); break; case PP_OD_EDIT_ACOUSTIC_TARGET: @@ -2713,6 +2841,7 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, od_table->OverDriveTable.AcousticTargetRpmThreshold = input[0]; od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); + od_table->OverDriveTable.FeatureCtrlMask &= ~BIT(PP_OD_FEATURE_FAN_LEGACY_BIT); break; case PP_OD_EDIT_FAN_TARGET_TEMPERATURE: @@ -2735,6 +2864,7 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, od_table->OverDriveTable.FanTargetTemperature = input[0]; od_table->OverDriveTable.FanMode = FAN_MODE_AUTO; od_table->OverDriveTable.FeatureCtrlMask |= BIT(PP_OD_FEATURE_FAN_CURVE_BIT); + od_table->OverDriveTable.FeatureCtrlMask &= ~BIT(PP_OD_FEATURE_FAN_LEGACY_BIT); break; case PP_OD_EDIT_FAN_MINIMUM_PWM: @@ -2812,44 +2942,38 @@ static int smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu, table_context->boot_overdrive_table, sizeof(OverDriveTableExternal_t)); od_table->OverDriveTable.FeatureCtrlMask = 0; - table_context->user_od_feature_mask = 0; } fallthrough; case PP_OD_COMMIT_DPM_TABLE: - /* - * PMFW interprets FeatureCtrlMask as the complete set of OD - * features that are currently overridden, not as a delta - * describing this single operation. Any feature whose bit is - * clear gets reset to its default on import. od_table's mask - * has accumulated every feature the user touched since the - * last full restore, so record it and keep restating it on - * subsequent uploads. - */ - table_context->user_od_feature_mask = - od_table->OverDriveTable.FeatureCtrlMask; - offset_of_voltageoffset = offsetof(OverDriveTable_t, VoltageOffsetPerZoneBoundary); - if (memcmp((u8 *)od_table + offset_of_voltageoffset, - table_context->user_overdrive_table + offset_of_voltageoffset, - sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset)) { - smu_v14_0_2_dump_od_table(smu, od_table); + next_mask = smu_v14_0_2_get_od_feature_mask(smu, + od->FeatureCtrlMask); + next_mask = smu_v14_0_2_normalize_feature_mask(od, boot, + next_mask); - ret = smu_v14_0_2_upload_overdrive_table(smu, od_table); + if (memcmp((u8 *)od_table + offset_of_voltageoffset, + (u8 *)user_od_table + offset_of_voltageoffset, + sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset) || + next_mask != table_context->user_od_feature_mask) { + memcpy(&upload_table, od_table, sizeof(upload_table)); + upload_table.OverDriveTable.FeatureCtrlMask = next_mask; + smu_v14_0_2_dump_od_table(smu, &upload_table); + + ret = smu_v14_0_2_upload_overdrive_table(smu, &upload_table); if (ret) { dev_err(adev->dev, "Failed to upload overdrive table!\n"); return ret; } - memcpy(table_context->user_overdrive_table + offset_of_voltageoffset, + table_context->user_od_feature_mask = next_mask; + od_table->OverDriveTable.FeatureCtrlMask = 0; + memcpy((u8 *)user_od_table + offset_of_voltageoffset, (u8 *)od_table + offset_of_voltageoffset, sizeof(OverDriveTableExternal_t) - offset_of_voltageoffset); + user_od_table->OverDriveTable.FeatureCtrlMask = 0; - if (!memcmp(table_context->user_overdrive_table, - table_context->boot_overdrive_table, - sizeof(OverDriveTableExternal_t))) - smu->user_dpm_profile.user_od = false; - else - smu->user_dpm_profile.user_od = true; + smu->user_dpm_profile.user_od = + !!(next_mask & ~BIT(PP_OD_FEATURE_PPT_BIT)); } break; @@ -2867,10 +2991,8 @@ static int smu_v14_0_2_set_ppt_limit(struct smu_context *smu, PPTable_t *pptable = smu->smu_table.driver_pptable; uint32_t msg_limit = pptable->SkuTable.MsgLimits.Power [PPT_THROTTLER_PPT0][POWER_SOURCE_AC]; - struct smu_table_context *table_context = &smu->smu_table; - OverDriveTableExternal_t *od_table = - (OverDriveTableExternal_t *)table_context->overdrive_table; OverDriveTableExternal_t current_od_table; + s16 ppt; int ret = 0; if (limit_type != SMU_PPT_LIMIT_PPT0) @@ -2882,13 +3004,7 @@ static int smu_v14_0_2_set_ppt_limit(struct smu_context *smu, return ret; if (current_od_table.OverDriveTable.Ppt) { - od_table->OverDriveTable.Ppt = 0; - table_context->user_od_feature_mask &= - ~(1U << PP_OD_FEATURE_PPT_BIT); - od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - - ret = smu_v14_0_2_upload_overdrive_table(smu, od_table); + ret = smu_v14_0_2_upload_ppt_overdrive_table(smu, 0); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); return ret; @@ -2900,12 +3016,8 @@ static int smu_v14_0_2_set_ppt_limit(struct smu_context *smu, if (ret) return ret; - od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit - 100; - table_context->user_od_feature_mask |= 1U << PP_OD_FEATURE_PPT_BIT; - od_table->OverDriveTable.FeatureCtrlMask = - table_context->user_od_feature_mask; - - ret = smu_v14_0_2_upload_overdrive_table(smu, od_table); + ppt = (limit * 100) / msg_limit - 100; + ret = smu_v14_0_2_upload_ppt_overdrive_table(smu, ppt); if (ret) { dev_err(smu->adev->dev, "Failed to upload overdrive table!\n"); return ret; From 9866cb13ba4d39aa8fed34b765d4aecabe04f02e Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 16 Oct 2026 22:15:04 +0800 Subject: [PATCH 45/52] drm/amdkfd: validate SRAT subtables before parsing The KFD SRAT walker reads the first subtable length without checking that a header is present and trusts subsequent lengths when accessing CPU and generic affinity records. Invalid firmware lengths can therefore cause out-of-bounds reads. Validate the table header, every generic subtable range, and each supported typed record before reading its fields. Fixes: ddec8d3be0f8 ("drm/amdkfd: add ACPI SRAT parsing for topology") Reviewed-by: Alex Deucher Signed-off-by: Jiale Yao Signed-off-by: Alex Deucher (cherry picked from commit d76b655c98e8cc9e791b31502ad274f47dee2aae) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_crat.c | 35 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c index 6e0df685503d..32d7573dffc9 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c @@ -2056,27 +2056,33 @@ static void kfd_find_numa_node_in_srat(struct kfd_node *kdev) return; } + if (table_header->length < sizeof(struct acpi_table_srat)) + goto invalid_srat; + table_end = (unsigned long)table_header + table_header->length; /* Parse all entries looking for a match. */ sub_header = (struct acpi_subtable_header *) ((unsigned long)table_header + sizeof(struct acpi_table_srat)); - subtable_len = sub_header->length; - while (((unsigned long)sub_header) + subtable_len < table_end) { - /* - * If length is 0, break from this loop to avoid - * infinite loop. - */ - if (subtable_len == 0) { - pr_err("SRAT invalid zero length\n"); - break; - } + while ((unsigned long)sub_header < table_end) { + unsigned long start = (unsigned long)sub_header; + + if (table_end - start < sizeof(*sub_header)) + goto invalid_srat; + + subtable_len = sub_header->length; + if (subtable_len < sizeof(*sub_header) || + subtable_len > table_end - start) + goto invalid_srat; switch (sub_header->type) { case ACPI_SRAT_TYPE_CPU_AFFINITY: cpu = (struct acpi_srat_cpu_affinity *)sub_header; + if (subtable_len < sizeof(*cpu)) + goto invalid_srat; + pxm = *((u32 *)cpu->proximity_domain_hi) << 8 | cpu->proximity_domain_lo; if (pxm > max_pxm) @@ -2084,6 +2090,9 @@ static void kfd_find_numa_node_in_srat(struct kfd_node *kdev) break; case ACPI_SRAT_TYPE_GENERIC_AFFINITY: gpu = (struct acpi_srat_generic_affinity *)sub_header; + if (subtable_len < sizeof(*gpu)) + goto invalid_srat; + bdf = *((u16 *)(&gpu->device_handle[0])) << 16 | *((u16 *)(&gpu->device_handle[2])); if (bdf == pci_id) { @@ -2100,9 +2109,13 @@ static void kfd_find_numa_node_in_srat(struct kfd_node *kdev) sub_header = (struct acpi_subtable_header *) ((unsigned long)sub_header + subtable_len); - subtable_len = sub_header->length; } + goto out; + +invalid_srat: + pr_err("SRAT contains an invalid subtable\n"); +out: acpi_put_table(table_header); /* Workaround bad cpu-gpu binding case */ From 2e0f334f5c7140b67f16eff9d93c2ca426848664 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 12 Oct 2026 09:27:03 +0800 Subject: [PATCH 46/52] drm/amdkfd: send SMI process and VM fault events only to allowed clients Commit 92a8dba246d3 ("drm/amdkfd: fix SMI event cross-process information leak") made the VMFAULT and PROCESS_START/END SMI events carry the pid of the process they are about, so that kfd_smi_ev_enabled() only passes them to clients of that process and to privileged clients. Commit 1142738572ef ("drm/amdkfd: Fix SMI event PID reporting for containers") then switched kfd_smi_event_add() to take a task and passed NULL for these events, i.e. pid 0, which every client receives again. On navi31, an unprivileged SMI client (uid 1000) subscribed to PROCESS_START/END sees root running rocminfo: event: c 223a9 rocminfo event: d 223a9 rocminfo The same commit also made the filter compare PIDs of different namespaces: the client stores its PID in its own namespace and events use the PID of the task in the task's namespace, so a client that is PID 1 in one container receives the events of PID 1 in another. And an exited task has no PID in its namespace anymore, which gives pid 0 for PROCESS_END. Match clients by global TGID, which is unique and stays valid after the task exited, and keep showing the namespace PID in the event text. Pass the TGID of the process for the process and VM fault events again. With this the unprivileged client gets nothing, a root client still gets both events, and an unprivileged client that is PID 1 in its own PID namespace no longer gets the events of rocminfo running as PID 1 in another one. v2: rename the pid of the clients and of the filter to tgid (Felix) Fixes: 1142738572ef ("drm/amdkfd: Fix SMI event PID reporting for containers") Reviewed-by: Felix Kuehling Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 77f5f4eae104a96399a5b1003730d5d739368b7d) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c | 60 ++++++++++++--------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c index 6a7b4d959541..c87370a9f5f0 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c @@ -40,7 +40,7 @@ struct kfd_smi_client { struct kfd_node *dev; spinlock_t lock; struct rcu_head rcu; - pid_t pid; + pid_t tgid; bool suser; }; @@ -160,18 +160,18 @@ static int kfd_smi_ev_release(struct inode *inode, struct file *filep) return 0; } -static bool kfd_smi_ev_enabled(pid_t pid, struct kfd_smi_client *client, +static bool kfd_smi_ev_enabled(pid_t tgid, struct kfd_smi_client *client, unsigned int event) { uint64_t events = READ_ONCE(client->events); - if (pid && client->pid != pid && !client->suser) + if (tgid && client->tgid != tgid && !client->suser) return false; return events & KFD_SMI_EVENT_MASK_FROM_INDEX(event); } -static void add_event_to_kfifo(pid_t pid, struct kfd_node *dev, +static void add_event_to_kfifo(pid_t tgid, struct kfd_node *dev, unsigned int smi_event, char *event_msg, int len) { struct kfd_smi_client *client; @@ -179,7 +179,7 @@ static void add_event_to_kfifo(pid_t pid, struct kfd_node *dev, rcu_read_lock(); list_for_each_entry_rcu(client, &dev->smi_clients, list) { - if (!kfd_smi_ev_enabled(pid, client, smi_event)) + if (!kfd_smi_ev_enabled(tgid, client, smi_event)) continue; spin_lock(&client->lock); if (kfifo_avail(&client->fifo) >= len) { @@ -210,27 +210,33 @@ 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; } +/* + * Clients are matched by global TGID, which is unique across PID namespaces + * and stays valid after the task exited. Events show kfd_smi_task_to_pid(). + */ +static inline pid_t kfd_smi_task_tgid(struct task_struct *task) +{ + return task ? task_tgid_nr(task) : 0; +} + __printf(4, 5) -static void kfd_smi_event_add(struct task_struct *task, struct kfd_node *dev, +static void kfd_smi_event_add(pid_t tgid, 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 = scnprintf(fifo_in, sizeof(fifo_in), "%x ", event); va_start(args, fmt); len += vscnprintf(fifo_in + len, sizeof(fifo_in) - len, fmt, args); va_end(args); - add_event_to_kfifo(pid, dev, event, fifo_in, len); + add_event_to_kfifo(tgid, dev, event, fifo_in, len); } void kfd_smi_event_update_gpu_reset(struct kfd_node *dev, bool post_reset, @@ -252,14 +258,14 @@ 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(NULL, dev, event, KFD_EVENT_FMT_UPDATE_GPU_RESET( + kfd_smi_event_add(0, 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(NULL, dev, KFD_SMI_EVENT_THERMAL_THROTTLE, + kfd_smi_event_add(0, dev, KFD_SMI_EVENT_THERMAL_THROTTLE, KFD_EVENT_FMT_THERMAL_THROTTLING( throttle_bitmask, amdgpu_dpm_get_thermal_throttling_counter(dev->adev))); @@ -271,10 +277,14 @@ void kfd_smi_event_update_vmfault(struct kfd_node *dev, uint16_t pasid) task_info = amdgpu_vm_get_task_info_pasid(dev->adev, pasid); if (task_info) { - /* Report VM faults from user applications, not retry from kernel */ + /* + * Report VM faults from user applications, not retry from + * kernel, and only to clients allowed to see that process. + */ if (task_info->task.pid) - kfd_smi_event_add(NULL, dev, KFD_SMI_EVENT_VMFAULT, KFD_EVENT_FMT_VMFAULT( - task_info->task.pid, task_info->task.comm)); + kfd_smi_event_add(task_info->tgid, dev, KFD_SMI_EVENT_VMFAULT, + KFD_EVENT_FMT_VMFAULT(task_info->task.pid, + task_info->task.comm)); amdgpu_vm_put_task_info(task_info); } } @@ -283,7 +293,7 @@ void kfd_smi_event_page_fault_start(struct kfd_node *node, struct task_struct *t unsigned long address, bool write_fault, ktime_t ts) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_PAGE_FAULT_START, + kfd_smi_event_add(kfd_smi_task_tgid(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')); @@ -292,7 +302,7 @@ void kfd_smi_event_page_fault_start(struct kfd_node *node, struct task_struct *t void kfd_smi_event_page_fault_end(struct kfd_node *node, struct task_struct *task, unsigned long address, bool migration) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_PAGE_FAULT_END, + kfd_smi_event_add(kfd_smi_task_tgid(task), node, KFD_SMI_EVENT_PAGE_FAULT_END, KFD_EVENT_FMT_PAGEFAULT_END(ktime_get_boottime_ns(), kfd_smi_task_to_pid(task), address, node->id, migration ? 'M' : 'U')); @@ -304,7 +314,7 @@ void kfd_smi_event_migration_start(struct kfd_node *node, struct task_struct *ta uint32_t prefetch_loc, uint32_t preferred_loc, uint32_t trigger) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_MIGRATE_START, + kfd_smi_event_add(kfd_smi_task_tgid(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)); @@ -315,7 +325,7 @@ void kfd_smi_event_migration_end(struct kfd_node *node, struct task_struct *task uint32_t from, uint32_t to, uint32_t trigger, int error_code) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_MIGRATE_END, + kfd_smi_event_add(kfd_smi_task_tgid(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)); @@ -324,14 +334,14 @@ void kfd_smi_event_migration_end(struct kfd_node *node, struct task_struct *task void kfd_smi_event_queue_eviction(struct kfd_node *node, struct task_struct *task, uint32_t trigger) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_QUEUE_EVICTION, + kfd_smi_event_add(kfd_smi_task_tgid(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, struct task_struct *task) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_QUEUE_RESTORE, + kfd_smi_event_add(kfd_smi_task_tgid(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')); } @@ -348,7 +358,7 @@ 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, pdd->dev, + kfd_smi_event_add(kfd_smi_task_tgid(p->lead_thread), pdd->dev, KFD_SMI_EVENT_QUEUE_RESTORE, KFD_EVENT_FMT_QUEUE_RESTORE(ktime_get_boottime_ns(), kfd_smi_task_to_pid(p->lead_thread), @@ -361,7 +371,7 @@ void kfd_smi_event_unmap_from_gpu(struct kfd_node *node, struct task_struct *tas unsigned long address, unsigned long last, uint32_t trigger) { - kfd_smi_event_add(task, node, KFD_SMI_EVENT_UNMAP_FROM_GPU, + kfd_smi_event_add(kfd_smi_task_tgid(task), node, KFD_SMI_EVENT_UNMAP_FROM_GPU, KFD_EVENT_FMT_UNMAP_FROM_GPU(ktime_get_boottime_ns(), kfd_smi_task_to_pid(task), address, last - address + 1, node->id, trigger)); @@ -379,7 +389,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(NULL, pdd->dev, + kfd_smi_event_add(kfd_smi_task_tgid(pdd->process->lead_thread), pdd->dev, start ? KFD_SMI_EVENT_PROCESS_START : KFD_SMI_EVENT_PROCESS_END, KFD_EVENT_FMT_PROCESS(task_info->task.pid, @@ -408,7 +418,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 = kfd_smi_task_to_pid(current); + client->tgid = kfd_smi_task_tgid(current); client->suser = capable(CAP_SYS_ADMIN); spin_lock(&dev->smi_lock); From c1584e10a7deb655438f21867b2b7a356a9aaf9f Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 15 Oct 2026 22:59:25 +0800 Subject: [PATCH 47/52] drm/amdgpu: drop the gang leader reference when freeing gang jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every job of a gang except the leader holds a reference to the scheduled fence of the leader in job->gang_submit. Only amdgpu_job_free(), used for jobs which never ran, drops it, so for every gang submission which completes the scheduler fence of the leader, and with it its hardware fence, is leaked. amdgpu_job_free() in turn checks for the leader only after drm_sched_job_cleanup() cleared the scheduler fence, so a leader freed there drops a reference it never took. On navi31, 100 submissions of a GFX + COMPUTE gang grow the slab cache drm_sched_fence is merged into by 60 to 180 objects each time, while 100 GFX only submissions don't. Use amdgpu_job_free() for the jobs the scheduler frees too, and check for the gang leader there before drm_sched_job_cleanup() clears the scheduler fence. As the scheduler clears the entity of the jobs it picked up, check for the scheduler fence instead to tell whether the job needs cleaning up. Keep freeing the remaining IBs with amdgpu_job_free_resources() after drm_sched_job_cleanup(), so that it frees them with the hardware fence or right away and not with the scheduler fence, which nobody signals for jobs aborted after drm_sched_job_arm(), like a CS returning -EAGAIN. v2: unify amdgpu_job_free() and amdgpu_job_free_cb() (Christian), don't free the remaining IBs with the scheduler fence v3: drop the comments (Christian) v4: free the IBs with amdgpu_job_free_resources() again (Christian) Fixes: 68ce8b242242 ("drm/amdgpu: add gang submit backend v2") Fixes: f7d66fb2ea43 ("drm/amdgpu: cleanup scheduler job initialization v2") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit df4d6d404d44bbc8fc850bc7f9f136a25dabf180) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index 9cc210198fa4..8689a3c933b2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -313,22 +313,7 @@ void amdgpu_job_free_resources(struct amdgpu_job *job) static void amdgpu_job_free_cb(struct drm_sched_job *s_job) { - struct amdgpu_job *job = to_amdgpu_job(s_job); - - drm_sched_job_cleanup(s_job); - - amdgpu_sync_free(&job->explicit_sync); - - if (dma_fence_was_initialized(&job->hw_fence->base)) - dma_fence_put(&job->hw_fence->base); - else - kfree(job->hw_fence); - if (dma_fence_was_initialized(&job->hw_vm_fence->base)) - dma_fence_put(&job->hw_vm_fence->base); - else - kfree(job->hw_vm_fence); - - kfree(job); + amdgpu_job_free(to_amdgpu_job(s_job)); } void amdgpu_job_set_gang_leader(struct amdgpu_job *job, @@ -349,13 +334,14 @@ void amdgpu_job_set_gang_leader(struct amdgpu_job *job, void amdgpu_job_free(struct amdgpu_job *job) { - if (job->base.entity) + if (job->gang_submit != &job->base.s_fence->scheduled) + dma_fence_put(job->gang_submit); + + if (job->base.s_fence) drm_sched_job_cleanup(&job->base); amdgpu_job_free_resources(job); amdgpu_sync_free(&job->explicit_sync); - if (job->gang_submit != &job->base.s_fence->scheduled) - dma_fence_put(job->gang_submit); if (dma_fence_was_initialized(&job->hw_fence->base)) dma_fence_put(&job->hw_fence->base); From 8f3ecda0ab42fb5ba7310fe848013d3eff31b416 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 12 Oct 2026 12:19:15 +0800 Subject: [PATCH 48/52] drm/amdkfd: evict the queues only once for CRIU PROCESS_INFO criu_process_info() evicts the queues of the process on every call, and each kfd_process_evict_queues() increments qpd->evicted, but CRIU UNPAUSE restores them only once. After PROCESS_INFO twice and UNPAUSE the queues stay evicted for good. On navi31, a barrier-AND packet on an HSA queue completes after PROCESS_INFO + UNPAUSE, but times out after PROCESS_INFO, PROCESS_INFO, UNPAUSE. The error path has a related problem: when kfd_process_evict_queues() itself fails, it has already rolled back its partial eviction, and the kfd_process_restore_queues() in the error path restores once more. Evict only when the queues are not paused yet, and on errors only undo an eviction done by this call. The barrier now completes after any number of PROCESS_INFO calls followed by UNPAUSE. Fixes: cd9f79103003 ("drm/amdkfd: CRIU Implement KFD unpause operation") Reviewed-by: Felix Kuehling Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 4d79c9e3c32c677fcadf5ca10ab81ee0a4b81f99) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index cad60ccdb216..d65ae53c81a5 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -2876,6 +2876,7 @@ static int criu_process_info(struct file *filep, struct kfd_process *p, struct kfd_ioctl_criu_args *args) { + bool paused_here = false; int ret = 0; mutex_lock(&p->mutex); @@ -2886,11 +2887,15 @@ static int criu_process_info(struct file *filep, goto err_unlock; } - ret = kfd_process_evict_queues(p, KFD_QUEUE_EVICTION_CRIU_CHECKPOINT); - if (ret) - goto err_unlock; + /* UNPAUSE restores the queues once, so evict them only once */ + if (!p->queues_paused) { + ret = kfd_process_evict_queues(p, KFD_QUEUE_EVICTION_CRIU_CHECKPOINT); + if (ret) + goto err_unlock; - p->queues_paused = true; + p->queues_paused = true; + paused_here = true; + } args->pid = task_pid_nr_ns(p->lead_thread, task_active_pid_ns(p->lead_thread)); @@ -2905,7 +2910,7 @@ static int criu_process_info(struct file *filep, args->priv_data_size); err_unlock: - if (ret) { + if (ret && paused_here) { kfd_process_restore_queues(p); p->queues_paused = false; } From 6a1dbb006056ad1ce6df0d6d5f2155767c20f410 Mon Sep 17 00:00:00 2001 From: Harkirat Gill Date: Wed, 21 Oct 2026 10:06:36 -0400 Subject: [PATCH 49/52] drm/amdgpu: re-enable GFXOFF for compute on GFX12 Commit 2affe2bbc997 ("drm/amdgpu: disable gfxoff with the compute workload on gfx12") disabled GFXOFF whenever a user-mode compute queue exists, as a workaround for an OpenCL test failure. The MES firmware issue behind that failure has since been addressed, so the workaround is no longer required. Without this change, idle inference servers that hold a context for hours never let the graphics block power down, costing roughly 3-4% reported GPU activity and ~3 W per GPU at idle. Measured on gfx1200 with an idle HIP process holding one stream and two resident KFD queues: before: VDDGFX 695 mV, GPU load 2%, ~9-10 W after: VDDGFX 35 mV, GPU load 0%, ~8 W (idle baseline) The resident queue count is unchanged in both cases; only GFXOFF availability differs. The ocltst runtime suite (40 tests) passes with and without this change. Fixes: 2affe2bbc997 ("drm/amdgpu: disable gfxoff with the compute workload on gfx12") Signed-off-by: Harkirat Gill Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit f6d97ecb68b8623da2f83997a9c8c57a1a9649f9) --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 3391e8c77f13..53dde87f97f5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -768,9 +768,8 @@ int amdgpu_amdkfd_submit_ib(struct amdgpu_device *adev, void amdgpu_amdkfd_set_compute_idle(struct amdgpu_device *adev, bool idle) { enum amd_powergating_state state = idle ? AMD_PG_STATE_GATE : AMD_PG_STATE_UNGATE; - if ((IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)) == 11 && - ((adev->mes.kiq_version & AMDGPU_MES_VERSION_MASK) <= 64)) || - (IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)) == 12)) { + if (IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)) == 11 && + ((adev->mes.kiq_version & AMDGPU_MES_VERSION_MASK) <= 64)) { pr_debug("GFXOFF is %s\n", idle ? "enabled" : "disabled"); amdgpu_gfx_off_ctrl(adev, idle); } else if ((IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0)) == 9) && From e9ddfb93fafa50f92165d2b48c7728720999b117 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Mon, 5 Oct 2026 16:22:39 -0400 Subject: [PATCH 50/52] drm/amd/display: Poll passive DVI sinks for EDID readiness A passive DP-DVI adapter is identified when the adapter DDC probe does not respond, but the downstream monitor's EDID EEPROM may still need time to become available after resume. Poll the downstream DDC address for passive DP-DVI links before reading and parsing the EDID. Keep the eDP detection optimization without adding a generic EDID retry delay or a deferred connector reprobe. Revert commit 3001d2073d65 ("drm/amd/display: Exit IPS before connector detection on resume") and rely on targeted passive DP-DVI EDID readiness polling instead of the generic pre-detection IPS exit. Fixes: d1d51519bc3b ("drm/amd/display: Skip eDP detection when no sink") Reviewed-by: Roman Li Signed-off-by: Fangzhi Zuo Signed-off-by: Tom Chung Tested-by: Karthikeyan B Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 8297e1c724733fc59027b717df73680eaf03dd56) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 ---- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 22 +++++++++++++++++++ 2 files changed, 22 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 caa5edcf1bc3..0ea45ab12119 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -2021,10 +2021,6 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) /* On resume we need to rewrite the MSTM control bits to enable MST*/ s3_handle_mst(ddev, false); - /* Exit IPS before the detection loop's first AUX/DDC access. */ - scoped_guard(mutex, &dm->dc_lock) - dc_exit_ips_for_hw_access(dm->dc); - /* Do detection*/ drm_connector_list_iter_begin(ddev, &iter); drm_for_each_connector_iter(connector, &iter) { 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 6febc15ebc2b..41e52c54156e 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 @@ -1244,6 +1244,26 @@ void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hd } EXPORT_IF_KUNIT(populate_hdmi_info_from_connector); +static void dm_helpers_wait_for_edid_ready(struct dc_link *link, + struct i2c_adapter *ddc) +{ + const unsigned int retry_count = 50; + const unsigned int retry_interval_ms = 20; + unsigned int retry; + + if (!ddc || link->dpcd_caps.dongle_type != + DISPLAY_DONGLE_DP_DVI_DONGLE) + return; + + for (retry = 0; retry < retry_count; retry++) { + if (drm_probe_ddc(ddc)) + return; + + if (retry + 1 < retry_count) + msleep(retry_interval_ms); + } +} + enum dc_edid_status dm_helpers_read_local_edid( struct dc_context *ctx, struct dc_link *link, @@ -1268,6 +1288,8 @@ enum dc_edid_status dm_helpers_read_local_edid( if (link->dc->hwss.prepare_ddc) link->dc->hwss.prepare_ddc(link); + dm_helpers_wait_for_edid_ready(link, ddc); + /* some dongles read edid incorrectly the first time, * do check sum and retry to make sure read correct edid. */ From 0d7ee3341609151ceb6071e85e102e9b1806b6dc Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Sat, 10 Oct 2026 11:01:35 -0400 Subject: [PATCH 51/52] drm/amd/display: Cap DSC bpp for USB4 DP tunnels On pre-DCN4.2 platforms, USB4 DP bandwidth allocation is unavailable. SST and MST streams are sized against their native DP link bandwidth even when multiple DP tunnels share the host router. A high-bandwidth DSC stream can consume too much shared tunnel capacity and cause intermittent display corruption when another tunnel is active. Cap the DSC target at 8 bpp for USB4 DPIA SST and MST streams when bandwidth allocation is unavailable. For MST, cap the DSC fairness range while preserving its per-link allocation. When bandwidth allocation is enabled on DCN4.2, retain forced DSC and the existing minimum-bpp behavior required to fit the tunnel grant. This is a follow-up to commit c30b9813bce1 ("drm/amd/display: Force DSC to 8bpp for SST DP tunneling over USB4") and commit cd53dcd2a7d7 ("drm/amd/display: Force DSC to 8bpp for MST DP tunneling over USB4"). Reviewed-by: Ivan Lipski Signed-off-by: Fangzhi Zuo Signed-off-by: Tom Chung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit e2bd5d908a89252ad4ee11a3dbc9fd57c257accd) --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 6 +++--- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 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 2b81396bd06e..ef28d2143b52 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 @@ -1289,10 +1289,10 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link, dc_link_get_link_cap(aconnector->dc_link)); - if (aconnector->dc_link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA && - aconnector->dc_link->dpia_bw_alloc_config.bw_alloc_enabled) { + if (aconnector->dc_link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA) { dsc_options.max_target_bpp_limit_override_x16 = 8 * 16; - dsc_options.force_dsc_when_not_needed = true; + if (aconnector->dc_link->dpia_bw_alloc_config.bw_alloc_enabled) + dsc_options.force_dsc_when_not_needed = true; } /* Set DSC policy according to dsc_clock_en */ 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 ee3f3df6bb8d..5697645ef7cc 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 @@ -1342,6 +1342,8 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_commit *state, DRM_DEBUG_DRIVER("%s: MST_DSC Try to set up params from %d streams\n", __func__, dc_state->stream_count); for (i = 0; i < dc_state->stream_count; i++) { struct dc_dsc_policy dsc_policy = {0}; + u32 min_target_bpp_x16; + u32 max_target_bpp_x16; stream = dc_state->streams[i]; @@ -1380,7 +1382,15 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_commit *state, dc_link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA && dc_link->dpia_bw_alloc_config.bw_alloc_enabled) params[count].clock_force_enable = DSC_CLK_FORCE_ENABLE; + dc_dsc_get_policy_for_timing(params[count].timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); + min_target_bpp_x16 = dsc_policy.min_target_bpp * 16; + max_target_bpp_x16 = dsc_policy.max_target_bpp * 16; + if (dc_link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA) { + max_target_bpp_x16 = min_t(u32, max_target_bpp_x16, 8 * 16); + if (dc_link->dpia_bw_alloc_config.bw_alloc_enabled) + max_target_bpp_x16 = min_target_bpp_x16; + } is_frl_endpoint_present = get_conv_frl_bw(aconnector, &frl_conv_bw_in_kbps, &frl_conv_dsc_bw_in_kbps); if (stream->sink->dsc_caps.dsc_dec_caps.is_dsc_supported && is_frl_endpoint_present && @@ -1390,11 +1400,8 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_commit *state, if (!dc_dsc_compute_bandwidth_range( stream->sink->ctx->dc->res_pool->dscs[0], stream->sink->ctx->dc->debug.dsc_min_slice_height_override, - dsc_policy.min_target_bpp * 16, - (dc_link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA && - dc_link->dpia_bw_alloc_config.bw_alloc_enabled) ? - dsc_policy.min_target_bpp * 16 : - dsc_policy.max_target_bpp * 16, + min_target_bpp_x16, + max_target_bpp_x16, &stream->sink->dsc_caps.dsc_dec_caps, &stream->timing, dc_link_get_highest_encoding_format(dc_link), From fbb201890156e04b062c4488d1b500de52367684 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Tue, 6 Oct 2026 21:06:05 -0400 Subject: [PATCH 52/52] drm/amd/display: refresh cached HDMI FreeSync state When an HDMI sink re-reports an EDID without an AMD VSDB, get_amd_vsdb() returns false but the connector's cached vsdb_info was previously updated only on success. A previous FreeSync-capable EDID could therefore leave freesync_supported set and keep selecting the FreeSync SPD path instead of the HDMI VRR VTEM path. Refresh the cached VSDB record for every HDMI EDID update so a missing AMD VSDB clears stale FreeSync state. Keep the existing SPD/VTEM packet separation and protocol-selection logic unchanged. Fixes: 123e9515f7e0 ("drm/amd/display: Send HDMI VRR metadata in VTEM info packet slot") Reviewed-by: IVAN LIPSKI Signed-off-by: Fangzhi Zuo Signed-off-by: Tom Chung Tested-by: PeiHsin Yang Signed-off-by: Alex Deucher (cherry picked from commit 732a165000944e618ed38769922daf11b0fe35eb) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index ef28d2143b52..b498fc5fbb14 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 @@ -3780,10 +3780,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, parse_edid_displayid_vrr(connector, edid); has_vsdb = get_amd_vsdb(amdgpu_dm_connector, &vsdb_info) != 0; + amdgpu_dm_connector->vsdb_info = vsdb_info; if (has_vsdb) { - amdgpu_dm_connector->vsdb_info = vsdb_info; - /* copy refresh rate info as long as VSDB advertises FreeSync */ if (vsdb_info.freesync_supported) { amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz;