From d28859a47058e3cd57f5bfa886067112894010fd Mon Sep 17 00:00:00 2001 From: Aurabindo Pillai Date: Wed, 4 Feb 2026 15:15:20 -0500 Subject: [PATCH 01/58] drm/amd/display: Add KUnit test for CRC function DM CRC parsing functions are an easy candidate for exploring the use of KUnit unit-testing frameworks. Add a few tests for the same. The test file and .kunitconfig are placed under amdgpu_dm/tests/ to follow the convention of keeping test code separate from production sources. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Harry Wentland Signed-off-by: Aurabindo Pillai Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/Kconfig | 12 ++ .../gpu/drm/amd/display/amdgpu_dm/Makefile | 5 + .../drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 17 ++- .../drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h | 7 + .../amdgpu_dm/amdgpu_dm_kunit_helpers.h | 19 +++ .../amd/display/amdgpu_dm/tests/.kunitconfig | 14 ++ .../drm/amd/display/amdgpu_dm/tests/Makefile | 7 + .../amdgpu_dm/tests/amdgpu_dm_crc_test.c | 121 ++++++++++++++++++ 8 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c diff --git a/drivers/gpu/drm/amd/display/Kconfig b/drivers/gpu/drm/amd/display/Kconfig index abd3b6564373..38323e574c6b 100644 --- a/drivers/gpu/drm/amd/display/Kconfig +++ b/drivers/gpu/drm/amd/display/Kconfig @@ -56,4 +56,16 @@ config DRM_AMD_SECURE_DISPLAY This option enables the calculation of crc of specific region via debugfs. Cooperate with specific DMCU FW. +config DRM_AMD_DC_KUNIT_TEST + tristate "KUnit tests for the AMD DC display driver" if !KUNIT_ALL_TESTS + depends on DRM_AMD_DC && KUNIT && DEBUG_FS + default KUNIT_ALL_TESTS + help + This option enables KUnit tests for the AMD Display Core driver. + These tests validate core functionality like CRC source parsing, + color management, and other utility functions. + + For more information on KUnit and unit tests in general, please + refer to the KUnit documentation in Documentation/dev-tools/kunit/. + endmenu diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 89350aa9ca7e..914f89af047c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -59,3 +59,8 @@ AMDGPU_DM = $(addprefix $(AMDDALPATH)/amdgpu_dm/,$(AMDGPUDM)) AMD_DISPLAY_FILES += $(AMDGPU_DM) endif + +# KUnit tests as separate module +ifneq ($(CONFIG_DRM_AMD_DC_KUNIT_TEST),) +obj-y += $(AMDDALPATH)/amdgpu_dm/tests/ +endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 5adbb0f6a0c8..88f7cfea5624 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -33,6 +33,7 @@ #include "amdgpu_securedisplay.h" #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" +#include "amdgpu_dm_kunit_helpers.h" static const char *const pipe_crc_sources[] = { "none", @@ -43,7 +44,8 @@ static const char *const pipe_crc_sources[] = { "auto", }; -static enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source) +STATIC_IFN_KUNIT +enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source) { if (!source || !strcmp(source, "none")) return AMDGPU_DM_PIPE_CRC_SOURCE_NONE; @@ -58,25 +60,32 @@ static enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source) return AMDGPU_DM_PIPE_CRC_SOURCE_INVALID; } +EXPORT_IF_KUNIT(dm_parse_crc_source); -static bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src) +STATIC_IFN_KUNIT +bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src) { return (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER); } +EXPORT_IF_KUNIT(dm_is_crc_source_crtc); -static bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src) +STATIC_IFN_KUNIT +bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src) { return (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER); } +EXPORT_IF_KUNIT(dm_is_crc_source_dprx); -static bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src) +STATIC_IFN_KUNIT +bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src) { return (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_NONE); } +EXPORT_IF_KUNIT(dm_need_crc_dither); const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc, size_t *count) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h index 8538513ea879..76731ee44e13 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h @@ -148,4 +148,11 @@ void amdgpu_dm_crtc_secure_display_create_contexts(struct amdgpu_device *adev); #define amdgpu_dm_crtc_secure_display_create_contexts(x) #endif +#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source); +bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src); +bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src); +bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src); +#endif + #endif /* AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h new file mode 100644 index 000000000000..4b2864375105 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef AMDGPU_DM_KUNIT_HELPERS_H +#define AMDGPU_DM_KUNIT_HELPERS_H + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#define STATIC_IFN_KUNIT +#define INLINE_IFN_KUNIT inline +#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) +#else +#define STATIC_IFN_KUNIT static +#define INLINE_IFN_KUNIT +#define EXPORT_IF_KUNIT(symbol) +#endif + +#endif /* AMDGPU_DM_KUNIT_HELPERS_H */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig new file mode 100644 index 000000000000..36676326ade4 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig @@ -0,0 +1,14 @@ +CONFIG_KUNIT=y +CONFIG_PCI=y +CONFIG_DRM=y +CONFIG_DRM_AMDGPU=y +CONFIG_DRM_AMD_DC=y +CONFIG_DEBUG_FS=y +CONFIG_DRM_AMD_DC_KUNIT_TEST=y +CONFIG_FW_LOADER=y +CONFIG_DRM_KMS_HELPER=y +CONFIG_DRM_TTM=y +CONFIG_HWMON=y +CONFIG_I2C=y +CONFIG_POWER_SUPPLY=y +CONFIG_CRC16=y diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile new file mode 100644 index 000000000000..1238d8832fa3 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for amdgpu_dm KUnit tests. + +ccflags-y += -I$(src)/.. + +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c new file mode 100644 index 000000000000..bba8b1a8fa1c --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_crc.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "amdgpu_dm_crc.h" + +static void dm_test_parse_crc_source_none(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source("none")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source(NULL)); +} + +static void dm_test_parse_crc_source_crtc(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, dm_parse_crc_source("crtc")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, dm_parse_crc_source("auto")); +} + +static void dm_test_parse_crc_source_dprx(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, dm_parse_crc_source("dprx")); +} + +static void dm_test_parse_crc_source_crtc_dither(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER, + dm_parse_crc_source("crtc dither")); +} + +static void dm_test_parse_crc_source_dprx_dither(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER, + dm_parse_crc_source("dprx dither")); +} + +static void dm_test_parse_crc_source_invalid(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID, + dm_parse_crc_source("invalid")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID, + dm_parse_crc_source("unknown")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID, + dm_parse_crc_source("")); +} + +static void dm_test_is_crc_source_crtc(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static void dm_test_is_crc_source_dprx(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static void dm_test_need_crc_dither(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static void dm_test_is_valid_crc_source(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_MAX)); + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static struct kunit_case dm_crc_test_cases[] = { + KUNIT_CASE(dm_test_parse_crc_source_none), + KUNIT_CASE(dm_test_parse_crc_source_crtc), + KUNIT_CASE(dm_test_parse_crc_source_dprx), + KUNIT_CASE(dm_test_parse_crc_source_crtc_dither), + KUNIT_CASE(dm_test_parse_crc_source_dprx_dither), + KUNIT_CASE(dm_test_parse_crc_source_invalid), + KUNIT_CASE(dm_test_is_crc_source_crtc), + KUNIT_CASE(dm_test_is_crc_source_dprx), + KUNIT_CASE(dm_test_need_crc_dither), + KUNIT_CASE(dm_test_is_valid_crc_source), + {} +}; + +static struct kunit_suite dm_crc_test_suite = { + .name = "amdgpu_dm_crc", + .test_cases = dm_crc_test_cases, +}; + +kunit_test_suite(dm_crc_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_crc"); +MODULE_AUTHOR("AMD"); From 7a5371e20884cd2b8d583f2879da7679648ff440 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 21 Apr 2026 20:57:34 -0600 Subject: [PATCH 02/58] drm/amd/display: Add KUnit test for HDCP process_output Expose process_output() as non-static when CONFIG_DRM_AMD_DC_KUNIT_TEST is enabled and add KUnit tests exercising its full branch logic: - property_validate_dwork is always enqueued (delay=0) - callback_dwork is scheduled when callback_needed is set - callback_dwork is cancelled when callback_stop is set - watchdog_timer_dwork is scheduled when watchdog_timer_needed is set - watchdog_timer_dwork is cancelled when watchdog_timer_stop is set - Both dworks are scheduled independently when both flags are set Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Harry Wentland Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 - .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 5 +- .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 13 +- .../drm/amd/display/amdgpu_dm/tests/Makefile | 5 + .../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 175 ++++++++++++++++++ 5 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 74f700fbeb6f..c4672ade22d5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -45,8 +45,6 @@ * in amdgpu_dm_kms.h file */ -#define AMDGPU_DM_MAX_DISPLAY_INDEX 31 - #define AMDGPU_DM_MAX_CRTC 6 #define AMDGPU_DM_MAX_NUM_EDP 2 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index eb73bbf8f411..adc5cd1007f2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -31,6 +31,7 @@ #include "dm_helpers.h" #include #include "hdcp_psp.h" +#include "amdgpu_dm_kunit_helpers.h" /* * If the SRM version being loaded is less than or equal to the @@ -158,7 +159,8 @@ static int psp_set_srm(struct psp_context *psp, return 0; } -static void process_output(struct hdcp_workqueue *hdcp_work) +STATIC_IFN_KUNIT +void process_output(struct hdcp_workqueue *hdcp_work) { struct mod_hdcp_output output = hdcp_work->output; @@ -178,6 +180,7 @@ static void process_output(struct hdcp_workqueue *hdcp_work) schedule_delayed_work(&hdcp_work->property_validate_dwork, msecs_to_jiffies(0)); } +EXPORT_IF_KUNIT(process_output); static void link_lock(struct hdcp_workqueue *work, bool lock) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 4faa344f196e..4bb072cfac1e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -31,12 +31,19 @@ #include "hdcp.h" #include "dc.h" #include "dm_cp_psp.h" -#include "amdgpu.h" + +/* + * Minimal declarations needed by this header. + * Full amdgpu/DM definitions come from amdgpu_dm.h included by each .c file. + */ +#define AMDGPU_DM_MAX_DISPLAY_INDEX 31 +struct amdgpu_dm_connector; struct mod_hdcp; struct mod_hdcp_link; struct mod_hdcp_display; struct cp_psp; +struct amdgpu_device; struct hdcp_workqueue { struct work_struct cpirq_work; @@ -87,4 +94,8 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *work); struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct cp_psp *cp_psp, struct dc *dc); +#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +void process_output(struct hdcp_workqueue *hdcp_work); +#endif + #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 1238d8832fa3..9669ea79a666 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -3,5 +3,10 @@ # Makefile for amdgpu_dm KUnit tests. ccflags-y += -I$(src)/.. +ccflags-y += -I$(src)/../.. +ccflags-y += -I$(src)/../../include +ccflags-y += -I$(src)/../../modules/inc +ccflags-y += -I$(src)/../../dc obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c new file mode 100644 index 000000000000..d03b606d27bc --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_hdcp.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "amdgpu_dm_hdcp.h" + +static void dummy_work_fn(struct work_struct *work) {} + +/* Tests for process_output() */ + +/* + * Helper: allocate and initialise a minimal hdcp_workqueue sufficient for + * process_output() testing. Only the three delayed works accessed by + * process_output() are initialised; everything else is zeroed. + */ +static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/* + * process_output() always schedules property_validate_dwork with delay=0, + * which queues the work item directly (bypassing the timer). Use + * work_pending() rather than delayed_work_pending() to detect this. + */ +static void dm_test_process_output_property_validate_always_scheduled(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + /* No flags set: only property_validate_dwork should be enqueued */ + process_output(work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.callback_needed=true must schedule callback_dwork. + */ +static void dm_test_process_output_callback_needed(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + work->output.callback_needed = true; + work->output.callback_delay = 500; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.callback_stop=true must cancel a previously scheduled callback_dwork. + */ +static void dm_test_process_output_callback_stop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + /* Pre-schedule callback_dwork with a long delay so it won't fire. */ + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + work->output.callback_stop = true; + + process_output(work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.watchdog_timer_needed=true must schedule watchdog_timer_dwork. + */ +static void dm_test_process_output_watchdog_needed(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 1000; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.watchdog_timer_stop=true must cancel a previously scheduled + * watchdog_timer_dwork. + */ +static void dm_test_process_output_watchdog_stop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + /* Pre-schedule watchdog_timer_dwork with a long delay. */ + schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + work->output.watchdog_timer_stop = true; + + process_output(work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * Both callback_needed and watchdog_timer_needed set: both dworks are + * scheduled independently. + */ +static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + work->output.callback_needed = true; + work->output.callback_delay = 200; + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 800; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} +/* End of tests for process_output() */ + +static struct kunit_case dm_hdcp_test_cases[] = { + KUNIT_CASE(dm_test_process_output_property_validate_always_scheduled), + KUNIT_CASE(dm_test_process_output_callback_needed), + KUNIT_CASE(dm_test_process_output_callback_stop), + KUNIT_CASE(dm_test_process_output_watchdog_needed), + KUNIT_CASE(dm_test_process_output_watchdog_stop), + KUNIT_CASE(dm_test_process_output_callback_and_watchdog_needed), + {} +}; + +static struct kunit_suite dm_hdcp_test_suite = { + .name = "amdgpu_dm_hdcp", + .test_cases = dm_hdcp_test_cases, +}; + +kunit_test_suite(dm_hdcp_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_hdcp"); +MODULE_AUTHOR("AMD"); From 77f3ed873c5985bb326ab8a4fd8d243e957e962a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 22 Apr 2026 11:37:19 -0600 Subject: [PATCH 03/58] drm/amd/display: Add KUnit test for colorop TF bitmasks Add KUnit tests that verify the three supported transfer function bitmask constants exported by amdgpu_dm_colorop.c: amdgpu_dm_supported_degam_tfs, amdgpu_dm_supported_shaper_tfs, and amdgpu_dm_supported_blnd_tfs. Each bitmask is tested for presence of each expected curve flag and absence of any unexpected bits. A cross-check confirms that degam and blnd bitmasks are identical. amdgpu_dm_initialize_default_pipeline() is not tested because it needs a fully initialised drm_plane backed by an amdgpu_device with DC color caps. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Harry Wentland Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 9669ea79a666..e300bcf39835 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -10,3 +10,4 @@ ccflags-y += -I$(src)/../../dc obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c new file mode 100644 index 000000000000..8bdebcaf42b2 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_colorop.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "amdgpu_dm_colorop.h" + +/* Tests for amdgpu_dm_supported_degam_tfs */ + +static void dm_test_supported_degam_tfs_has_srgb_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF)); +} + +static void dm_test_supported_degam_tfs_has_pq125_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF)); +} + +static void dm_test_supported_degam_tfs_has_bt2020_inv_oetf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF)); +} + +static void dm_test_supported_degam_tfs_has_gamma22_inv(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV)); +} + +static void dm_test_supported_degam_tfs_no_extra_bits(struct kunit *test) +{ + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_degam_tfs, expected); +} + +/* Tests for amdgpu_dm_supported_shaper_tfs */ + +static void dm_test_supported_shaper_tfs_has_srgb_inv_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF)); +} + +static void dm_test_supported_shaper_tfs_has_pq125_inv_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF)); +} + +static void dm_test_supported_shaper_tfs_has_bt2020_oetf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF)); +} + +static void dm_test_supported_shaper_tfs_has_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_GAMMA22)); +} + +static void dm_test_supported_degam_tfs_no_extra_bits(struct kunit *test) +{ + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_shaper_tfs, expected); +} + +/* Tests for amdgpu_dm_supported_blnd_tfs */ + +static void dm_test_supported_blnd_tfs_has_srgb_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF)); +} + +static void dm_test_supported_blnd_tfs_has_pq125_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF)); +} + +static void dm_test_supported_blnd_tfs_has_bt2020_inv_oetf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF)); +} + +static void dm_test_supported_blnd_tfs_has_gamma22_inv(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV)); +} + +static void dm_test_supported_blnd_tfs_no_extra_bits(struct kunit *test) +{ + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_blnd_tfs, expected); +} + +/* degam and blnd should support the same set of EOTF curves */ +static void dm_test_degam_and_blnd_tfs_match(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_degam_tfs, + amdgpu_dm_supported_blnd_tfs); +} + +static struct kunit_case dm_colorop_test_cases[] = { + /* degam TFs */ + KUNIT_CASE(dm_test_supported_degam_tfs_has_srgb_eotf), + KUNIT_CASE(dm_test_supported_degam_tfs_has_pq125_eotf), + KUNIT_CASE(dm_test_supported_degam_tfs_has_bt2020_inv_oetf), + KUNIT_CASE(dm_test_supported_degam_tfs_has_gamma22_inv), + KUNIT_CASE(dm_test_supported_degam_tfs_no_extra_bits), + /* shaper TFs */ + KUNIT_CASE(dm_test_supported_shaper_tfs_has_srgb_inv_eotf), + KUNIT_CASE(dm_test_supported_shaper_tfs_has_pq125_inv_eotf), + KUNIT_CASE(dm_test_supported_shaper_tfs_has_bt2020_oetf), + KUNIT_CASE(dm_test_supported_shaper_tfs_has_gamma22), + KUNIT_CASE(dm_test_supported_shaper_tfs_no_extra_bits), + /* blnd TFs */ + KUNIT_CASE(dm_test_supported_blnd_tfs_has_srgb_eotf), + KUNIT_CASE(dm_test_supported_blnd_tfs_has_pq125_eotf), + KUNIT_CASE(dm_test_supported_blnd_tfs_has_bt2020_inv_oetf), + KUNIT_CASE(dm_test_supported_blnd_tfs_has_gamma22_inv), + KUNIT_CASE(dm_test_supported_blnd_tfs_no_extra_bits), + /* cross-check */ + KUNIT_CASE(dm_test_degam_and_blnd_tfs_match), + {} +}; + +static struct kunit_suite dm_colorop_test_suite = { + .name = "amdgpu_dm_colorop", + .test_cases = dm_colorop_test_cases, +}; + +kunit_test_suite(dm_colorop_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_colorop"); +MODULE_AUTHOR("AMD"); From 647d1fd046527c3eafb4974433cb68b87f2d4e18 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 22 Apr 2026 11:54:33 -0600 Subject: [PATCH 04/58] drm/amd/display: Add KUnit test for color helpers Add KUnit tests for six pure-logic functions in amdgpu_dm_color.c: amdgpu_dm_fixpt_from_s3132, __is_lut_linear, __drm_ctm_to_dc_matrix, __drm_ctm_3x4_to_dc_matrix, amdgpu_tf_to_dc_tf, and amdgpu_colorop_tf_to_dc_tf. Expose these static functions under CONFIG_DRM_AMD_DC_KUNIT_TEST and add a new amdgpu_dm_color.h header with the KUnit-only prototypes. The test file re-declares the dc and amdgpu transfer function enums locally to avoid pulling in the full DC/amdgpu include chain that fails under UML. 26 test cases cover signed-magnitude to two's complement conversion, LUT linearity detection, CTM-to-DC matrix conversion, and transfer function enum mapping. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Harry Wentland Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 65 +- .../amd/display/amdgpu_dm/amdgpu_dm_color.h | 89 ++ .../drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../amdgpu_dm/tests/amdgpu_dm_color_test.c | 1071 +++++++++++++++++ 4 files changed, 1209 insertions(+), 18 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index fa6883ae4dfb..4339e84360a5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -29,9 +29,12 @@ #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_color.h" #include "amdgpu_dm_colorop.h" #include "dc.h" #include "modules/color/color_gamma.h" +#include "amdgpu_dm_kunit_helpers.h" + /** * DOC: overview @@ -157,8 +160,6 @@ * */ -#define MAX_DRM_LUT_VALUE 0xFFFF -#define MAX_DRM_LUT32_VALUE 0xFFFFFFFF #define SDR_WHITE_LEVEL_INIT_VALUE 80 /** @@ -172,7 +173,8 @@ void amdgpu_dm_init_color_mod(void) setup_x_points_distribution(); } -static inline struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) +STATIC_IFN_KUNIT INLINE_IFN_KUNIT +struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) { struct fixed31_32 val; @@ -183,6 +185,7 @@ static inline struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) val.value = x; return val; } +EXPORT_IF_KUNIT(amdgpu_dm_fixpt_from_s3132); #ifdef AMD_PRIVATE_COLOR /* Pre-defined Transfer Functions (TF) @@ -421,12 +424,14 @@ amdgpu_dm_create_color_properties(struct amdgpu_device *adev) * Returns: * DRM LUT or NULL */ -static const struct drm_color_lut * +STATIC_IFN_KUNIT +const struct drm_color_lut * __extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size) { *size = blob ? drm_color_lut_size(blob) : 0; return blob ? (struct drm_color_lut *)blob->data : NULL; } +EXPORT_IF_KUNIT(__extract_blob_lut); /** * __extract_blob_lut32 - Extracts the DRM lut and lut size from a blob. @@ -436,12 +441,14 @@ __extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size) * Returns: * DRM LUT or NULL */ -static const struct drm_color_lut32 * +STATIC_IFN_KUNIT +const struct drm_color_lut32 * __extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size) { *size = blob ? drm_color_lut32_size(blob) : 0; return blob ? (struct drm_color_lut32 *)blob->data : NULL; } +EXPORT_IF_KUNIT(__extract_blob_lut32); /** * __is_lut_linear - check if the given lut is a linear mapping of values @@ -456,7 +463,8 @@ __extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size) * True if the given lut is a linear mapping of values, i.e. it acts like a * bypass LUT. Otherwise, false. */ -static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) +STATIC_IFN_KUNIT +bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) { int i; uint32_t expected; @@ -476,6 +484,7 @@ static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) } return true; } +EXPORT_IF_KUNIT(__is_lut_linear); /** * __drm_lut_to_dc_gamma - convert the drm_color_lut to dc_gamma. @@ -485,7 +494,8 @@ static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) * * The conversion depends on the size of the lut - whether or not it's legacy. */ -static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, +STATIC_IFN_KUNIT +void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, struct dc_gamma *gamma, bool is_legacy) { uint32_t r, g, b; @@ -515,6 +525,7 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, gamma->entries.blue[i] = dc_fixpt_from_fraction(b, MAX_DRM_LUT_VALUE); } } +EXPORT_IF_KUNIT(__drm_lut_to_dc_gamma); /** * __drm_lut32_to_dc_gamma - convert the drm_color_lut to dc_gamma. @@ -523,7 +534,8 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, * * The conversion depends on the size of the lut - whether or not it's legacy. */ -static void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc_gamma *gamma) +STATIC_IFN_KUNIT +void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc_gamma *gamma) { int i; @@ -533,6 +545,7 @@ static void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc gamma->entries.blue[i] = dc_fixpt_from_fraction(lut[i].blue, MAX_DRM_LUT32_VALUE); } } +EXPORT_IF_KUNIT(__drm_lut32_to_dc_gamma); /** * __drm_ctm_to_dc_matrix - converts a DRM CTM to a DC CSC float matrix @@ -541,8 +554,9 @@ static void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc * * The matrix needs to be a 3x4 (12 entry) matrix. */ -static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, - struct fixed31_32 *matrix) +STATIC_IFN_KUNIT +void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + struct fixed31_32 *matrix) { int i; @@ -565,6 +579,7 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, matrix[i] = amdgpu_dm_fixpt_from_s3132(ctm->matrix[i - (i / 4)]); } } +EXPORT_IF_KUNIT(__drm_ctm_to_dc_matrix); /** * __drm_ctm_3x4_to_dc_matrix - converts a DRM CTM 3x4 to a DC CSC float matrix @@ -573,8 +588,9 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, * * The matrix needs to be a 3x4 (12 entry) matrix. */ -static void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, - struct fixed31_32 *matrix) +STATIC_IFN_KUNIT +void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, + struct fixed31_32 *matrix) { int i; @@ -587,6 +603,7 @@ static void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, matrix[i] = amdgpu_dm_fixpt_from_s3132(ctm->matrix[i]); } } +EXPORT_IF_KUNIT(__drm_ctm_3x4_to_dc_matrix); /** * __set_legacy_tf - Calculates the legacy transfer function @@ -851,7 +868,8 @@ static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func return res ? 0 : -ENOMEM; } -static enum dc_transfer_func_predefined +STATIC_IFN_KUNIT +enum dc_transfer_func_predefined amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf) { switch (tf) { @@ -879,8 +897,10 @@ amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf) return TRANSFER_FUNCTION_GAMMA26; } } +EXPORT_IF_KUNIT(amdgpu_tf_to_dc_tf); -static enum dc_transfer_func_predefined +STATIC_IFN_KUNIT +enum dc_transfer_func_predefined amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf) { switch (tf) { @@ -900,8 +920,10 @@ amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf) return TRANSFER_FUNCTION_LINEAR; } } +EXPORT_IF_KUNIT(amdgpu_colorop_tf_to_dc_tf); -static void __to_dc_lut3d_color(struct dc_rgb *rgb, +STATIC_IFN_KUNIT +void __to_dc_lut3d_color(struct dc_rgb *rgb, const struct drm_color_lut lut, int bit_precision) { @@ -909,8 +931,10 @@ static void __to_dc_lut3d_color(struct dc_rgb *rgb, rgb->green = drm_color_lut_extract(lut.green, bit_precision); rgb->blue = drm_color_lut_extract(lut.blue, bit_precision); } +EXPORT_IF_KUNIT(__to_dc_lut3d_color); -static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, +STATIC_IFN_KUNIT +void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, uint32_t lut3d_size, struct tetrahedral_params *params, bool use_tetrahedral_9, @@ -953,8 +977,10 @@ static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, /* lut0 has 1229 points (lut_size/4 + 1) */ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth); } +EXPORT_IF_KUNIT(__drm_3dlut_to_dc_3dlut); -static void __to_dc_lut3d_32_color(struct dc_rgb *rgb, +STATIC_IFN_KUNIT +void __to_dc_lut3d_32_color(struct dc_rgb *rgb, const struct drm_color_lut32 lut, int bit_precision) { @@ -962,8 +988,10 @@ static void __to_dc_lut3d_32_color(struct dc_rgb *rgb, rgb->green = drm_color_lut32_extract(lut.green, bit_precision); rgb->blue = drm_color_lut32_extract(lut.blue, bit_precision); } +EXPORT_IF_KUNIT(__to_dc_lut3d_32_color); -static void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, +STATIC_IFN_KUNIT +void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, uint32_t lut3d_size, struct tetrahedral_params *params, bool use_tetrahedral_9, @@ -1006,6 +1034,7 @@ static void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, /* lut0 has 1229 points (lut_size/4 + 1) */ __to_dc_lut3d_32_color(&lut0[lut_i], lut[i], bit_depth); } +EXPORT_IF_KUNIT(__drm_3dlut32_to_dc_3dlut); /* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream * @drm_lut3d: user 3D LUT diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h new file mode 100644 index 000000000000..5e484359b8a7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_DM_COLOR_H__ +#define __AMDGPU_DM_COLOR_H__ + +#define MAX_DRM_LUT_VALUE 0xFFFF +#define MAX_DRM_LUT32_VALUE 0xFFFFFFFF + +#include + +struct drm_color_lut; +struct drm_color_lut32; +struct drm_color_ctm; +struct drm_color_ctm_3x4; +struct drm_property_blob; +struct dc_gamma; +struct dc_rgb; +struct fixed31_32; +struct tetrahedral_params; + +#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +/* + * Prototypes for functions exposed to KUnit tests. The enum types + * used below (dc_transfer_func_predefined, amdgpu_transfer_function, + * drm_colorop_curve_1d_type) must be defined before this header is + * included — the source file (amdgpu_dm_color.c) ensures this via + * its own includes of dc.h, amdgpu_dm.h, and drm/drm_colorop.h. + */ +struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x); +bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size); +void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, + struct dc_gamma *gamma, bool is_legacy); +void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, + struct dc_gamma *gamma); +void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + struct fixed31_32 *matrix); +void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, + struct fixed31_32 *matrix); +enum dc_transfer_func_predefined +amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf); +enum dc_transfer_func_predefined +amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf); +const struct drm_color_lut * +__extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size); +const struct drm_color_lut32 * +__extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size); +void __to_dc_lut3d_color(struct dc_rgb *rgb, + const struct drm_color_lut lut, + int bit_precision); +void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, + uint32_t lut3d_size, + struct tetrahedral_params *params, + bool use_tetrahedral_9, + int bit_depth); +void __to_dc_lut3d_32_color(struct dc_rgb *rgb, + const struct drm_color_lut32 lut, + int bit_precision); +void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, + uint32_t lut3d_size, + struct tetrahedral_params *params, + bool use_tetrahedral_9, + int bit_depth); +#endif + +#endif /* __AMDGPU_DM_COLOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index e300bcf39835..2b442353beae 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -7,7 +7,9 @@ ccflags-y += -I$(src)/../.. ccflags-y += -I$(src)/../../include ccflags-y += -I$(src)/../../modules/inc ccflags-y += -I$(src)/../../dc +ccflags-y += -I$(src)/../../../amdgpu obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c new file mode 100644 index 000000000000..377fa4342ca1 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_color.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_color.h" + +/* ---- Tests for amdgpu_dm_fixpt_from_s3132 ---- */ + +static void dm_test_fixpt_from_s3132_zero(struct kunit *test) +{ + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(0ULL); + + KUNIT_EXPECT_EQ(test, val.value, 0LL); +} + +static void dm_test_fixpt_from_s3132_one(struct kunit *test) +{ + /* 1.0 in S31.32 signed-magnitude = 1ULL << 32 */ + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(1ULL << 32); + + KUNIT_EXPECT_EQ(test, val.value, (long long)(1ULL << 32)); +} + +static void dm_test_fixpt_from_s3132_negative_one(struct kunit *test) +{ + /* -1.0 in S31.32 signed-magnitude: sign bit set | magnitude 1<<32 */ + __u64 neg_one = (1ULL << 63) | (1ULL << 32); + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(neg_one); + + /* 2's complement of 1.0 is -(1<<32) */ + KUNIT_EXPECT_EQ(test, val.value, -(long long)(1ULL << 32)); +} + +static void dm_test_fixpt_from_s3132_half(struct kunit *test) +{ + /* 0.5 in S31.32 = 1ULL << 31 */ + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(1ULL << 31); + + KUNIT_EXPECT_EQ(test, val.value, (long long)(1ULL << 31)); +} + +static void dm_test_fixpt_from_s3132_neg_half(struct kunit *test) +{ + __u64 neg_half = (1ULL << 63) | (1ULL << 31); + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(neg_half); + + KUNIT_EXPECT_EQ(test, val.value, -(long long)(1ULL << 31)); +} + +/* ---- Tests for __is_lut_linear ---- */ + +static void dm_test_is_lut_linear_with_linear_lut(struct kunit *test) +{ + const uint32_t size = 256; + struct drm_color_lut *lut; + int i; + + lut = kunit_kcalloc(test, size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, lut); + + for (i = 0; i < size; i++) { + uint16_t val = (uint16_t)(i * MAX_DRM_LUT_VALUE / (size - 1)); + + lut[i].red = val; + lut[i].green = val; + lut[i].blue = val; + } + + KUNIT_EXPECT_TRUE(test, __is_lut_linear(lut, size)); +} + +static void dm_test_is_lut_linear_with_nonlinear_lut(struct kunit *test) +{ + const uint32_t size = 256; + struct drm_color_lut *lut; + int i; + + lut = kunit_kcalloc(test, size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, lut); + + /* Fill with all-max values: clearly non-linear */ + for (i = 0; i < size; i++) { + lut[i].red = MAX_DRM_LUT_VALUE; + lut[i].green = MAX_DRM_LUT_VALUE; + lut[i].blue = MAX_DRM_LUT_VALUE; + } + + KUNIT_EXPECT_FALSE(test, __is_lut_linear(lut, size)); +} + +static void dm_test_is_lut_linear_rgb_mismatch(struct kunit *test) +{ + const uint32_t size = 256; + struct drm_color_lut *lut; + int i; + + lut = kunit_kcalloc(test, size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, lut); + + for (i = 0; i < size; i++) { + uint16_t val = (uint16_t)(i * MAX_DRM_LUT_VALUE / (size - 1)); + + lut[i].red = val; + lut[i].green = val; + lut[i].blue = val; + } + + /* Introduce R/G mismatch at entry 128 */ + lut[128].red = lut[128].green + 10; + + KUNIT_EXPECT_FALSE(test, __is_lut_linear(lut, size)); +} + +/* ---- Tests for __drm_ctm_to_dc_matrix ---- */ + +static void dm_test_drm_ctm_to_dc_matrix_identity(struct kunit *test) +{ + struct drm_color_ctm ctm; + struct fixed31_32 matrix[12]; + int i; + long long one = 1LL << 32; + + memset(&ctm, 0, sizeof(ctm)); + + /* Identity 3x3 in S31.32 signed-magnitude: diagonal = 1.0 */ + ctm.matrix[0] = 1ULL << 32; /* [0][0] */ + ctm.matrix[4] = 1ULL << 32; /* [1][1] */ + ctm.matrix[8] = 1ULL << 32; /* [2][2] */ + + __drm_ctm_to_dc_matrix(&ctm, matrix); + + /* Expect 3x4 identity: diag=1.0, 4th column=0, off-diag=0 */ + for (i = 0; i < 12; i++) { + if (i == 0 || i == 5 || i == 10) + KUNIT_EXPECT_EQ(test, matrix[i].value, one); + else + KUNIT_EXPECT_EQ(test, matrix[i].value, 0LL); + } +} + +static void dm_test_drm_ctm_to_dc_matrix_negative(struct kunit *test) +{ + struct drm_color_ctm ctm; + struct fixed31_32 matrix[12]; + long long neg_one = -(1LL << 32); + + memset(&ctm, 0, sizeof(ctm)); + + /* -1.0 in S31.32 signed-magnitude */ + ctm.matrix[0] = (1ULL << 63) | (1ULL << 32); + + __drm_ctm_to_dc_matrix(&ctm, matrix); + + KUNIT_EXPECT_EQ(test, matrix[0].value, neg_one); +} + +static void dm_test_drm_ctm_to_dc_matrix_4th_col_zero(struct kunit *test) +{ + struct drm_color_ctm ctm; + struct fixed31_32 matrix[12]; + + memset(&ctm, 0, sizeof(ctm)); + + /* Fill all 9 CTM entries with 1.0 */ + for (int i = 0; i < 9; i++) + ctm.matrix[i] = 1ULL << 32; + + __drm_ctm_to_dc_matrix(&ctm, matrix); + + /* 4th column (indices 3, 7, 11) must always be zero */ + KUNIT_EXPECT_EQ(test, matrix[3].value, 0LL); + KUNIT_EXPECT_EQ(test, matrix[7].value, 0LL); + KUNIT_EXPECT_EQ(test, matrix[11].value, 0LL); +} + +/* ---- Tests for __drm_ctm_3x4_to_dc_matrix ---- */ + +static void dm_test_drm_ctm_3x4_to_dc_matrix_identity(struct kunit *test) +{ + struct drm_color_ctm_3x4 ctm; + struct fixed31_32 matrix[12]; + int i; + long long one = 1LL << 32; + + memset(&ctm, 0, sizeof(ctm)); + + /* Identity with offsets in 4th column */ + ctm.matrix[0] = 1ULL << 32; /* [0][0] */ + ctm.matrix[5] = 1ULL << 32; /* [1][1] */ + ctm.matrix[10] = 1ULL << 32; /* [2][2] */ + + __drm_ctm_3x4_to_dc_matrix(&ctm, matrix); + + for (i = 0; i < 12; i++) { + if (i == 0 || i == 5 || i == 10) + KUNIT_EXPECT_EQ(test, matrix[i].value, one); + else + KUNIT_EXPECT_EQ(test, matrix[i].value, 0LL); + } +} + +static void dm_test_drm_ctm_3x4_to_dc_matrix_offset(struct kunit *test) +{ + struct drm_color_ctm_3x4 ctm; + struct fixed31_32 matrix[12]; + long long half = 1LL << 31; + + memset(&ctm, 0, sizeof(ctm)); + + /* Set 4th column (offsets) to 0.5 */ + ctm.matrix[3] = 1ULL << 31; + ctm.matrix[7] = 1ULL << 31; + ctm.matrix[11] = 1ULL << 31; + + __drm_ctm_3x4_to_dc_matrix(&ctm, matrix); + + KUNIT_EXPECT_EQ(test, matrix[3].value, half); + KUNIT_EXPECT_EQ(test, matrix[7].value, half); + KUNIT_EXPECT_EQ(test, matrix[11].value, half); +} + +/* ---- Tests for amdgpu_tf_to_dc_tf ---- */ + +static void dm_test_tf_to_dc_tf_default(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_DEFAULT), + (int)TRANSFER_FUNCTION_LINEAR); +} + +static void dm_test_tf_to_dc_tf_identity(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_IDENTITY), + (int)TRANSFER_FUNCTION_LINEAR); +} + +static void dm_test_tf_to_dc_tf_srgb(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF), + (int)TRANSFER_FUNCTION_SRGB); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF), + (int)TRANSFER_FUNCTION_SRGB); +} + +static void dm_test_tf_to_dc_tf_bt709(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_BT709_OETF), + (int)TRANSFER_FUNCTION_BT709); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF), + (int)TRANSFER_FUNCTION_BT709); +} + +static void dm_test_tf_to_dc_tf_pq(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_PQ_EOTF), + (int)TRANSFER_FUNCTION_PQ); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF), + (int)TRANSFER_FUNCTION_PQ); +} + +static void dm_test_tf_to_dc_tf_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF), + (int)TRANSFER_FUNCTION_GAMMA22); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF), + (int)TRANSFER_FUNCTION_GAMMA22); +} + +static void dm_test_tf_to_dc_tf_gamma24(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF), + (int)TRANSFER_FUNCTION_GAMMA24); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF), + (int)TRANSFER_FUNCTION_GAMMA24); +} + +static void dm_test_tf_to_dc_tf_gamma26(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF), + (int)TRANSFER_FUNCTION_GAMMA26); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF), + (int)TRANSFER_FUNCTION_GAMMA26); +} + +/* ---- Tests for amdgpu_colorop_tf_to_dc_tf ---- */ + +static void dm_test_colorop_tf_to_dc_tf_srgb(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_SRGB_EOTF), + (int)TRANSFER_FUNCTION_SRGB); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF), + (int)TRANSFER_FUNCTION_SRGB); +} + +static void dm_test_colorop_tf_to_dc_tf_pq(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_PQ_125_EOTF), + (int)TRANSFER_FUNCTION_PQ); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF), + (int)TRANSFER_FUNCTION_PQ); +} + +static void dm_test_colorop_tf_to_dc_tf_bt2020(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF), + (int)TRANSFER_FUNCTION_BT709); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_BT2020_OETF), + (int)TRANSFER_FUNCTION_BT709); +} + +static void dm_test_colorop_tf_to_dc_tf_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_GAMMA22), + (int)TRANSFER_FUNCTION_GAMMA22); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_GAMMA22_INV), + (int)TRANSFER_FUNCTION_GAMMA22); +} + +static void dm_test_colorop_tf_to_dc_tf_default(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_COUNT), + (int)TRANSFER_FUNCTION_LINEAR); +} + +/* ---- Tests for __drm_lut_to_dc_gamma (legacy path) ---- */ + +static void dm_test_drm_lut_to_dc_gamma_legacy_zero(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + __drm_lut_to_dc_gamma(lut, gamma, true); + + /* All-zero LUT should produce all-zero gamma entries */ + for (int i = 0; i < MAX_COLOR_LEGACY_LUT_ENTRIES; i++) { + KUNIT_EXPECT_EQ(test, gamma->entries.red[i].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.green[i].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[i].value, 0LL); + } +} + +static void dm_test_drm_lut_to_dc_gamma_legacy_max(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + long long expected = (long long)MAX_DRM_LUT_VALUE << 32; + + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Set first and last entries to max */ + lut[0].red = MAX_DRM_LUT_VALUE; + lut[0].green = MAX_DRM_LUT_VALUE; + lut[0].blue = MAX_DRM_LUT_VALUE; + lut[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].red = MAX_DRM_LUT_VALUE; + lut[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].green = MAX_DRM_LUT_VALUE; + lut[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].blue = MAX_DRM_LUT_VALUE; + + __drm_lut_to_dc_gamma(lut, gamma, true); + + /* Legacy uses dc_fixpt_from_int(val) = val << 32 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, expected); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, expected); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, expected); + KUNIT_EXPECT_EQ(test, gamma->entries.red[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].value, + expected); +} + +static void dm_test_drm_lut_to_dc_gamma_legacy_channels(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Set distinct values per channel at index 1 */ + lut[1].red = 100; + lut[1].green = 200; + lut[1].blue = 300; + + __drm_lut_to_dc_gamma(lut, gamma, true); + + KUNIT_EXPECT_EQ(test, gamma->entries.red[1].value, 100LL << 32); + KUNIT_EXPECT_EQ(test, gamma->entries.green[1].value, 200LL << 32); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[1].value, 300LL << 32); +} + +/* ---- Tests for __drm_lut_to_dc_gamma (non-legacy path) ---- */ + +static void dm_test_drm_lut_to_dc_gamma_nonlegacy_zero(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + __drm_lut_to_dc_gamma(lut, gamma, false); + + /* All-zero LUT → fraction(0, 0xFFFF) = 0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, 0LL); +} + +static void dm_test_drm_lut_to_dc_gamma_nonlegacy_max(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + long long one = 1LL << 32; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Max LUT value should map to 1.0 in fixed-point */ + lut[0].red = MAX_DRM_LUT_VALUE; + lut[0].green = MAX_DRM_LUT_VALUE; + lut[0].blue = MAX_DRM_LUT_VALUE; + + __drm_lut_to_dc_gamma(lut, gamma, false); + + /* dc_fixpt_from_fraction(0xFFFF, 0xFFFF) = 1.0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, one); +} + +/* ---- Tests for __drm_lut32_to_dc_gamma ---- */ + +static void dm_test_drm_lut32_to_dc_gamma_zero(struct kunit *test) +{ + struct drm_color_lut32 *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + __drm_lut32_to_dc_gamma(lut, gamma); + + /* All-zero LUT → fraction(0, 0xFFFFFFFF) = 0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, 0LL); +} + +static void dm_test_drm_lut32_to_dc_gamma_max(struct kunit *test) +{ + struct drm_color_lut32 *lut; + struct dc_gamma *gamma; + long long one = 1LL << 32; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Max 32-bit LUT value should map to 1.0 in fixed-point */ + lut[0].red = MAX_DRM_LUT32_VALUE; + lut[0].green = MAX_DRM_LUT32_VALUE; + lut[0].blue = MAX_DRM_LUT32_VALUE; + + __drm_lut32_to_dc_gamma(lut, gamma); + + /* dc_fixpt_from_fraction(0xFFFFFFFF, 0xFFFFFFFF) = 1.0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, one); +} + +static void dm_test_drm_lut32_to_dc_gamma_channels(struct kunit *test) +{ + struct drm_color_lut32 *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Set distinct values per channel at index 1 */ + lut[1].red = 1000; + lut[1].green = 2000; + lut[1].blue = 3000; + + __drm_lut32_to_dc_gamma(lut, gamma); + + /* Channels should differ */ + KUNIT_EXPECT_NE(test, gamma->entries.red[1].value, + gamma->entries.green[1].value); + KUNIT_EXPECT_NE(test, gamma->entries.green[1].value, + gamma->entries.blue[1].value); + /* Red < Green < Blue since 1000 < 2000 < 3000 */ + KUNIT_EXPECT_LT(test, gamma->entries.red[1].value, + gamma->entries.green[1].value); + KUNIT_EXPECT_LT(test, gamma->entries.green[1].value, + gamma->entries.blue[1].value); +} + +/* ---- Tests for __extract_blob_lut ---- */ + +static void dm_test_extract_blob_lut_null(struct kunit *test) +{ + uint32_t size = 42; + const struct drm_color_lut *lut; + + lut = __extract_blob_lut(NULL, &size); + + KUNIT_EXPECT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, size, 0); +} + +static void dm_test_extract_blob_lut_valid(struct kunit *test) +{ + const int num_entries = 4; + struct drm_property_blob *blob; + struct drm_color_lut *data; + const struct drm_color_lut *lut; + uint32_t size = 0; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + + data = kunit_kcalloc(test, num_entries, sizeof(*data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, data); + + data[0].red = 100; + data[0].green = 200; + data[0].blue = 300; + + blob->data = data; + blob->length = num_entries * sizeof(struct drm_color_lut); + + lut = __extract_blob_lut(blob, &size); + + KUNIT_EXPECT_EQ(test, size, (uint32_t)num_entries); + KUNIT_ASSERT_NOT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, lut[0].red, 100); + KUNIT_EXPECT_EQ(test, lut[0].green, 200); + KUNIT_EXPECT_EQ(test, lut[0].blue, 300); +} + +/* ---- Tests for __extract_blob_lut32 ---- */ + +static void dm_test_extract_blob_lut32_null(struct kunit *test) +{ + uint32_t size = 42; + const struct drm_color_lut32 *lut; + + lut = __extract_blob_lut32(NULL, &size); + + KUNIT_EXPECT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, size, 0); +} + +static void dm_test_extract_blob_lut32_valid(struct kunit *test) +{ + const int num_entries = 4; + struct drm_property_blob *blob; + struct drm_color_lut32 *data; + const struct drm_color_lut32 *lut; + uint32_t size = 0; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + + data = kunit_kcalloc(test, num_entries, sizeof(*data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, data); + + data[0].red = 100000; + data[0].green = 200000; + data[0].blue = 300000; + + blob->data = data; + blob->length = num_entries * sizeof(struct drm_color_lut32); + + lut = __extract_blob_lut32(blob, &size); + + KUNIT_EXPECT_EQ(test, size, (uint32_t)num_entries); + KUNIT_ASSERT_NOT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, lut[0].red, (uint32_t)100000); + KUNIT_EXPECT_EQ(test, lut[0].green, (uint32_t)200000); + KUNIT_EXPECT_EQ(test, lut[0].blue, (uint32_t)300000); +} + +/* ---- Tests for __to_dc_lut3d_color ---- */ + +static void dm_test_to_dc_lut3d_color_zero(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut lut = {0}; + + __to_dc_lut3d_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 0U); + KUNIT_EXPECT_EQ(test, rgb.green, 0U); + KUNIT_EXPECT_EQ(test, rgb.blue, 0U); +} + +static void dm_test_to_dc_lut3d_color_max(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut lut = { + .red = 0xFFFF, + .green = 0xFFFF, + .blue = 0xFFFF, + }; + + /* 12-bit extraction: 0xFFFF maps to (1 << 12) - 1 = 4095 */ + __to_dc_lut3d_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 4095U); + KUNIT_EXPECT_EQ(test, rgb.green, 4095U); + KUNIT_EXPECT_EQ(test, rgb.blue, 4095U); +} + +static void dm_test_to_dc_lut3d_color_channels(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut lut = { + .red = 0x8000, + .green = 0x4000, + .blue = 0xC000, + }; + + __to_dc_lut3d_color(&rgb, lut, 12); + + /* Channels should be distinct and ordered: green < red < blue */ + KUNIT_EXPECT_GT(test, rgb.red, rgb.green); + KUNIT_EXPECT_GT(test, rgb.blue, rgb.red); +} + +/* ---- Tests for __drm_3dlut_to_dc_3dlut ---- */ + +static void dm_test_3dlut_to_dc_3dlut_distribution(struct kunit *test) +{ + /* + * Use 9 entries: loop processes 2 groups of 4, then lut0 gets + * one extra final entry. Total: lut0=3, lut1=2, lut2=2, lut3=2. + */ + const uint32_t lut3d_size = 9; + struct drm_color_lut *lut; + struct tetrahedral_params *params; + int i; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + /* Fill LUT with distinct values per entry */ + for (i = 0; i < lut3d_size; i++) { + lut[i].red = (i + 1) * 1000; + lut[i].green = (i + 1) * 2000; + lut[i].blue = (i + 1) * 3000; + } + + __drm_3dlut_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* Group 0: lut[0]→lut0[0], lut[1]→lut1[0], lut[2]→lut2[0], lut[3]→lut3[0] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut_extract(lut[0].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[0].red, + drm_color_lut_extract(lut[1].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut2[0].red, + drm_color_lut_extract(lut[2].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut_extract(lut[3].red, 12)); + + /* Group 1: lut[4]→lut0[1], lut[5]→lut1[1], lut[6]→lut2[1], lut[7]→lut3[1] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[1].red, + drm_color_lut_extract(lut[4].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[1].red, + drm_color_lut_extract(lut[5].red, 12)); + + /* Final extra entry: lut[8]→lut0[2] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[2].red, + drm_color_lut_extract(lut[8].red, 12)); +} + +static void dm_test_3dlut_to_dc_3dlut_tetrahedral_17(struct kunit *test) +{ + /* Minimal test with 5 entries using tetrahedral_17 path */ + const uint32_t lut3d_size = 5; + struct drm_color_lut *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 0xFFFF; + lut[0].green = 0; + lut[0].blue = 0; + lut[1].red = 0; + lut[1].green = 0xFFFF; + lut[1].blue = 0; + lut[2].red = 0; + lut[2].green = 0; + lut[2].blue = 0xFFFF; + lut[3].red = 0x8000; + lut[3].green = 0x8000; + lut[3].blue = 0x8000; + lut[4].red = 0xFFFF; + lut[4].green = 0xFFFF; + lut[4].blue = 0xFFFF; + + __drm_3dlut_to_dc_3dlut(lut, lut3d_size, params, false, 12); + + /* lut[0]→lut0[0]: red=4095, green=0, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].green, 0U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].blue, 0U); + + /* lut[1]→lut1[0]: red=0, green=4095, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut1[0].green, 4095U); + + /* lut[2]→lut2[0]: red=0, green=0, blue=4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut2[0].blue, 4095U); + + /* lut[4]→lut0[1] (extra final entry): all 4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].green, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].blue, 4095U); +} + +static void dm_test_3dlut_to_dc_3dlut_green_blue(struct kunit *test) +{ + /* Verify green and blue channels are also correctly distributed */ + const uint32_t lut3d_size = 5; + struct drm_color_lut *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 100; + lut[0].green = 200; + lut[0].blue = 300; + lut[3].red = 400; + lut[3].green = 500; + lut[3].blue = 600; + + __drm_3dlut_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* lut[0]→lut0[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut_extract(100, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].green, + drm_color_lut_extract(200, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].blue, + drm_color_lut_extract(300, 12)); + + /* lut[3]→lut3[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut_extract(400, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].green, + drm_color_lut_extract(500, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].blue, + drm_color_lut_extract(600, 12)); +} + +/* ---- Tests for __to_dc_lut3d_32_color ---- */ + +static void dm_test_to_dc_lut3d_32_color_zero(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut32 lut = {0}; + + __to_dc_lut3d_32_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 0U); + KUNIT_EXPECT_EQ(test, rgb.green, 0U); + KUNIT_EXPECT_EQ(test, rgb.blue, 0U); +} + +static void dm_test_to_dc_lut3d_32_color_max(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut32 lut = { + .red = 0xFFFFFFFF, + .green = 0xFFFFFFFF, + .blue = 0xFFFFFFFF, + }; + + /* 12-bit extraction: 0xFFFFFFFF maps to (1 << 12) - 1 = 4095 */ + __to_dc_lut3d_32_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 4095U); + KUNIT_EXPECT_EQ(test, rgb.green, 4095U); + KUNIT_EXPECT_EQ(test, rgb.blue, 4095U); +} + +static void dm_test_to_dc_lut3d_32_color_channels(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut32 lut = { + .red = 0x80000000, + .green = 0x40000000, + .blue = 0xC0000000, + }; + + __to_dc_lut3d_32_color(&rgb, lut, 12); + + /* Channels should be distinct and ordered: green < red < blue */ + KUNIT_EXPECT_GT(test, rgb.red, rgb.green); + KUNIT_EXPECT_GT(test, rgb.blue, rgb.red); +} + +/* ---- Tests for __drm_3dlut32_to_dc_3dlut ---- */ + +static void dm_test_3dlut32_to_dc_3dlut_distribution(struct kunit *test) +{ + /* + * Use 9 entries: loop processes 2 groups of 4, then lut0 gets + * one extra final entry. Total: lut0=3, lut1=2, lut2=2, lut3=2. + */ + const uint32_t lut3d_size = 9; + struct drm_color_lut32 *lut; + struct tetrahedral_params *params; + int i; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + /* Fill LUT with distinct values per entry */ + for (i = 0; i < lut3d_size; i++) { + lut[i].red = (i + 1) * 100000; + lut[i].green = (i + 1) * 200000; + lut[i].blue = (i + 1) * 300000; + } + + __drm_3dlut32_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* Group 0: lut[0]→lut0[0], lut[1]→lut1[0], lut[2]→lut2[0], lut[3]→lut3[0] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut32_extract(lut[0].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[0].red, + drm_color_lut32_extract(lut[1].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut2[0].red, + drm_color_lut32_extract(lut[2].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut32_extract(lut[3].red, 12)); + + /* Group 1: lut[4]→lut0[1], lut[5]→lut1[1], lut[6]→lut2[1], lut[7]→lut3[1] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[1].red, + drm_color_lut32_extract(lut[4].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[1].red, + drm_color_lut32_extract(lut[5].red, 12)); + + /* Final extra entry: lut[8]→lut0[2] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[2].red, + drm_color_lut32_extract(lut[8].red, 12)); +} + +static void dm_test_3dlut32_to_dc_3dlut_tetrahedral_17(struct kunit *test) +{ + /* Minimal test with 5 entries using tetrahedral_17 path */ + const uint32_t lut3d_size = 5; + struct drm_color_lut32 *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 0xFFFFFFFF; + lut[0].green = 0; + lut[0].blue = 0; + lut[1].red = 0; + lut[1].green = 0xFFFFFFFF; + lut[1].blue = 0; + lut[2].red = 0; + lut[2].green = 0; + lut[2].blue = 0xFFFFFFFF; + lut[3].red = 0x80000000; + lut[3].green = 0x80000000; + lut[3].blue = 0x80000000; + lut[4].red = 0xFFFFFFFF; + lut[4].green = 0xFFFFFFFF; + lut[4].blue = 0xFFFFFFFF; + + __drm_3dlut32_to_dc_3dlut(lut, lut3d_size, params, false, 12); + + /* lut[0]→lut0[0]: red=4095, green=0, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].green, 0U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].blue, 0U); + + /* lut[1]→lut1[0]: red=0, green=4095, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut1[0].green, 4095U); + + /* lut[2]→lut2[0]: red=0, green=0, blue=4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut2[0].blue, 4095U); + + /* lut[4]→lut0[1] (extra final entry): all 4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].green, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].blue, 4095U); +} + +static void dm_test_3dlut32_to_dc_3dlut_green_blue(struct kunit *test) +{ + /* Verify green and blue channels are also correctly distributed */ + const uint32_t lut3d_size = 5; + struct drm_color_lut32 *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 100000; + lut[0].green = 200000; + lut[0].blue = 300000; + lut[3].red = 400000; + lut[3].green = 500000; + lut[3].blue = 600000; + + __drm_3dlut32_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* lut[0]→lut0[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut32_extract(100000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].green, + drm_color_lut32_extract(200000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].blue, + drm_color_lut32_extract(300000, 12)); + + /* lut[3]→lut3[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut32_extract(400000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].green, + drm_color_lut32_extract(500000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].blue, + drm_color_lut32_extract(600000, 12)); +} + +static struct kunit_case dm_color_test_cases[] = { + /* amdgpu_dm_fixpt_from_s3132 */ + KUNIT_CASE(dm_test_fixpt_from_s3132_zero), + KUNIT_CASE(dm_test_fixpt_from_s3132_one), + KUNIT_CASE(dm_test_fixpt_from_s3132_negative_one), + KUNIT_CASE(dm_test_fixpt_from_s3132_half), + KUNIT_CASE(dm_test_fixpt_from_s3132_neg_half), + /* __is_lut_linear */ + KUNIT_CASE(dm_test_is_lut_linear_with_linear_lut), + KUNIT_CASE(dm_test_is_lut_linear_with_nonlinear_lut), + KUNIT_CASE(dm_test_is_lut_linear_rgb_mismatch), + /* __drm_ctm_to_dc_matrix */ + KUNIT_CASE(dm_test_drm_ctm_to_dc_matrix_identity), + KUNIT_CASE(dm_test_drm_ctm_to_dc_matrix_negative), + KUNIT_CASE(dm_test_drm_ctm_to_dc_matrix_4th_col_zero), + /* __drm_ctm_3x4_to_dc_matrix */ + KUNIT_CASE(dm_test_drm_ctm_3x4_to_dc_matrix_identity), + KUNIT_CASE(dm_test_drm_ctm_3x4_to_dc_matrix_offset), + /* amdgpu_tf_to_dc_tf */ + KUNIT_CASE(dm_test_tf_to_dc_tf_default), + KUNIT_CASE(dm_test_tf_to_dc_tf_identity), + KUNIT_CASE(dm_test_tf_to_dc_tf_srgb), + KUNIT_CASE(dm_test_tf_to_dc_tf_bt709), + KUNIT_CASE(dm_test_tf_to_dc_tf_pq), + KUNIT_CASE(dm_test_tf_to_dc_tf_gamma22), + KUNIT_CASE(dm_test_tf_to_dc_tf_gamma24), + KUNIT_CASE(dm_test_tf_to_dc_tf_gamma26), + /* amdgpu_colorop_tf_to_dc_tf */ + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_srgb), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_pq), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_bt2020), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_gamma22), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_default), + /* __drm_lut_to_dc_gamma */ + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_legacy_zero), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_legacy_max), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_legacy_channels), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_nonlegacy_zero), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_nonlegacy_max), + /* __drm_lut32_to_dc_gamma */ + KUNIT_CASE(dm_test_drm_lut32_to_dc_gamma_zero), + KUNIT_CASE(dm_test_drm_lut32_to_dc_gamma_max), + KUNIT_CASE(dm_test_drm_lut32_to_dc_gamma_channels), + /* __extract_blob_lut */ + KUNIT_CASE(dm_test_extract_blob_lut_null), + KUNIT_CASE(dm_test_extract_blob_lut_valid), + /* __extract_blob_lut32 */ + KUNIT_CASE(dm_test_extract_blob_lut32_null), + KUNIT_CASE(dm_test_extract_blob_lut32_valid), + /* __to_dc_lut3d_color */ + KUNIT_CASE(dm_test_to_dc_lut3d_color_zero), + KUNIT_CASE(dm_test_to_dc_lut3d_color_max), + KUNIT_CASE(dm_test_to_dc_lut3d_color_channels), + /* __drm_3dlut_to_dc_3dlut */ + KUNIT_CASE(dm_test_3dlut_to_dc_3dlut_distribution), + KUNIT_CASE(dm_test_3dlut_to_dc_3dlut_tetrahedral_17), + KUNIT_CASE(dm_test_3dlut_to_dc_3dlut_green_blue), + /* __to_dc_lut3d_32_color */ + KUNIT_CASE(dm_test_to_dc_lut3d_32_color_zero), + KUNIT_CASE(dm_test_to_dc_lut3d_32_color_max), + KUNIT_CASE(dm_test_to_dc_lut3d_32_color_channels), + /* __drm_3dlut32_to_dc_3dlut */ + KUNIT_CASE(dm_test_3dlut32_to_dc_3dlut_distribution), + KUNIT_CASE(dm_test_3dlut32_to_dc_3dlut_tetrahedral_17), + KUNIT_CASE(dm_test_3dlut32_to_dc_3dlut_green_blue), + {} +}; + +static struct kunit_suite dm_color_test_suite = { + .name = "amdgpu_dm_color", + .test_cases = dm_color_test_cases, +}; + +kunit_test_suite(dm_color_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_color"); +MODULE_AUTHOR("AMD"); From 53af0cf6c8e8712da2c30c55fd2b9118c8445b83 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 22 Apr 2026 18:12:46 -0600 Subject: [PATCH 05/58] drm/amd/display: Add KUnit test for PSR function Add KUnit tests for amdgpu_dm_psr_fill_caps() which validates PSR capability population from DPCD data. Export amdgpu_dm_psr_fill_caps() conditionally when CONFIG_DRM_AMD_DC_KUNIT_TEST is enabled, following the existing pattern used by CRC and HDCP test files. The test covers PSR version mapping, RFB setup time calculation, link training flag, DPCD field passthrough, rate control caps, and power optimization flags. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Harry Wentland Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c | 6 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h | 5 + .../drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_psr_test.c | 255 ++++++++++++++++++ 4 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c index dc5913a6456e..4b823bba4392 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c @@ -29,6 +29,8 @@ #include "dc.h" #include "amdgpu_dm.h" #include "modules/power/power_helpers.h" +#include "amdgpu_dm_kunit_helpers.h" + static bool link_supports_psrsu(struct dc_link *link) { @@ -58,7 +60,8 @@ static bool link_supports_psrsu(struct dc_link *link) return false; } -static void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) +STATIC_IFN_KUNIT +void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) { struct dpcd_caps *dpcd_caps = &link->dpcd_caps; unsigned int power_opts = 0; @@ -86,6 +89,7 @@ static void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) caps->rate_control_caps = 0; /* TODO: read in rc caps from aux */ caps->psr_power_opt_flag = power_opts; } +EXPORT_IF_KUNIT(amdgpu_dm_psr_fill_caps); /* * amdgpu_dm_set_psr_caps() - set link psr capabilities diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h index 16d535806ad6..1a41d9b99eb4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h @@ -39,4 +39,9 @@ bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm); bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream_state *stream, bool set_event, enum psr_event event, bool wait_for_disable); + +#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps); +#endif + #endif /* AMDGPU_DM_AMDGPU_DM_PSR_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 2b442353beae..f29b5c0dac8e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c new file mode 100644 index 000000000000..61a4167898cb --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_psr.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" + +/* Extern declaration for the function under test */ +extern void amdgpu_dm_psr_fill_caps(struct dc_link *link, + struct psr_caps *caps); + +/* + * Helper: allocate and zero-initialise a dc_link sufficient for + * amdgpu_dm_psr_fill_caps() testing. The function only accesses + * embedded members (dpcd_caps, psr_settings) so no pointer fields + * need to be wired up. + */ +static struct dc_link *alloc_test_link(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + return link; +} + +/* Tests for amdgpu_dm_psr_fill_caps() — PSR version mapping */ + +static void dm_test_psr_fill_caps_version_1(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->psr_settings.psr_version = DC_PSR_VERSION_1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 1); +} + +static void dm_test_psr_fill_caps_version_su1(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->psr_settings.psr_version = DC_PSR_VERSION_SU_1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 2); +} + +static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED; + + amdgpu_dm_psr_fill_caps(link, &caps); + + /* + * Neither DC_PSR_VERSION_1 nor DC_PSR_VERSION_SU_1, + * so psr_version stays at its zero-initialised value. + */ + KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 0); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — RFB setup time */ + +static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + /* PSR_SETUP_TIME = 0 → (6 - 0) * 55 = 330 */ + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 0; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 330U); +} + +static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + /* PSR_SETUP_TIME = 3 → (6 - 3) * 55 = 165 */ + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 3; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 165U); +} + +static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + /* PSR_SETUP_TIME = 6 → (6 - 6) * 55 = 0 */ + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 6; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 0U); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — link training flag */ + +static void dm_test_psr_fill_caps_link_training_required(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED = 0; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_TRUE(test, caps.psr_exit_link_training_required); +} + +static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED = 1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_FALSE(test, caps.psr_exit_link_training_required); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — DPCD field passthrough */ + +static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + + link->dpcd_caps.edp_rev = 0x14; + link->dpcd_caps.psr_info.psr_version = 2; + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED = 1; + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 1; + link->dpcd_caps.psr_info.psr2_su_y_granularity_cap = 4; + link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; + link->dpcd_caps.alpm_caps.bits.PM_STATE_2A_SUPPORT = 1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.edp_revision, 0x14); + KUNIT_EXPECT_EQ(test, (int)caps.support_ver, 2); + KUNIT_EXPECT_TRUE(test, caps.su_granularity_required); + KUNIT_EXPECT_TRUE(test, caps.y_coordinate_required); + KUNIT_EXPECT_EQ(test, (int)caps.su_y_granularity, 4); + KUNIT_EXPECT_TRUE(test, caps.alpm_cap); + KUNIT_EXPECT_TRUE(test, caps.standby_support); +} + +static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0xFF, sizeof(caps)); + + /* All dpcd_caps fields are zero from kzalloc */ + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.edp_revision, 0); + KUNIT_EXPECT_EQ(test, (int)caps.support_ver, 0); + KUNIT_EXPECT_FALSE(test, caps.su_granularity_required); + KUNIT_EXPECT_FALSE(test, caps.y_coordinate_required); + KUNIT_EXPECT_EQ(test, (int)caps.su_y_granularity, 0); + KUNIT_EXPECT_FALSE(test, caps.alpm_cap); + KUNIT_EXPECT_FALSE(test, caps.standby_support); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — rate control and power opts */ + +static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + /* Pre-fill caps with non-zero to verify overwrite */ + memset(&caps, 0xFF, sizeof(caps)); + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.rate_control_caps, 0); +} + +static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + + amdgpu_dm_psr_fill_caps(link, &caps); + + /* + * psr_power_opt_z10_static_screen is always added to power_opts + * regardless of amdgpu_dc_feature_mask. + */ + KUNIT_EXPECT_TRUE(test, + (caps.psr_power_opt_flag & + psr_power_opt_z10_static_screen) != 0); +} +/* End of tests for amdgpu_dm_psr_fill_caps() */ + +static struct kunit_case dm_psr_test_cases[] = { + KUNIT_CASE(dm_test_psr_fill_caps_version_1), + KUNIT_CASE(dm_test_psr_fill_caps_version_su1), + KUNIT_CASE(dm_test_psr_fill_caps_version_unsupported), + KUNIT_CASE(dm_test_psr_fill_caps_setup_time_zero), + KUNIT_CASE(dm_test_psr_fill_caps_setup_time_mid), + KUNIT_CASE(dm_test_psr_fill_caps_setup_time_max), + KUNIT_CASE(dm_test_psr_fill_caps_link_training_required), + KUNIT_CASE(dm_test_psr_fill_caps_link_training_not_required), + KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields), + KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields_unset), + KUNIT_CASE(dm_test_psr_fill_caps_rate_control_always_zero), + KUNIT_CASE(dm_test_psr_fill_caps_power_opts_z10_always_set), + {} +}; + +static struct kunit_suite dm_psr_test_suite = { + .name = "amdgpu_dm_psr", + .test_cases = dm_psr_test_cases, +}; + +kunit_test_suite(dm_psr_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_psr"); +MODULE_AUTHOR("AMD"); From 15b2363e90f7a0b5b904ab2f011593fef3f73e16 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 22 Apr 2026 19:51:47 -0600 Subject: [PATCH 06/58] drm/amd/display: Add KUnit test for replay Add KUnit tests for amdgpu_dm_link_supports_replay() which validates panel replay capability based on link DPCD caps, freesync state, and VSDB info. Nine test cases cover the positive path and each individual failure condition. Export the function under CONFIG_DRM_AMD_DC_KUNIT_TEST and add the amdgpu include path to the tests Makefile so that amdgpu_dm.h can resolve amdgpu_mode.h types under UML. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Harry Wentland Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_replay.c | 3 + .../drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_replay_test.c | 206 ++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c index 297125d1db70..22aa4305d2af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c @@ -31,6 +31,8 @@ #include "modules/power/power_helpers.h" #include "dmub/inc/dmub_cmd.h" #include "dc/inc/link_service.h" +#include "amdgpu_dm_kunit_helpers.h" + /* * amdgpu_dm_link_supports_replay() - check if the link supports replay @@ -68,6 +70,7 @@ bool amdgpu_dm_link_supports_replay(struct dc_link *link, struct amdgpu_dm_conne return true; } +EXPORT_IF_KUNIT(amdgpu_dm_link_supports_replay); /* * amdgpu_dm_set_replay_caps() - setup Replay capabilities diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index f29b5c0dac8e..7d9c983f4995 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c new file mode 100644 index 000000000000..28ff8bbcc0f7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_replay.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" + +/* Extern declaration for the function under test */ +extern bool amdgpu_dm_link_supports_replay(struct dc_link *link, + struct amdgpu_dm_connector *aconnector); + +/* + * Helper: allocate a dc_link, amdgpu_dm_connector, and dm_connector_state + * wired up so that to_dm_connector_state(aconnector->base.state) works. + */ +struct replay_test_ctx { + struct dc_link *link; + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *dm_state; +}; + +static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) +{ + struct replay_test_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + + ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + + /* Wire connector state so to_dm_connector_state() works */ + ctx->aconnector->base.state = &ctx->dm_state->base; + + return ctx; +} + +/* + * Helper: set all conditions for replay support to pass so individual + * tests can disable one condition at a time. + */ +static void set_all_replay_caps(struct replay_test_ctx *ctx) +{ + ctx->dm_state->freesync_capable = true; + ctx->aconnector->vsdb_info.replay_mode = true; + ctx->link->dpcd_caps.edp_rev = EDP_REVISION_13; + ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; + ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 1; + ctx->link->dpcd_caps.pr_info.max_deviation_line = 1; +} + +/* Tests for amdgpu_dm_link_supports_replay() — all caps met */ + +static void dm_test_replay_supports_all_caps(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — freesync not capable */ + +static void dm_test_replay_no_freesync(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->dm_state->freesync_capable = false; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — no replay mode in VSDB */ + +static void dm_test_replay_no_vsdb_replay_mode(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->aconnector->vsdb_info.replay_mode = false; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — eDP revision too low */ + +static void dm_test_replay_edp_rev_too_low(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.edp_rev = EDP_REVISION_12; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — no ALPM AUX wake cap */ + +static void dm_test_replay_no_alpm_aux_wake(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — no adaptive sync SDP */ + +static void dm_test_replay_no_adaptive_sync_sdp(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — zero pixel deviation */ + +static void dm_test_replay_zero_pixel_deviation(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — zero max deviation line */ + +static void dm_test_replay_zero_max_deviation_line(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.pr_info.max_deviation_line = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — both deviation fields zero */ + +static void dm_test_replay_both_deviations_zero(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 0; + ctx->link->dpcd_caps.pr_info.max_deviation_line = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* End of tests for amdgpu_dm_link_supports_replay() */ + +static struct kunit_case dm_replay_test_cases[] = { + KUNIT_CASE(dm_test_replay_supports_all_caps), + KUNIT_CASE(dm_test_replay_no_freesync), + KUNIT_CASE(dm_test_replay_no_vsdb_replay_mode), + KUNIT_CASE(dm_test_replay_edp_rev_too_low), + KUNIT_CASE(dm_test_replay_no_alpm_aux_wake), + KUNIT_CASE(dm_test_replay_no_adaptive_sync_sdp), + KUNIT_CASE(dm_test_replay_zero_pixel_deviation), + KUNIT_CASE(dm_test_replay_zero_max_deviation_line), + KUNIT_CASE(dm_test_replay_both_deviations_zero), + {} +}; + +static struct kunit_suite dm_replay_test_suite = { + .name = "amdgpu_dm_replay", + .test_cases = dm_replay_test_cases, +}; + +kunit_test_suite(dm_replay_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_replay"); +MODULE_AUTHOR("AMD"); From 5838a89515c87c072057c12cf022f958ece1c720 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 23 Apr 2026 14:12:36 -0600 Subject: [PATCH 07/58] drm/amd/display: Add KUnit test for ISM functions Add KUnit tests for three static functions in amdgpu_dm_ism.c: dm_ism_next_state, dm_ism_get_sso_delay, and dm_ism_get_idle_allow_delay. The 32 test cases cover the full FSM transition table, SSO delay calculation with various timings, and hysteresis-based idle allow delay including circular buffer wraparound and old history cutoff logic. Conditionally remove static linkage and export the three functions under CONFIG_DRM_AMD_DC_KUNIT_TEST so the test module can call them. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Harry Wentland Signed-off-by: Alex Hung Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 20 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h | 10 + .../drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_ism_test.c | 636 ++++++++++++++++++ 4 files changed, 661 insertions(+), 6 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c index 7dcc587c45e9..1324b55c403c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c @@ -32,6 +32,8 @@ #include "amdgpu_dm_ism.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_trace.h" +#include "amdgpu_dm_kunit_helpers.h" + /** * dm_ism_next_state - Get next state based on current state and event @@ -42,9 +44,10 @@ * This function defines the idle state management FSM. Invalid transitions * are ignored and will not progress the FSM. */ -static bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, - enum amdgpu_dm_ism_event event, - enum amdgpu_dm_ism_state *next_state) +STATIC_IFN_KUNIT +bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, + enum amdgpu_dm_ism_event event, + enum amdgpu_dm_ism_state *next_state) { switch (STATE_EVENT(current_state, event)) { case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING, @@ -125,8 +128,10 @@ static bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, } return true; } +EXPORT_IF_KUNIT(dm_ism_next_state); -static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, +STATIC_IFN_KUNIT +uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, const struct dc_stream_state *stream) { const struct amdgpu_dm_ism_config *config = &ism->config; @@ -148,6 +153,7 @@ static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, return sso_delay_ns; } +EXPORT_IF_KUNIT(dm_ism_get_sso_delay); /** * dm_ism_get_idle_allow_delay - Calculate hysteresis-based idle allow delay @@ -157,8 +163,9 @@ static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, * Calculates the delay before allowing idle optimizations based on recent * idle history and the current stream timing. */ -static uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, - const struct dc_stream_state *stream) +STATIC_IFN_KUNIT +uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, + const struct dc_stream_state *stream) { const struct amdgpu_dm_ism_config *config = &ism->config; uint32_t v_total, h_total; @@ -217,6 +224,7 @@ static uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, return ret_ns; } +EXPORT_IF_KUNIT(dm_ism_get_idle_allow_delay); /** * dm_ism_insert_record - Insert a record into the circular history buffer diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h index 964408cd9a83..72e2dac49e55 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h @@ -149,4 +149,14 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm); void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm); void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, + enum amdgpu_dm_ism_event event, + enum amdgpu_dm_ism_state *next_state); +uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, + const struct dc_stream_state *stream); +uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, + const struct dc_stream_state *stream); +#endif + #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 7d9c983f4995..768f9bbc50e1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c new file mode 100644 index 000000000000..e761105e1995 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_ism.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu_dm_ism.h" + +/* + * Helper: allocate and zero-initialise a dc_stream_state for timing tests. + * Only the timing sub-struct is accessed by the functions under test. + */ +static struct dc_stream_state *alloc_test_stream(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + return stream; +} + +/* + * Helper: allocate and zero-initialise an ISM instance. + */ +static struct amdgpu_dm_ism *alloc_test_ism(struct kunit *test) +{ + struct amdgpu_dm_ism *ism; + + ism = kunit_kzalloc(test, sizeof(*ism), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ism); + + return ism; +} + +/* ===== Tests for dm_ism_next_state — FULL_POWER_RUNNING transitions ===== */ + +static void dm_test_ism_next_state_running_enter_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING); +} + +static void dm_test_ism_next_state_running_begin_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY); +} + +static void dm_test_ism_next_state_running_invalid(struct kunit *test) +{ + enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_FALSE(test, ok); + /* next should remain untouched on invalid transition */ + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES); +} + +/* ===== Tests for dm_ism_next_state — FULL_POWER_BUSY transitions ===== */ + +static void dm_test_ism_next_state_busy_enter_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +static void dm_test_ism_next_state_busy_end_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY, + DM_ISM_EVENT_END_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +/* ===== Tests for dm_ism_next_state — HYSTERESIS_WAITING transitions ===== */ + +static void dm_test_ism_next_state_hyst_wait_exit_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_TIMER_ABORTED); +} + +static void dm_test_ism_next_state_hyst_wait_begin_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +static void dm_test_ism_next_state_hyst_wait_timer(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_TIMER_ELAPSED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE); +} + +static void dm_test_ism_next_state_hyst_wait_immediate(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_IMMEDIATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE); +} + +/* ===== Tests for dm_ism_next_state — HYSTERESIS_BUSY transitions ===== */ + +static void dm_test_ism_next_state_hyst_busy_exit_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY); +} + +static void dm_test_ism_next_state_hyst_busy_end_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY, + DM_ISM_EVENT_END_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING); +} + +/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE transitions ===== */ + +static void dm_test_ism_next_state_opt_idle_exit(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +static void dm_test_ism_next_state_opt_idle_begin_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +static void dm_test_ism_next_state_opt_idle_sso_timer(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_SSO_TIMER_ELAPSED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO); +} + +static void dm_test_ism_next_state_opt_idle_immediate(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_IMMEDIATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO); +} + +/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE_SSO transitions ===== */ + +static void dm_test_ism_next_state_opt_idle_sso_exit(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +static void dm_test_ism_next_state_opt_idle_sso_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +/* ===== Tests for dm_ism_next_state — TIMER_ABORTED transitions ===== */ + +static void dm_test_ism_next_state_aborted_immediate(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED, + DM_ISM_EVENT_IMMEDIATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +static void dm_test_ism_next_state_aborted_invalid(struct kunit *test) +{ + enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next); + KUNIT_EXPECT_FALSE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES); +} + +/* ===== Tests for dm_ism_get_sso_delay ===== */ + +static void dm_test_ism_sso_delay_null_stream(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + + ism->config.sso_num_frames = 5; + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, NULL), (uint64_t)0); +} + +static void dm_test_ism_sso_delay_zero_frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.sso_num_frames = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), (uint64_t)0); +} + +static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t expected_one_frame_ns, expected; + + /* + * 1080p@60Hz: v_total=1125, h_total=2200, pix_clk=148.5MHz + * pix_clk_100hz = 1485000 + * one_frame_ns = (1125 * 2200 * 10000000) / 1485000 = 16666666 ns + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.sso_num_frames = 3; + + expected_one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + expected = 3 * expected_one_frame_ns; + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), expected); +} + +static void dm_test_ism_sso_delay_4k60_1frame(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t expected_one_frame_ns; + + /* + * 4K@60Hz: v_total=2250, h_total=4400, pix_clk=594MHz + * pix_clk_100hz = 5940000 + */ + stream->timing.v_total = 2250; + stream->timing.h_total = 4400; + stream->timing.pix_clk_100hz = 5940000; + ism->config.sso_num_frames = 1; + + expected_one_frame_ns = div64_u64((uint64_t)2250 * 4400 * 10000000ULL, + 5940000); + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), + expected_one_frame_ns); +} + +/* ===== Tests for dm_ism_get_idle_allow_delay ===== */ + +static void dm_test_ism_idle_delay_null_stream(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, NULL), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.filter_num_frames = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns; + + /* + * All history records have long durations (well above the + * short_idle_ns threshold), so no delay should be applied. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* Fill history with long idle durations */ + for (int i = 0; i < 8; i++) { + ism->records[i].duration_ns = one_frame_ns * 100; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns, expected; + + /* + * Fill history with short idle durations that meet the threshold. + * filter_entry_count=3, so 3 short idles should trigger the delay. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* Fill history with short idle durations (1 frame each) */ + for (int i = 0; i < 8; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; + + expected = 10 * one_frame_ns; + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + expected); +} + +static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns, expected; + + /* + * Test the circular buffer wraparound: next_record_idx at 2 means + * the most recent records are at indices 1, 0, 15, 14, ... + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* Fill entire buffer with short idles */ + for (int i = 0; i < AMDGPU_DM_IDLE_HIST_LEN; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + /* Position next_record_idx at 2 to test wraparound */ + ism->next_record_idx = 2; + + expected = 10 * one_frame_ns; + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + expected); +} + +static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns; + + /* + * Test old_history_threshold: only recent entries within the + * threshold should be counted. Set up 2 recent short idles but + * require 3 — older entries are outside the threshold. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + /* Threshold: entries older than 20 frames are ignored */ + ism->config.filter_old_history_threshold = 20; + + ism->last_idle_timestamp_ns = one_frame_ns * 100; + + /* 2 recent short idles (within threshold) */ + ism->records[6].duration_ns = one_frame_ns; + ism->records[6].timestamp_ns = one_frame_ns * 95; + ism->records[7].duration_ns = one_frame_ns; + ism->records[7].timestamp_ns = one_frame_ns * 98; + + /* Older entries outside the threshold with long durations */ + for (int i = 0; i < 6; i++) { + ism->records[i].duration_ns = one_frame_ns * 100; + ism->records[i].timestamp_ns = one_frame_ns * 10; + } + ism->next_record_idx = 8; + + /* + * Only 2 short idles within threshold, but 3 required — + * should return 0 (no delay). + */ + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns; + + /* + * Mix of short and long idle durations. Only 2 short idles + * in 8 entries, but filter_entry_count=3, so no delay. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* 2 short idles, 6 long idles */ + for (int i = 0; i < 8; i++) { + if (i == 6 || i == 7) + ism->records[i].duration_ns = one_frame_ns; + else + ism->records[i].duration_ns = one_frame_ns * 100; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static struct kunit_case dm_ism_test_cases[] = { + /* dm_ism_next_state — FULL_POWER_RUNNING */ + KUNIT_CASE(dm_test_ism_next_state_running_enter_idle), + KUNIT_CASE(dm_test_ism_next_state_running_begin_cursor), + KUNIT_CASE(dm_test_ism_next_state_running_invalid), + /* dm_ism_next_state — FULL_POWER_BUSY */ + KUNIT_CASE(dm_test_ism_next_state_busy_enter_idle), + KUNIT_CASE(dm_test_ism_next_state_busy_end_cursor), + /* dm_ism_next_state — HYSTERESIS_WAITING */ + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_exit_idle), + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_begin_cursor), + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_timer), + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_immediate), + /* dm_ism_next_state — HYSTERESIS_BUSY */ + KUNIT_CASE(dm_test_ism_next_state_hyst_busy_exit_idle), + KUNIT_CASE(dm_test_ism_next_state_hyst_busy_end_cursor), + /* dm_ism_next_state — OPTIMIZED_IDLE */ + KUNIT_CASE(dm_test_ism_next_state_opt_idle_exit), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_begin_cursor), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_timer), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_immediate), + /* dm_ism_next_state — OPTIMIZED_IDLE_SSO */ + KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_exit), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_cursor), + /* dm_ism_next_state — TIMER_ABORTED */ + KUNIT_CASE(dm_test_ism_next_state_aborted_immediate), + KUNIT_CASE(dm_test_ism_next_state_aborted_invalid), + /* dm_ism_get_sso_delay */ + KUNIT_CASE(dm_test_ism_sso_delay_null_stream), + KUNIT_CASE(dm_test_ism_sso_delay_zero_frames), + KUNIT_CASE(dm_test_ism_sso_delay_1080p60_3frames), + KUNIT_CASE(dm_test_ism_sso_delay_4k60_1frame), + /* dm_ism_get_idle_allow_delay */ + KUNIT_CASE(dm_test_ism_idle_delay_null_stream), + KUNIT_CASE(dm_test_ism_idle_delay_zero_filter_frames), + KUNIT_CASE(dm_test_ism_idle_delay_zero_entry_count), + KUNIT_CASE(dm_test_ism_idle_delay_zero_delay_frames), + KUNIT_CASE(dm_test_ism_idle_delay_no_short_idles), + KUNIT_CASE(dm_test_ism_idle_delay_enough_short_idles), + KUNIT_CASE(dm_test_ism_idle_delay_wraps_around_buffer), + KUNIT_CASE(dm_test_ism_idle_delay_old_history_cutoff), + KUNIT_CASE(dm_test_ism_idle_delay_mixed_durations), + {} +}; + +static struct kunit_suite dm_ism_test_suite = { + .name = "amdgpu_dm_ism", + .test_cases = dm_ism_test_cases, +}; + +kunit_test_suite(dm_ism_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_ism"); +MODULE_AUTHOR("AMD"); From b0687d047b075b390623962aa3e01fe8c089f6a1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 14 May 2026 11:01:39 -0600 Subject: [PATCH 08/58] drm/amdkfd: Fix UML build guards for x86_64-only code cpu_data().topo.apicid and kfd_fill_iolink_info_for_cpu() rely on x86-specific structs not present on UML. The kfd_topology.c and kfd_crat.c were guarded by CONFIG_X86_64 alone, causing build failures when CONFIG_DRM_AMDGPU is selected on UML. Update guards to '#if defined(CONFIG_X86_64) && !defined(CONFIG_UML)' to ensure x86_64-only paths are excluded on UML builds. Fixes: af3f2f5db265 ("drm/amdgpu: Remove UML build exclusion from Kconfig") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605140506.TI8zPIBG-lkp@intel.com/ Cc: Harry Wentland Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Felix Kuehling Acked-by: Alex Deucher Signed-off-by: Alex Hung Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_crat.c | 6 +++--- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c index a1087c13f241..cf7b1b038d5f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c @@ -1821,7 +1821,7 @@ static int kfd_fill_mem_info_for_cpu(int numa_node_id, int *avail_size, return 0; } -#ifdef CONFIG_X86_64 +#if defined(CONFIG_X86_64) && !defined(CONFIG_UML) static int kfd_fill_iolink_info_for_cpu(int numa_node_id, int *avail_size, uint32_t *num_entries, struct crat_subtype_iolink *sub_type_hdr) @@ -1880,7 +1880,7 @@ static int kfd_create_vcrat_image_cpu(void *pcrat_image, size_t *size) struct crat_subtype_generic *sub_type_hdr; int avail_size = *size; int numa_node_id; -#ifdef CONFIG_X86_64 +#if defined(CONFIG_X86_64) && !defined(CONFIG_UML) uint32_t entries = 0; #endif int ret = 0; @@ -1945,7 +1945,7 @@ static int kfd_create_vcrat_image_cpu(void *pcrat_image, size_t *size) sub_type_hdr->length); /* Fill in Subtype: IO Link */ -#ifdef CONFIG_X86_64 +#if defined(CONFIG_X86_64) && !defined(CONFIG_UML) ret = kfd_fill_iolink_info_for_cpu(numa_node_id, &avail_size, &entries, (struct crat_subtype_iolink *)sub_type_hdr); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 630f46091dc7..dc1c6bd1252f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2350,7 +2350,7 @@ static int kfd_cpumask_to_apic_id(const struct cpumask *cpumask) first_cpu_of_numa_node = cpumask_first(cpumask); if (first_cpu_of_numa_node >= nr_cpu_ids) return -1; -#ifdef CONFIG_X86_64 +#if defined(CONFIG_X86_64) && !defined(CONFIG_UML) return cpu_data(first_cpu_of_numa_node).topo.apicid; #else return first_cpu_of_numa_node; From 651479b69add00ffd269ee6d31476b63e898d45f Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Tue, 19 May 2026 11:44:55 +0800 Subject: [PATCH 09/58] drm/amd/display: Fix amdgpu_dm KUnit allmodconfig build [Why] With CONFIG_DRM_AMD_DC_KUNIT_TEST=m, allmodconfig only defines the _MODULE variant. Four KUnit helper headers gate their declarations with #ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST, so the declarations vanish while the matching .c files (driven by IS_ENABLED() via STATIC_IFN_KUNIT) keep the functions non-static. The build breaks with implicit declarations and -Werror=missing-prototypes. amdgpu_dm_crc.h additionally uses symbols that its test file does not pull in indirectly, amdgpu_dm_colorop_test.c has a copy-paste duplicate function with the wrong expected bitmask, and the three colorop TF bitmasks are not exported for modpost. [How] - Switch the crc/hdcp/color/psr KUnit guards to IS_ENABLED(). - Make amdgpu_dm_crc.h self-contained (dc_types.h + forward decl). - Rename the duplicated shaper test back to its intended name and fix its expected bitmask. - Export amdgpu_dm_supported_{degam,shaper,blnd}_tfs via EXPORT_IF_KUNIT(). Assisted-by: Copilot:claude-4-opus Reviewed-by: Alex Hung Acked-by: Alex Deucher Signed-off-by: Ray Wu Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 4 ++++ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h | 5 ++++- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h | 2 +- .../display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 10 +++++----- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h index 5e484359b8a7..19d3a13572f5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -42,7 +42,7 @@ struct dc_rgb; struct fixed31_32; struct tetrahedral_params; -#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) /* * Prototypes for functions exposed to KUnit tests. The enum types * used below (dc_transfer_func_predefined, amdgpu_transfer_function, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c index 7ee051cb3c05..ab20e3f41d60 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c @@ -31,6 +31,7 @@ #include "amdgpu.h" #include "amdgpu_dm_colorop.h" +#include "amdgpu_dm_kunit_helpers.h" #include "dc.h" const u64 amdgpu_dm_supported_degam_tfs = @@ -38,18 +39,21 @@ const u64 amdgpu_dm_supported_degam_tfs = BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | BIT(DRM_COLOROP_1D_CURVE_GAMMA22); +EXPORT_IF_KUNIT(amdgpu_dm_supported_degam_tfs); const u64 amdgpu_dm_supported_shaper_tfs = BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF) | BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF) | BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF) | BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); +EXPORT_IF_KUNIT(amdgpu_dm_supported_shaper_tfs); const u64 amdgpu_dm_supported_blnd_tfs = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | BIT(DRM_COLOROP_1D_CURVE_GAMMA22); +EXPORT_IF_KUNIT(amdgpu_dm_supported_blnd_tfs); #define MAX_COLOR_PIPELINE_OPS 10 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h index 76731ee44e13..c9aa0c82038f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h @@ -27,8 +27,11 @@ #ifndef AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ #define AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ +#include "dc_types.h" + struct drm_crtc; struct dm_crtc_state; +struct amdgpu_device; enum amdgpu_dm_pipe_crc_source { AMDGPU_DM_PIPE_CRC_SOURCE_NONE = 0, @@ -148,7 +151,7 @@ void amdgpu_dm_crtc_secure_display_create_contexts(struct amdgpu_device *adev); #define amdgpu_dm_crtc_secure_display_create_contexts(x) #endif -#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source); bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src); bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 4bb072cfac1e..90b18c450ca6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -94,7 +94,7 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *work); struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct cp_psp *cp_psp, struct dc *dc); -#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void process_output(struct hdcp_workqueue *hdcp_work); #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h index 1a41d9b99eb4..9f3e22520ca0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h @@ -40,7 +40,7 @@ bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream_state *stream, bool set_event, enum psr_event event, bool wait_for_disable); -#ifdef CONFIG_DRM_AMD_DC_KUNIT_TEST +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps); #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c index 8bdebcaf42b2..6c77a7159188 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -72,12 +72,12 @@ static void dm_test_supported_shaper_tfs_has_gamma22(struct kunit *test) BIT(DRM_COLOROP_1D_CURVE_GAMMA22)); } -static void dm_test_supported_degam_tfs_no_extra_bits(struct kunit *test) +static void dm_test_supported_shaper_tfs_no_extra_bits(struct kunit *test) { - u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | - BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | - BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | - BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22); KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_shaper_tfs, expected); } From b69d3256d79de15f54c322986ff4da68f1d65b0a Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 17 May 2026 09:17:42 -0400 Subject: [PATCH 10/58] drm/amdgpu: fix lock leak on ENOMEM in AMDGPU_GEM_OP_GET_MAPPING_INFO The AMDGPU_GEM_OP_GET_MAPPING_INFO branch of amdgpu_gem_op_ioctl() holds three cleanup-tracked resources before calling kvcalloc(): the drm_gem_object reference from drm_gem_object_lookup(), the drm_exec lock on the looked-up GEM via drm_exec_lock_obj(), and the drm_exec lock on the per-process VM root page directory via amdgpu_vm_lock_pd(). All three are released by the out_exec label that every other error path in this function jumps to. The kvcalloc() failure path returns -ENOMEM directly, skipping out_exec and leaking all three. The leaked per-process VM root PD dma_resv lock is the load-bearing leak: any subsequent operation on the same VM (further GEM ops, command-submission, eviction, TTM shrinker callbacks) blocks on the held lock. DRM_IOCTL_AMDGPU_GEM_OP is DRM_AUTH | DRM_RENDER_ALLOW, so this is an unprivileged-local denial of service against the caller's GPU context, reachable by any process with /dev/dri/renderD* access. Route the failure through out_exec so drm_exec_fini() and drm_gem_object_put() run. Reproduced on stock 7.0.0-10, Ryzen 7 5700U / Radeon Vega (Lucienne): the failing ioctl returns -ENOMEM and a second GET_MAPPING_INFO on the same fd then blocks in drm_exec_lock_obj() on the leaked dma_resv. SIGKILL on the caller does not reap the task; the fd-release path during process exit goes through amdgpu_gem_object_close() -> drm_exec_prepare_obj() on the same lock, leaving the task in D state until the box is rebooted. The patched kernel was not rebuilt and re-tested on this hardware; the fix is mechanical. Tested on a single Lucienne / Vega box only. Ziyi Guo posted an independent INT_MAX-bound check for args->num_entries in the same branch [1]; the two patches are complementary and can land in either order. Fixes: 4d82724f7f2b ("drm/amdgpu: Add mapping info option for GEM_OP ioctl") Link: https://lore.kernel.org/all/20260208000255.4073363-1-n7l8m4@u.northwestern.edu/ # [1] Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 27be5083f2af..aff4912ed95e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -1095,8 +1095,10 @@ int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data, * be retried. */ vm_entries = kvcalloc(args->num_entries, sizeof(*vm_entries), GFP_KERNEL); - if (!vm_entries) - return -ENOMEM; + if (!vm_entries) { + r = -ENOMEM; + goto out_exec; + } amdgpu_vm_bo_va_for_each_valid_mapping(bo_va, mapping) { if (num_mappings < args->num_entries) { From 1fe7bf5457f6efd7be60b17e23163ba54341d73d Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Sun, 8 Feb 2026 00:02:55 +0000 Subject: [PATCH 11/58] drm/amdgpu: check num_entries in GEM_OP GET_MAPPING_INFO kvcalloc(args->num_entries, sizeof(*vm_entries), GFP_KERNEL) at amdgpu_gem.c:1050 uses the user-supplied num_entries directly without any upper bounds check. Since num_entries is a __u32 and sizeof(drm_amdgpu_gem_vm_entry) is 32 bytes, a large num_entries produces an allocation exceeding INT_MAX, triggering WARNING in __kvmalloc_node_noprof(), causing a kernel WARNING, TAINT_WARN, and panic on CONFIG_PANIC_ON_WARN=y systems. Add a size bounds check before we invoke the kvzalloc() to reject oversized num_entries early with -EINVAL. Fixes: 4d82724f7f2b ("drm/amdgpu: Add mapping info option for GEM_OP ioctl") Signed-off-by: Ziyi Guo Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index aff4912ed95e..1120f8225ac0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -1094,6 +1094,11 @@ int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data, * If that number is larger than the size of the array, the ioctl must * be retried. */ + if (args->num_entries > INT_MAX / sizeof(*vm_entries)) { + r = -EINVAL; + goto out_exec; + } + vm_entries = kvcalloc(args->num_entries, sizeof(*vm_entries), GFP_KERNEL); if (!vm_entries) { r = -ENOMEM; From 72fdf627a07e2880719e1d606eda7b1e5418628a Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 14 May 2026 12:38:08 +0200 Subject: [PATCH 12/58] drm/amd/display: Replace use of system_unbound_wq with system_dfl_wq This patch continues the effort to refactor workqueue APIs, which has begun with the changes introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The point of the refactoring is to eventually alter the default behavior of workqueues to become unbound by default so that their workload placement is optimized by the scheduler. Before that to happen, workqueue users must be converted to the better named new workqueues with no intended behaviour changes: system_wq -> system_percpu_wq system_unbound_wq -> system_dfl_wq This way the old obsolete workqueues (system_wq, system_unbound_wq) can be removed in the future. Cc: Ray Wu Cc: Harry Wentland Cc: Leo Li Cc: Rodrigo Siqueira Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c index 1324b55c403c..06594fbfceee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c @@ -383,7 +383,7 @@ static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state( } /* Schedule worker */ - mod_delayed_work(system_unbound_wq, &ism->delayed_work, + mod_delayed_work(system_dfl_wq, &ism->delayed_work, nsecs_to_jiffies(delay_ns)); break; @@ -399,14 +399,14 @@ static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state( * have a negative power impact. Skip idle allow here, * and let the sso_delayed_work handle it. */ - mod_delayed_work(system_unbound_wq, + mod_delayed_work(system_dfl_wq, &ism->sso_delayed_work, nsecs_to_jiffies(sso_delay_ns)); } else { /* Enable idle optimization without SSO */ dm_ism_commit_idle_optimization_state( ism, acrtc_state->stream, false, false); - mod_delayed_work(system_unbound_wq, + mod_delayed_work(system_dfl_wq, &ism->sso_delayed_work, nsecs_to_jiffies(sso_delay_ns)); } From dbb3907d8b3ee68b6c60685671ad0c28c3abc500 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 14 May 2026 12:38:09 +0200 Subject: [PATCH 13/58] drm/amdgpu: Replace use of system_unbound_wq with system_dfl_wq This patch continues the effort to refactor workqueue APIs, which has begun with the changes introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The point of the refactoring is to eventually alter the default behavior of workqueues to become unbound by default so that their workload placement is optimized by the scheduler. Before that to happen, workqueue users must be converted to the better named new workqueues with no intended behaviour changes: system_wq -> system_percpu_wq system_unbound_wq -> system_dfl_wq This way the old obsolete workqueues (system_wq, system_unbound_wq) can be removed in the future. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index d386bc775d03..0811593fca7f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -586,7 +586,7 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, */ adev->coredump = coredump; /* Kick off coredump formatting to a worker thread. */ - queue_work(system_unbound_wq, &adev->coredump_work); + queue_work(system_dfl_wq, &adev->coredump_work); drm_info(dev, "AMDGPU device coredump file has been created\n"); drm_info(dev, "Check your /sys/class/drm/card%d/device/devcoredump/data\n", From 8c7506f7ba945f21e5abe7f8eac0a3acca6b5330 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 19 May 2026 15:02:00 +0530 Subject: [PATCH 14/58] drm/amdgpu/userq: Fix doorbell object cleanup of queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unpin and unref the door bell obj if queue creation fails before initialization is complete. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index b634defb0be8..8ea8b1cc4fde 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -787,7 +787,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) r = uq_funcs->mqd_create(queue, &args->in); if (r) { drm_file_err(uq_mgr->file, "Failed to create Queue\n"); - goto clean_mapping; + goto clean_doorbell_bo; } /* Update VM owner at userq submit-time for page-fault attribution. */ @@ -808,7 +808,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) if (r) { drm_file_err(uq_mgr->file, "Failed to map Queue\n"); mutex_unlock(&uq_mgr->userq_mutex); - goto clean_doorbell; + goto erase_doorbell; } } @@ -831,10 +831,15 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) args->out.queue_id = qid; return 0; -clean_doorbell: +erase_doorbell: xa_erase_irq(&adev->userq_doorbell_xa, index); clean_mqd: uq_funcs->mqd_destroy(queue); +clean_doorbell_bo: + amdgpu_bo_reserve(queue->db_obj.obj, true); + amdgpu_bo_unpin(queue->db_obj.obj); + amdgpu_bo_unreserve(queue->db_obj.obj); + amdgpu_bo_unref(&queue->db_obj.obj); clean_mapping: amdgpu_bo_reserve(fpriv->vm.root.bo, true); amdgpu_userq_buffer_vas_list_cleanup(adev, queue); From e54a822b61e8dfea5587567dbf035ad41e6a09ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:05 +0200 Subject: [PATCH 15/58] drm/amd/display: Delete unimplemented dm_pp_apply_power_level_change_request() (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dm_pp_apply_power_level_change_request() was called from old DCE clock manager implementations on DCE6, 8, 10, 11.2 but has not been implemented ever since the beginning of DC. Affected GPUs have been working fine without that implementation for many years. Let's delete it now. v2: - Delete dm_pp_apply_power_level_change_request too Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 8 -------- .../gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c | 9 --------- .../drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c | 9 --------- .../drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c | 9 --------- drivers/gpu/drm/amd/display/dc/dm_services.h | 4 ---- drivers/gpu/drm/amd/display/dc/dm_services_types.h | 4 ---- 6 files changed, 43 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 11b2ea6edf95..17f42201ab86 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -417,14 +417,6 @@ bool dm_pp_notify_wm_clock_changes( return false; } -bool dm_pp_apply_power_level_change_request( - const struct dc_context *ctx, - struct dm_pp_power_level_change_request *level_change_req) -{ - /* TODO: to be implemented */ - return false; -} - bool dm_pp_apply_clock_for_voltage_request( const struct dc_context *ctx, struct dm_pp_clock_for_voltage_req *clock_for_voltage_req) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c index 808e24f0e88f..4ccc6c742d00 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c @@ -431,19 +431,10 @@ static void dce_update_clocks(struct clk_mgr *clk_mgr_base, bool safe_to_lower) { struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base); - struct dm_pp_power_level_change_request level_change_req; const int max_disp_clk = clk_mgr_dce->max_clks_by_state[DM_PP_CLOCKS_STATE_PERFORMANCE].display_clk_khz; int patched_disp_clk = MIN(max_disp_clk, context->bw_ctx.bw.dce.dispclk_khz); - level_change_req.power_level = dce_get_required_clocks_state(clk_mgr_base, context); - /* get max clock state from PPLIB */ - if ((level_change_req.power_level < clk_mgr_dce->cur_min_clks_state && safe_to_lower) - || level_change_req.power_level > clk_mgr_dce->cur_min_clks_state) { - if (dm_pp_apply_power_level_change_request(clk_mgr_base->ctx, &level_change_req)) - clk_mgr_dce->cur_min_clks_state = level_change_req.power_level; - } - if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) { patched_disp_clk = dce_set_clock(clk_mgr_base, patched_disp_clk); clk_mgr_base->clks.dispclk_khz = patched_disp_clk; diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c index b35a44976477..d3cd542063db 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c @@ -257,21 +257,12 @@ static void dce11_update_clocks(struct clk_mgr *clk_mgr_base, bool safe_to_lower) { struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base); - struct dm_pp_power_level_change_request level_change_req; int patched_disp_clk = context->bw_ctx.bw.dce.dispclk_khz; /*TODO: W/A for dal3 linux, investigate why this works */ if (!clk_mgr_dce->dfs_bypass_active) patched_disp_clk = patched_disp_clk * 115 / 100; - level_change_req.power_level = dce_get_required_clocks_state(clk_mgr_base, context); - /* get max clock state from PPLIB */ - if ((level_change_req.power_level < clk_mgr_dce->cur_min_clks_state && safe_to_lower) - || level_change_req.power_level > clk_mgr_dce->cur_min_clks_state) { - if (dm_pp_apply_power_level_change_request(clk_mgr_base->ctx, &level_change_req)) - clk_mgr_dce->cur_min_clks_state = level_change_req.power_level; - } - if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) { context->bw_ctx.bw.dce.dispclk_khz = dce_set_clock(clk_mgr_base, patched_disp_clk); clk_mgr_base->clks.dispclk_khz = patched_disp_clk; diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c index 1f36ad8a7de4..48393c69735b 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c @@ -193,21 +193,12 @@ static void dce112_update_clocks(struct clk_mgr *clk_mgr_base, bool safe_to_lower) { struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base); - struct dm_pp_power_level_change_request level_change_req; int patched_disp_clk = context->bw_ctx.bw.dce.dispclk_khz; /*TODO: W/A for dal3 linux, investigate why this works */ if (!clk_mgr_dce->dfs_bypass_active) patched_disp_clk = patched_disp_clk * 115 / 100; - level_change_req.power_level = dce_get_required_clocks_state(clk_mgr_base, context); - /* get max clock state from PPLIB */ - if ((level_change_req.power_level < clk_mgr_dce->cur_min_clks_state && safe_to_lower) - || level_change_req.power_level > clk_mgr_dce->cur_min_clks_state) { - if (dm_pp_apply_power_level_change_request(clk_mgr_base->ctx, &level_change_req)) - clk_mgr_dce->cur_min_clks_state = level_change_req.power_level; - } - if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) { patched_disp_clk = dce112_set_clock(clk_mgr_base, patched_disp_clk); clk_mgr_base->clks.dispclk_khz = patched_disp_clk; diff --git a/drivers/gpu/drm/amd/display/dc/dm_services.h b/drivers/gpu/drm/amd/display/dc/dm_services.h index fbbf9c757b3c..1395d36bfabe 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services.h @@ -224,10 +224,6 @@ bool dm_pp_apply_display_requirements( const struct dc_context *ctx, const struct dm_pp_display_configuration *pp_display_cfg); -bool dm_pp_apply_power_level_change_request( - const struct dc_context *ctx, - struct dm_pp_power_level_change_request *level_change_req); - bool dm_pp_apply_clock_for_voltage_request( const struct dc_context *ctx, struct dm_pp_clock_for_voltage_req *clock_for_voltage_req); diff --git a/drivers/gpu/drm/amd/display/dc/dm_services_types.h b/drivers/gpu/drm/amd/display/dc/dm_services_types.h index 3b093b8699ab..cae3ed6056d6 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services_types.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services_types.h @@ -246,10 +246,6 @@ enum dm_acpi_display_type { AcpiDisplayType_DFP6 = 12 }; -struct dm_pp_power_level_change_request { - enum dm_pp_clocks_state power_level; -}; - struct dm_pp_clock_for_voltage_req { enum dm_pp_clock_type clk_type; uint32_t clocks_in_khz; From 384dbef269d101e5b671fc7b942c56734cd1d186 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 19 May 2026 15:12:42 +0530 Subject: [PATCH 16/58] drm/amdgpu/userq: Fix the mutex_init cleanup for fence_drv_lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mutex fence_drv_lock is destroyed in amdgpu_userq_fence_driver_free also in one of the jump condition mutex_destroy is also called leading to double mutex_destroy. So rearranging the code so amdgpu_userq_fence_driver_free takes care of the clean up along with mutex_destroy. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 8ea8b1cc4fde..af9904f9d703 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -748,12 +748,12 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) INIT_DELAYED_WORK(&queue->hang_detect_work, amdgpu_userq_hang_detect_work); - mutex_init(&queue->fence_drv_lock); - xa_init_flags(&queue->fence_drv_xa, XA_FLAGS_ALLOC); r = amdgpu_userq_fence_driver_alloc(adev, &queue->fence_drv); if (r) goto free_queue; + xa_init_flags(&queue->fence_drv_xa, XA_FLAGS_ALLOC); + mutex_init(&queue->fence_drv_lock); /* Make sure the queue can actually run with those virtual addresses. */ r = amdgpu_bo_reserve(fpriv->vm.root.bo, false); if (r) @@ -844,7 +844,6 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) amdgpu_bo_reserve(fpriv->vm.root.bo, true); amdgpu_userq_buffer_vas_list_cleanup(adev, queue); amdgpu_bo_unreserve(fpriv->vm.root.bo); - mutex_destroy(&queue->fence_drv_lock); free_fence_drv: amdgpu_userq_fence_driver_free(queue); free_queue: From 08236c3ef284cd2d110e5e3d51fc9615e551f9dc Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Thu, 14 May 2026 11:53:50 -0400 Subject: [PATCH 17/58] drm/amd/display: Write REFCLK to 48MHz on DCN21 [Why&How] dccg21_init() calls dccg2_init() which hardcodes 100MHz refclk values for MICROSECOND_TIME_BASE_DIV and MILLISECOND_TIME_BASE_DIV. DCN21 uses 48MHz refclk, so the wrong values corrupt DCCG timing and cause eDP link training failure on cold boot. Write the correct 48MHz values directly instead of calling dccg2_init(). v2: Fixed typo Fixes: e6e2b956fc81 ("drm/amd/display: Add missing DCCG register entries for DCN20-DCN316") Reported-by: Max Chernoff Tested-by: Max Chernoff Signed-off-by: Ivan Lipski Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dccg/dcn21/dcn21_dccg.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn21/dcn21_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn21/dcn21_dccg.c index c4d4eea140f3..1f23dfccf07a 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn21/dcn21_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn21/dcn21_dccg.c @@ -105,15 +105,26 @@ static void dccg21_update_dpp_dto(struct dccg *dccg, int dpp_inst, int req_dppcl * dccg2_init() unconditionally overwrites MICROSECOND_TIME_BASE_DIV to * 0x00120264, destroying the marker before it can be read. * - * Guard the call: if the S0i3 marker is present, skip dccg2_init() so the + * Guard the call: if the S0i3 marker is present, skip init so the * WA can function correctly. bios_golden_init() will handle init in that case. + * + * DCN21 uses 48MHz refclk, not 100MHz, so we must explicitly set the correct + * values (48MHz is taken from rn_clk_mgr_construct()). */ static void dccg21_init(struct dccg *dccg) { + struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); + if (dccg2_is_s0i3_golden_init_wa_done(dccg)) return; - dccg2_init(dccg); + /* 48MHz refclk from rn_clk_mgr_construct() */ + REG_WRITE(MICROSECOND_TIME_BASE_DIV, 0x00120230); + REG_WRITE(MILLISECOND_TIME_BASE_DIV, 0x0010bb80); + REG_WRITE(DISPCLK_FREQ_CHANGE_CNTL, 0x0e01003c); + + if (REG(REFCLK_CNTL)) + REG_WRITE(REFCLK_CNTL, 0); } static const struct dccg_funcs dccg21_funcs = { From 83a26c812e0529eb040d31a76f73e33e637243d4 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Thu, 7 May 2026 15:51:49 -0400 Subject: [PATCH 18/58] drm/amdkfd: fix NULL pointer bug in svm_range_set_attr The process_info could be NULL if user doesn't call kfd_ioctl_acquire_vm before calling kfd_ioctl_svm. Signed-off-by: Eric Huang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 35ec67d9739b..3841943da5ec 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -3732,6 +3732,9 @@ svm_range_set_attr(struct kfd_process *p, struct mm_struct *mm, svms = &p->svms; + if (!process_info) + return -EINVAL; + mutex_lock(&process_info->lock); svm_range_list_lock_and_flush_work(svms, mm); From e947ec9d0529d5f93dbdb33cd197347f6a7b2922 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 18 May 2026 17:42:15 +0530 Subject: [PATCH 19/58] drm/amdgpu: simplify return value in amdgpu_userq_get_doorbell_index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_get_doorbell_index returns a uint64 type index as well as a int type failure values. Simplifying this and using a int type return value and getting the index in input pointer of type uint64 type. Also since it's used at once place making it static would be better. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 21 +++++++++++---------- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 4 ---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index af9904f9d703..3a1d08032450 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -536,12 +536,13 @@ void amdgpu_userq_destroy_object(struct amdgpu_userq_mgr *uq_mgr, amdgpu_bo_unref(&userq_obj->obj); } -uint64_t +static int amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_db_info *db_info, - struct drm_file *filp) + struct drm_file *filp, + u64 *index) { - uint64_t index; + u64 doorbell_index; struct drm_gem_object *gobj; struct amdgpu_userq_obj *db_obj = db_info->db_obj; int r, db_size; @@ -588,12 +589,13 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, goto unpin_bo; } - index = amdgpu_doorbell_index_on_bar(uq_mgr->adev, db_obj->obj, - db_info->doorbell_offset, db_size); + doorbell_index = amdgpu_doorbell_index_on_bar(uq_mgr->adev, db_obj->obj, + db_info->doorbell_offset, db_size); drm_dbg_driver(adev_to_drm(uq_mgr->adev), - "[Usermode queues] doorbell index=%lld\n", index); + "[Usermode queues] doorbell index=%lld\n", doorbell_index); amdgpu_bo_unreserve(db_obj->obj); - return index; + *index = doorbell_index; + return 0; unpin_bo: amdgpu_bo_unpin(db_obj->obj); @@ -776,10 +778,9 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) db_info.doorbell_handle = queue->doorbell_handle; db_info.db_obj = &queue->db_obj; db_info.doorbell_offset = args->in.doorbell_offset; - index = amdgpu_userq_get_doorbell_index(uq_mgr, &db_info, filp); - if (index == (uint64_t)-EINVAL) { + r = amdgpu_userq_get_doorbell_index(uq_mgr, &db_info, filp, &index); + if (r) { drm_file_err(uq_mgr->file, "Failed to get doorbell for queue\n"); - r = -EINVAL; goto clean_mapping; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 49b33e2d6932..033b8a0de6b1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -163,10 +163,6 @@ void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_ensure_ev_fence(struct amdgpu_userq_mgr *userq_mgr, struct amdgpu_eviction_fence_mgr *evf_mgr); -uint64_t amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, - struct amdgpu_db_info *db_info, - struct drm_file *filp); - u32 amdgpu_userq_get_supported_ip_mask(struct amdgpu_device *adev); bool amdgpu_userq_enabled(struct drm_device *dev); From d8145c437ccdc2d91c579787290f82788172bea0 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 18 May 2026 18:33:00 +0530 Subject: [PATCH 20/58] drm/amdgpu/userq: add amdgpu_bo_unpin when amdgpu_ttm_alloc_gart fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unpin the wptr_obj->obj when amdgpu_ttm_alloc_gart fails. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 9bb8f7330537..4eaf98ba757a 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -81,7 +81,7 @@ mes_userq_create_wptr_mapping(struct amdgpu_device *adev, ret = amdgpu_ttm_alloc_gart(&wptr_obj->obj->tbo); if (ret) { DRM_ERROR("Failed to bind bo to GART. ret %d\n", ret); - goto fail_map; + goto fail_alloc_gart; } queue->wptr_obj.gpu_addr = amdgpu_bo_gpu_offset(wptr_obj->obj); @@ -89,6 +89,8 @@ mes_userq_create_wptr_mapping(struct amdgpu_device *adev, drm_exec_fini(&exec); return 0; +fail_alloc_gart: + amdgpu_bo_unpin(wptr_obj->obj); fail_map: amdgpu_bo_unref(&wptr_obj->obj); fail_lock: From d409ab4e387d94b2e593d558b54b7bfd315e0e75 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 18 May 2026 18:55:25 +0530 Subject: [PATCH 21/58] drm/amdgpu/userq: reserve root bo without interruption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the code to make it an uninterruptible reservation for root bo. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 3a1d08032450..e5aa830f8a46 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -620,11 +620,7 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que /* Cancel any pending hang detection work and cleanup */ cancel_delayed_work_sync(&queue->hang_detect_work); - r = amdgpu_bo_reserve(vm->root.bo, false); - if (r) { - drm_file_err(uq_mgr->file, "Failed to reserve root bo during userqueue destroy\n"); - return r; - } + amdgpu_bo_reserve(vm->root.bo, true); amdgpu_userq_buffer_vas_list_cleanup(adev, queue); amdgpu_bo_unreserve(vm->root.bo); From 1a66ceb98b137d18d303b9889f0e7d8c4db73943 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 18 May 2026 19:58:08 +0530 Subject: [PATCH 22/58] drm/amdgpu/userq: make sure queue is valid in the hang_detect_work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thread 1: Running amdgpu_userq_destroy which eventually remove the queue from door bell and set userq_mgr = NULL. Thread2: An interrupt might have scheduled the hang_detect_work which still need userq_mgr to be valid but could get an NULL ptrs. To fix that make sure we cancel the hang_detect_work again before setting userq_mgr to NULL. Along with that we also need all the queue va to remain valid till we could be running anything on the queue and hence moving the userq_va post hang_detect handler is cancelled. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index e5aa830f8a46..6840e2eb3c4f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -427,8 +427,6 @@ static void amdgpu_userq_cleanup(struct amdgpu_usermode_queue *queue) xa_erase_irq(&adev->userq_doorbell_xa, queue->doorbell_index); amdgpu_userq_fence_driver_free(queue); queue->fence_drv = NULL; - queue->userq_mgr = NULL; - list_del(&queue->userq_va_list); up_read(&adev->reset_domain->sem); } @@ -619,11 +617,6 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que /* Cancel any pending hang detection work and cleanup */ cancel_delayed_work_sync(&queue->hang_detect_work); - - amdgpu_bo_reserve(vm->root.bo, true); - amdgpu_userq_buffer_vas_list_cleanup(adev, queue); - amdgpu_bo_unreserve(vm->root.bo); - mutex_lock(&uq_mgr->userq_mutex); amdgpu_userq_wait_for_last_fence(queue); @@ -635,6 +628,13 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que amdgpu_userq_cleanup(queue); mutex_unlock(&uq_mgr->userq_mutex); + cancel_delayed_work_sync(&queue->hang_detect_work); + amdgpu_bo_reserve(vm->root.bo, true); + amdgpu_userq_buffer_vas_list_cleanup(adev, queue); + amdgpu_bo_unreserve(vm->root.bo); + list_del(&queue->userq_va_list); + queue->userq_mgr = NULL; + amdgpu_bo_reserve(queue->db_obj.obj, true); amdgpu_bo_unpin(queue->db_obj.obj); amdgpu_bo_unreserve(queue->db_obj.obj); From 1a58070fda26857a8f6acc0ab05428e60d5c6844 Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Mon, 11 May 2026 16:49:19 +0800 Subject: [PATCH 23/58] drm/amdgpu: fix potential overflow in fs_info.debugfs_name Use snprintf() with sizeof(fs_info.debugfs_name) so a long RAS block name plus the "_err_inject" suffix cannot overflow the 32-byte buffer. Signed-off-by: Stanley.Yang Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 3e1f8b15f75d..093c0bf760c1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -2280,7 +2280,8 @@ void amdgpu_ras_debugfs_create_all(struct amdgpu_device *adev) list_for_each_entry(obj, &con->head, node) { if (amdgpu_ras_is_supported(adev, obj->head.block) && (obj->attr_inuse == 1)) { - sprintf(fs_info.debugfs_name, "%s_err_inject", + snprintf(fs_info.debugfs_name, sizeof(fs_info.debugfs_name), + "%s_err_inject", get_ras_block_str(&obj->head)); fs_info.head = obj->head; amdgpu_ras_debugfs_create(adev, &fs_info, dir); From 2bab781dac78916c5cc8de76345a4102449267d7 Mon Sep 17 00:00:00 2001 From: David Francis Date: Thu, 14 May 2026 10:31:20 -0400 Subject: [PATCH 24/58] drm/amdkfd: Check for pdd drm file first in CRIU restore path CRIU restore ioctls are meant to be called by CRIU with no existing drm file. There's an error path for if the drm file unexpectedly exists. It was positioned so it was missing a fput(drm_file). Do that check earlier, as soon as we have the pdd. Signed-off-by: David Francis Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 81bcb16eb6dd..a2b100d14425 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -2399,6 +2399,11 @@ static int criu_restore_devices(struct kfd_process *p, ret = -EINVAL; goto exit; } + + if (pdd->drm_file) { + ret = -EINVAL; + goto exit; + } pdd->user_gpu_id = device_buckets[i].user_gpu_id; drm_file = fget(device_buckets[i].drm_fd); @@ -2409,11 +2414,6 @@ static int criu_restore_devices(struct kfd_process *p, goto exit; } - if (pdd->drm_file) { - ret = -EINVAL; - goto exit; - } - /* create the vm using render nodes for kfd pdd */ if (kfd_process_device_init_vm(pdd, drm_file)) { pr_err("could not init vm for given pdd\n"); From 36ddabadd8b418cbca8e31d537f16bbf635c17f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:06 +0200 Subject: [PATCH 25/58] drm/amd/display: Delete dce_get_required_clocks_state() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is not called from anywhere anymore. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../display/dc/clk_mgr/dce100/dce_clk_mgr.c | 34 ------------------- .../display/dc/clk_mgr/dce100/dce_clk_mgr.h | 3 -- 2 files changed, 37 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c index 4ccc6c742d00..e8daee975b79 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c @@ -220,40 +220,6 @@ uint32_t dce_get_max_pixel_clock_for_all_paths(struct dc_state *context) return max_pix_clk; } -enum dm_pp_clocks_state dce_get_required_clocks_state( - struct clk_mgr *clk_mgr_base, - struct dc_state *context) -{ - struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base); - int i; - enum dm_pp_clocks_state low_req_clk; - int max_pix_clk = dce_get_max_pixel_clock_for_all_paths(context); - - /* Iterate from highest supported to lowest valid state, and update - * lowest RequiredState with the lowest state that satisfies - * all required clocks - */ - for (i = clk_mgr_dce->max_clks_state; i >= DM_PP_CLOCKS_STATE_ULTRA_LOW; i--) - if (context->bw_ctx.bw.dce.dispclk_khz > - clk_mgr_dce->max_clks_by_state[i].display_clk_khz - || max_pix_clk > - clk_mgr_dce->max_clks_by_state[i].pixel_clk_khz) - break; - - low_req_clk = i + 1; - if (low_req_clk > clk_mgr_dce->max_clks_state) { - /* set max clock state for high phyclock, invalid on exceeding display clock */ - if (clk_mgr_dce->max_clks_by_state[clk_mgr_dce->max_clks_state].display_clk_khz - < context->bw_ctx.bw.dce.dispclk_khz) - low_req_clk = DM_PP_CLOCKS_STATE_INVALID; - else - low_req_clk = clk_mgr_dce->max_clks_state; - } - - return low_req_clk; -} - - /* TODO: remove use the two broken down functions */ int dce_set_clock( struct clk_mgr *clk_mgr_base, diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h index 9ea1b0a9923d..e1c6a92d7957 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h @@ -32,9 +32,6 @@ /* functions shared by other dce clk mgrs */ int dce_adjust_dp_ref_freq_for_ss(struct clk_mgr_internal *clk_mgr_dce, int dp_ref_clk_khz); int dce_get_dp_ref_freq_khz(struct clk_mgr *clk_mgr_base); -enum dm_pp_clocks_state dce_get_required_clocks_state( - struct clk_mgr *clk_mgr_base, - struct dc_state *context); uint32_t dce_get_max_pixel_clock_for_all_paths(struct dc_state *context); From 1a77e14ab3ba46b5cda97387464f67ecf8287e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:07 +0200 Subject: [PATCH 26/58] drm/amd/display: Remove min/max clock levels from clk_mgr (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These fields are not used by anything anymore. v2: - Delete dm_pp_get_static_clocks() - Delete pp_to_dc_powerlevel_state() Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 44 ------------------- .../display/dc/clk_mgr/dce100/dce_clk_mgr.c | 14 ------ .../dc/clk_mgr/dce112/dce112_clk_mgr.c | 15 ------- drivers/gpu/drm/amd/display/dc/dm_services.h | 4 -- .../amd/display/dc/inc/hw/clk_mgr_internal.h | 2 - 5 files changed, 79 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 17f42201ab86..a3ee580d8dd1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -183,33 +183,6 @@ static enum amd_pp_clock_type dc_to_pp_clock_type( return amd_pp_clk_type; } -static enum dm_pp_clocks_state pp_to_dc_powerlevel_state( - enum PP_DAL_POWERLEVEL max_clocks_state) -{ - switch (max_clocks_state) { - case PP_DAL_POWERLEVEL_0: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_0; - case PP_DAL_POWERLEVEL_1: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_1; - case PP_DAL_POWERLEVEL_2: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_2; - case PP_DAL_POWERLEVEL_3: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_3; - case PP_DAL_POWERLEVEL_4: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_4; - case PP_DAL_POWERLEVEL_5: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_5; - case PP_DAL_POWERLEVEL_6: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_6; - case PP_DAL_POWERLEVEL_7: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_7; - default: - DRM_ERROR("DM_PPLIB: invalid powerlevel state: %d!\n", - max_clocks_state); - return DM_PP_CLOCKS_STATE_INVALID; - } -} - static void pp_to_dc_clock_levels( const struct amd_pp_clocks *pp_clks, struct dm_pp_clock_levels *dc_clks, @@ -438,23 +411,6 @@ bool dm_pp_apply_clock_for_voltage_request( return true; } -bool dm_pp_get_static_clocks( - const struct dc_context *ctx, - struct dm_pp_static_clock_info *static_clk_info) -{ - struct amdgpu_device *adev = ctx->driver_context; - struct amd_pp_clock_info pp_clk_info = {0}; - - if (amdgpu_dpm_get_current_clocks(adev, &pp_clk_info)) - return false; - - static_clk_info->max_clocks_state = pp_to_dc_powerlevel_state(pp_clk_info.max_clocks_state); - static_clk_info->max_mclk_khz = pp_clk_info.max_memory_clock * 10; - static_clk_info->max_sclk_khz = pp_clk_info.max_engine_clock * 10; - - return true; -} - static void pp_rv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges) { diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c index e8daee975b79..b7b72af9570a 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c @@ -257,11 +257,6 @@ int dce_set_clock( actual_clock = pxl_clk_params.dfs_bypass_display_clock; } - /* from power down, we need mark the clock state as ClocksStateNominal - * from HWReset, so when resume we will call pplib voltage regulator.*/ - if (requested_clk_khz == 0) - clk_mgr_dce->cur_min_clks_state = DM_PP_CLOCKS_STATE_NOMINAL; - if (dmcu && dmcu->funcs->is_dmcu_initialized(dmcu)) dmcu->funcs->set_psr_wait_loop(dmcu, actual_clock / 1000 / 7); @@ -425,7 +420,6 @@ void dce_clk_mgr_construct( struct clk_mgr_internal *clk_mgr) { struct clk_mgr *base = &clk_mgr->base; - struct dm_pp_static_clock_info static_clk_info = {0}; if (ctx->dce_version <= DCE_VERSION_6_4) memcpy(clk_mgr->max_clks_by_state, @@ -451,14 +445,6 @@ void dce_clk_mgr_construct( clk_mgr->dprefclk_ss_divider = 1000; clk_mgr->ss_on_dprefclk = false; - if (ctx->dce_version >= DCE_VERSION_8_0) { - if (dm_pp_get_static_clocks(ctx, &static_clk_info)) - clk_mgr->max_clks_state = static_clk_info.max_clocks_state; - else - clk_mgr->max_clks_state = DM_PP_CLOCKS_STATE_NOMINAL; - clk_mgr->cur_min_clks_state = DM_PP_CLOCKS_STATE_INVALID; - } - base->clks.max_supported_dispclk_khz = clk_mgr->max_clks_by_state[DM_PP_CLOCKS_STATE_PERFORMANCE].display_clk_khz; diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c index 48393c69735b..0f3f8df4df96 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c @@ -89,13 +89,6 @@ int dce112_set_clock(struct clk_mgr *clk_mgr_base, int requested_clk_khz) bp->funcs->set_dce_clock(bp, &dce_clk_params); actual_clock = dce_clk_params.target_clock_frequency; - /* - * from power down, we need mark the clock state as ClocksStateNominal - * from HWReset, so when resume we will call pplib voltage regulator. - */ - if (requested_clk_khz == 0) - clk_mgr_dce->cur_min_clks_state = DM_PP_CLOCKS_STATE_NOMINAL; - /*Program DP ref Clock*/ /*VBIOS will determine DPREFCLK frequency, so we don't set it*/ dce_clk_params.target_clock_frequency = 0; @@ -143,14 +136,6 @@ int dce112_set_dispclk(struct clk_mgr_internal *clk_mgr, int requested_clk_khz) bp->funcs->set_dce_clock(bp, &dce_clk_params); actual_clock = dce_clk_params.target_clock_frequency; - /* - * from power down, we need mark the clock state as ClocksStateNominal - * from HWReset, so when resume we will call pplib voltage regulator. - */ - if (requested_clk_khz == 0) - clk_mgr->cur_min_clks_state = DM_PP_CLOCKS_STATE_NOMINAL; - - if (dmcu && dmcu->funcs->is_dmcu_initialized(dmcu)) { if (clk_mgr->dfs_bypass_disp_clk != actual_clock) dmcu->funcs->set_psr_wait_loop(dmcu, diff --git a/drivers/gpu/drm/amd/display/dc/dm_services.h b/drivers/gpu/drm/amd/display/dc/dm_services.h index 1395d36bfabe..8b062b011fc6 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services.h @@ -228,10 +228,6 @@ bool dm_pp_apply_clock_for_voltage_request( const struct dc_context *ctx, struct dm_pp_clock_for_voltage_req *clock_for_voltage_req); -bool dm_pp_get_static_clocks( - const struct dc_context *ctx, - struct dm_pp_static_clock_info *static_clk_info); - /****** end of PP interfaces ******/ struct persistent_data_flag { diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h index c69ccfcebeb5..e01bf6bd7f3f 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h @@ -477,8 +477,6 @@ struct clk_mgr_internal { */ int dprefclk_ss_divider; - enum dm_pp_clocks_state max_clks_state; - enum dm_pp_clocks_state cur_min_clks_state; bool periodic_retraining_disabled; unsigned int cur_phyclk_req_table[MAX_LINKS]; From b4b7d7f932452473582c2893ada704f8d0a2b407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:08 +0200 Subject: [PATCH 27/58] drm/amd/display: Delete max_clocks_state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's not used by anything anymore. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dm_services_types.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dm_services_types.h b/drivers/gpu/drm/amd/display/dc/dm_services_types.h index cae3ed6056d6..f114ab819afd 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services_types.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services_types.h @@ -254,9 +254,6 @@ struct dm_pp_clock_for_voltage_req { struct dm_pp_static_clock_info { uint32_t max_sclk_khz; uint32_t max_mclk_khz; - - /* max possible display block clocks state */ - enum dm_pp_clocks_state max_clocks_state; }; struct dtn_min_clk_info { From 7b6fbeac9c89e42da9f73827c1d4c686f0cf2fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:09 +0200 Subject: [PATCH 28/58] drm/amd/display: Set max supported display clock without max_clks_by_state (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The max_clks_by_state was based on hardcoded values, which are not really used anywhere, only to know the maximum clock. Just hardcode the same maximum clock for each DCE version. v2: - Use previous max display clock for DCE 11.2 Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c index b7b72af9570a..978bbc307991 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c @@ -391,9 +391,7 @@ static void dce_update_clocks(struct clk_mgr *clk_mgr_base, struct dc_state *context, bool safe_to_lower) { - struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base); - const int max_disp_clk = - clk_mgr_dce->max_clks_by_state[DM_PP_CLOCKS_STATE_PERFORMANCE].display_clk_khz; + const int max_disp_clk = clk_mgr_base->clks.max_supported_dispclk_khz; int patched_disp_clk = MIN(max_disp_clk, context->bw_ctx.bw.dce.dispclk_khz); if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) { @@ -445,8 +443,16 @@ void dce_clk_mgr_construct( clk_mgr->dprefclk_ss_divider = 1000; clk_mgr->ss_on_dprefclk = false; - base->clks.max_supported_dispclk_khz = - clk_mgr->max_clks_by_state[DM_PP_CLOCKS_STATE_PERFORMANCE].display_clk_khz; + if (ctx->dce_version >= DCE_VERSION_12_0) + base->clks.max_supported_dispclk_khz = 1133000; + else if (ctx->dce_version >= DCE_VERSION_11_2) + base->clks.max_supported_dispclk_khz = 1132000; + else if (ctx->dce_version >= DCE_VERSION_11_0) + base->clks.max_supported_dispclk_khz = 643000; + else if (ctx->dce_version >= DCE_VERSION_8_0) + base->clks.max_supported_dispclk_khz = 625000; + else + base->clks.max_supported_dispclk_khz = 600000; dce_clock_read_integrated_info(clk_mgr); dce_clock_read_ss_info(clk_mgr); From df85bf48732f6ba8aa011299f129c422bedd529e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:10 +0200 Subject: [PATCH 29/58] drm/amd/display: Delete max_clks_by_state from DCE clock manager (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was not used by anything anymore. Note that the parts of DC that need this information actually already query it from the pplib and don't use the hardcoded information from max_clks_by_state. v2: - Also delete state_dependent_clocks Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../display/dc/clk_mgr/dce100/dce_clk_mgr.c | 71 ------------------- .../dc/clk_mgr/dce110/dce110_clk_mgr.c | 16 ----- .../dc/clk_mgr/dce112/dce112_clk_mgr.c | 17 ----- .../dc/clk_mgr/dce120/dce120_clk_mgr.c | 16 ----- .../amd/display/dc/inc/hw/clk_mgr_internal.h | 8 --- 5 files changed, 128 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c index 978bbc307991..d891b3bfe2a1 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c @@ -62,32 +62,6 @@ static const struct clk_mgr_mask disp_clk_mask = { CLK_COMMON_MASK_SH_LIST_DCE_COMMON_BASE(_MASK) }; -/* Max clock values for each state indexed by "enum clocks_state": */ -static const struct state_dependent_clocks dce60_max_clks_by_state[] = { -/* ClocksStateInvalid - should not be used */ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/* ClocksStateUltraLow - not expected to be used for DCE 6.0 */ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/* ClocksStateLow */ -{ .display_clk_khz = 352000, .pixel_clk_khz = 330000}, -/* ClocksStateNominal */ -{ .display_clk_khz = 600000, .pixel_clk_khz = 400000 }, -/* ClocksStatePerformance */ -{ .display_clk_khz = 600000, .pixel_clk_khz = 400000 } }; - -/* Max clock values for each state indexed by "enum clocks_state": */ -static const struct state_dependent_clocks dce80_max_clks_by_state[] = { -/* ClocksStateInvalid - should not be used */ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/* ClocksStateUltraLow - not expected to be used for DCE 8.0 */ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/* ClocksStateLow */ -{ .display_clk_khz = 352000, .pixel_clk_khz = 330000}, -/* ClocksStateNominal */ -{ .display_clk_khz = 625000, .pixel_clk_khz = 400000 }, -/* ClocksStatePerformance */ -{ .display_clk_khz = 625000, .pixel_clk_khz = 400000 } }; - unsigned int dentist_get_divider_from_did(unsigned int did) { if (did < DENTIST_BASE_DID_1) @@ -268,7 +242,6 @@ static void dce_clock_read_integrated_info(struct clk_mgr_internal *clk_mgr_dce) { struct dc_debug_options *debug = &clk_mgr_dce->base.ctx->dc->debug; struct dc_bios *bp = clk_mgr_dce->base.ctx->dc_bios; - int i; if (bp->integrated_info) clk_mgr_dce->base.dentist_vco_freq_khz = bp->integrated_info->dentist_vco_freq; @@ -278,40 +251,6 @@ static void dce_clock_read_integrated_info(struct clk_mgr_internal *clk_mgr_dce) clk_mgr_dce->base.dentist_vco_freq_khz = 3600000; } - /*update the maximum display clock for each power state*/ - for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) { - enum dm_pp_clocks_state clk_state = DM_PP_CLOCKS_STATE_INVALID; - - switch (i) { - case 0: - clk_state = DM_PP_CLOCKS_STATE_ULTRA_LOW; - break; - - case 1: - clk_state = DM_PP_CLOCKS_STATE_LOW; - break; - - case 2: - clk_state = DM_PP_CLOCKS_STATE_NOMINAL; - break; - - case 3: - clk_state = DM_PP_CLOCKS_STATE_PERFORMANCE; - break; - - default: - clk_state = DM_PP_CLOCKS_STATE_INVALID; - break; - } - - /*Do not allow bad VBIOS/SBIOS to override with invalid values, - * check for > 100MHz*/ - if (bp->integrated_info) - if (bp->integrated_info->disp_clk_voltage[i].max_supported_clk >= 100000) - clk_mgr_dce->max_clks_by_state[clk_state].display_clk_khz = - bp->integrated_info->disp_clk_voltage[i].max_supported_clk; - } - if (!debug->disable_dfs_bypass && bp->integrated_info) if (bp->integrated_info->gpu_cap_info & DFS_BYPASS_ENABLE) clk_mgr_dce->dfs_bypass_enabled = true; @@ -419,16 +358,6 @@ void dce_clk_mgr_construct( { struct clk_mgr *base = &clk_mgr->base; - if (ctx->dce_version <= DCE_VERSION_6_4) - memcpy(clk_mgr->max_clks_by_state, - dce60_max_clks_by_state, - sizeof(dce60_max_clks_by_state)); - else - memcpy(clk_mgr->max_clks_by_state, - dce80_max_clks_by_state, - sizeof(dce80_max_clks_by_state)); - - base->ctx = ctx; base->funcs = &dce_funcs; diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c index d3cd542063db..d52789ba2613 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c @@ -51,18 +51,6 @@ static const struct clk_mgr_mask disp_clk_mask = { CLK_COMMON_MASK_SH_LIST_DCE_COMMON_BASE(_MASK) }; -static const struct state_dependent_clocks dce110_max_clks_by_state[] = { -/*ClocksStateInvalid - should not be used*/ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/*ClocksStateUltraLow - currently by HW design team not supposed to be used*/ -{ .display_clk_khz = 352000, .pixel_clk_khz = 330000 }, -/*ClocksStateLow*/ -{ .display_clk_khz = 352000, .pixel_clk_khz = 330000 }, -/*ClocksStateNominal*/ -{ .display_clk_khz = 467000, .pixel_clk_khz = 400000 }, -/*ClocksStatePerformance*/ -{ .display_clk_khz = 643000, .pixel_clk_khz = 400000 } }; - static uint32_t determine_sclk_from_bounding_box( const struct dc *dc, uint32_t required_sclk) @@ -281,10 +269,6 @@ void dce110_clk_mgr_construct( { dce_clk_mgr_construct(ctx, clk_mgr); - memcpy(clk_mgr->max_clks_by_state, - dce110_max_clks_by_state, - sizeof(dce110_max_clks_by_state)); - clk_mgr->regs = &disp_clk_regs; clk_mgr->clk_mgr_shift = &disp_clk_shift; clk_mgr->clk_mgr_mask = &disp_clk_mask; diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c index 0f3f8df4df96..08ed6f88025f 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c @@ -53,19 +53,6 @@ static const struct clk_mgr_mask disp_clk_mask = { CLK_COMMON_MASK_SH_LIST_DCE_COMMON_BASE(_MASK) }; -static const struct state_dependent_clocks dce112_max_clks_by_state[] = { -/*ClocksStateInvalid - should not be used*/ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/*ClocksStateUltraLow - currently by HW design team not supposed to be used*/ -{ .display_clk_khz = 389189, .pixel_clk_khz = 346672 }, -/*ClocksStateLow*/ -{ .display_clk_khz = 459000, .pixel_clk_khz = 400000 }, -/*ClocksStateNominal*/ -{ .display_clk_khz = 667000, .pixel_clk_khz = 600000 }, -/*ClocksStatePerformance*/ -{ .display_clk_khz = 1132000, .pixel_clk_khz = 600000 } }; - - //TODO: remove use the two broken down functions int dce112_set_clock(struct clk_mgr *clk_mgr_base, int requested_clk_khz) { @@ -202,10 +189,6 @@ void dce112_clk_mgr_construct( { dce_clk_mgr_construct(ctx, clk_mgr); - memcpy(clk_mgr->max_clks_by_state, - dce112_max_clks_by_state, - sizeof(dce112_max_clks_by_state)); - clk_mgr->regs = &disp_clk_regs; clk_mgr->clk_mgr_shift = &disp_clk_shift; clk_mgr->clk_mgr_mask = &disp_clk_mask; diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c index c9ba7b3fd2c3..f8ef3a4710fc 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c @@ -32,18 +32,6 @@ #include "dce100/dce_clk_mgr.h" #include "dce120/dce120_hwseq.h" -static const struct state_dependent_clocks dce120_max_clks_by_state[] = { -/*ClocksStateInvalid - should not be used*/ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/*ClocksStateUltraLow - currently by HW design team not supposed to be used*/ -{ .display_clk_khz = 0, .pixel_clk_khz = 0 }, -/*ClocksStateLow*/ -{ .display_clk_khz = 460000, .pixel_clk_khz = 400000 }, -/*ClocksStateNominal*/ -{ .display_clk_khz = 670000, .pixel_clk_khz = 600000 }, -/*ClocksStatePerformance*/ -{ .display_clk_khz = 1133000, .pixel_clk_khz = 600000 } }; - /** * dce121_clock_patch_xgmi_ss_info() - Save XGMI spread spectrum info * @clk_mgr_dce: clock manager internal structure @@ -129,10 +117,6 @@ void dce120_clk_mgr_construct(struct dc_context *ctx, struct clk_mgr_internal *c { dce_clk_mgr_construct(ctx, clk_mgr); - memcpy(clk_mgr->max_clks_by_state, - dce120_max_clks_by_state, - sizeof(dce120_max_clks_by_state)); - clk_mgr->base.dprefclk_khz = 600000; clk_mgr->base.funcs = &dce120_funcs; } diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h index e01bf6bd7f3f..450bce3d8e02 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h @@ -405,12 +405,6 @@ enum clock_type { clock_type_dtbclk, }; - -struct state_dependent_clocks { - int display_clk_khz; - int pixel_clk_khz; -}; - struct clk_mgr_internal { struct clk_mgr base; int smu_ver; @@ -429,8 +423,6 @@ struct clk_mgr_internal { const struct clk_mgr_shift *clk_mgr_shift; const struct clk_mgr_mask *clk_mgr_mask; - struct state_dependent_clocks max_clks_by_state[DM_PP_CLOCKS_MAX_STATES]; - /*TODO: figure out which of the below fields should be here vs in asic specific portion */ /* Cache the status of DFS-bypass feature*/ bool dfs_bypass_enabled; From ea25284d098d5aad86efc6bf541c063b53e30f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:11 +0200 Subject: [PATCH 30/58] drm/amd/display: Delete disp_clk_voltage from integrated info (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only DCE 11.0 relies on this information and even that didn't use this field, because it queries the information from the pplib. It also filled the field incorrectly on that version. On newer GPUs, the VIOS integrated info no longer contains display clock voltage dependencies, so we don't need it. v2: - Also delete some code wrapped in #if 0 Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/bios/bios_parser.c | 36 ------------------- .../drm/amd/display/dc/bios/bios_parser2.c | 18 ---------- .../display/include/grph_object_ctrl_defs.h | 9 ----- 3 files changed, 63 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c index 30567ef74e5c..91df37c4c8da 100644 --- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c +++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c @@ -2419,15 +2419,6 @@ static enum bp_result get_integrated_info_v8( info->dentist_vco_freq = le32_to_cpu(info_v8->ulDentistVCOFreq) * 10; info->boot_up_uma_clock = le32_to_cpu(info_v8->ulBootUpUMAClock) * 10; - for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) { - /* Convert [10KHz] into [KHz] */ - info->disp_clk_voltage[i].max_supported_clk = - le32_to_cpu(info_v8->sDISPCLK_Voltage[i]. - ulMaximumSupportedCLK) * 10; - info->disp_clk_voltage[i].voltage_index = - le32_to_cpu(info_v8->sDISPCLK_Voltage[i].ulVoltageIndex); - } - info->boot_up_req_display_vector = le32_to_cpu(info_v8->ulBootUpReqDisplayVector); info->gpu_cap_info = @@ -2570,14 +2561,6 @@ static enum bp_result get_integrated_info_v9( info->dentist_vco_freq = le32_to_cpu(info_v9->ulDentistVCOFreq) * 10; info->boot_up_uma_clock = le32_to_cpu(info_v9->ulBootUpUMAClock) * 10; - for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) { - /* Convert [10KHz] into [KHz] */ - info->disp_clk_voltage[i].max_supported_clk = - le32_to_cpu(info_v9->sDISPCLK_Voltage[i].ulMaximumSupportedCLK) * 10; - info->disp_clk_voltage[i].voltage_index = - le32_to_cpu(info_v9->sDISPCLK_Voltage[i].ulVoltageIndex); - } - info->boot_up_req_display_vector = le32_to_cpu(info_v9->ulBootUpReqDisplayVector); info->gpu_cap_info = le32_to_cpu(info_v9->ulGPUCapInfo); @@ -2719,25 +2702,6 @@ static enum bp_result construct_integrated_info( } } - /* Sort voltage table from low to high*/ - if (result == BP_RESULT_OK) { - int32_t i; - int32_t j; - - for (i = 1; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) { - for (j = i; j > 0; --j) { - if ( - info->disp_clk_voltage[j].max_supported_clk < - info->disp_clk_voltage[j-1].max_supported_clk) { - /* swap j and j - 1*/ - swap(info->disp_clk_voltage[j - 1], - info->disp_clk_voltage[j]); - } - } - } - - } - return result; } diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c index ee6d52b87b02..06bab03a8579 100644 --- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c +++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c @@ -2671,15 +2671,6 @@ static enum bp_result get_integrated_info_v11( info->dentist_vco_freq = le32_to_cpu(info_v11->ulDentistVCOFreq) * 10; info->boot_up_uma_clock = le32_to_cpu(info_v8->ulBootUpUMAClock) * 10; - for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) { - /* Convert [10KHz] into [KHz] */ - info->disp_clk_voltage[i].max_supported_clk = - le32_to_cpu(info_v11->sDISPCLK_Voltage[i]. - ulMaximumSupportedCLK) * 10; - info->disp_clk_voltage[i].voltage_index = - le32_to_cpu(info_v11->sDISPCLK_Voltage[i].ulVoltageIndex); - } - info->boot_up_req_display_vector = le32_to_cpu(info_v11->ulBootUpReqDisplayVector); info->boot_up_nb_voltage = @@ -3032,7 +3023,6 @@ static enum bp_result construct_integrated_info( struct atom_data_revision revision; int32_t i; - int32_t j; if (!info) return result; @@ -3134,14 +3124,6 @@ static enum bp_result construct_integrated_info( DC_LOG_BIOS("driver forced fixdpvoltageswing = %d\n", info->ext_disp_conn_info.fixdpvoltageswing); } } - /* Sort voltage table from low to high*/ - for (i = 1; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) { - for (j = i; j > 0; --j) { - if (info->disp_clk_voltage[j].max_supported_clk < - info->disp_clk_voltage[j-1].max_supported_clk) - swap(info->disp_clk_voltage[j-1], info->disp_clk_voltage[j]); - } - } return result; } diff --git a/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h b/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h index a0f03fb67605..3e4e8d55dd0f 100644 --- a/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h +++ b/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h @@ -273,7 +273,6 @@ struct transmitter_configuration { #define NUMBER_OF_UCHAR_FOR_GUID 16 #define MAX_NUMBER_OF_EXT_DISPLAY_PATH 7 #define NUMBER_OF_CSR_M3_ARB 10 -#define NUMBER_OF_DISP_CLK_VOLTAGE 4 #define NUMBER_OF_AVAILABLE_SCLK 5 struct i2c_reg_info { @@ -302,14 +301,6 @@ struct edp_info { /* V6 */ struct integrated_info { - struct clock_voltage_caps { - /* The Voltage Index indicated by FUSE, same voltage index - shared with SCLK DPM fuse table */ - uint32_t voltage_index; - /* Maximum clock supported with specified voltage index */ - uint32_t max_supported_clk; /* in KHz */ - } disp_clk_voltage[NUMBER_OF_DISP_CLK_VOLTAGE]; - struct display_connection_info { struct external_display_path { /* A bit vector to show what devices are supported */ From ff3af5416ac72419df601ba056a6c297bcd0bd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:12 +0200 Subject: [PATCH 31/58] drm/amd/display: Delete dm_pp_clocks_state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It isn't used by anything anymore. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dm_services_types.h | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dm_services_types.h b/drivers/gpu/drm/amd/display/dc/dm_services_types.h index f114ab819afd..b3505d93503f 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services_types.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services_types.h @@ -36,30 +36,7 @@ struct dm_pp_clock_range { int max_khz; }; -enum dm_pp_clocks_state { - DM_PP_CLOCKS_STATE_INVALID, - DM_PP_CLOCKS_STATE_ULTRA_LOW, - DM_PP_CLOCKS_STATE_LOW, - DM_PP_CLOCKS_STATE_NOMINAL, - DM_PP_CLOCKS_STATE_PERFORMANCE, - - /* Starting from DCE11, Max 8 levels of DPM state supported. */ - DM_PP_CLOCKS_DPM_STATE_LEVEL_INVALID = DM_PP_CLOCKS_STATE_INVALID, - DM_PP_CLOCKS_DPM_STATE_LEVEL_0, - DM_PP_CLOCKS_DPM_STATE_LEVEL_1, - DM_PP_CLOCKS_DPM_STATE_LEVEL_2, - /* to be backward compatible */ - DM_PP_CLOCKS_DPM_STATE_LEVEL_3, - DM_PP_CLOCKS_DPM_STATE_LEVEL_4, - DM_PP_CLOCKS_DPM_STATE_LEVEL_5, - DM_PP_CLOCKS_DPM_STATE_LEVEL_6, - DM_PP_CLOCKS_DPM_STATE_LEVEL_7, - - DM_PP_CLOCKS_MAX_STATES -}; - struct dm_pp_gpu_clock_range { - enum dm_pp_clocks_state clock_state; struct dm_pp_clock_range sclk; struct dm_pp_clock_range mclk; struct dm_pp_clock_range eclk; From f8bcc082149c74b0ef718692eccc995ad635f5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:13 +0200 Subject: [PATCH 32/58] drm/amd/pm: Delete unused get_display_power_level() function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was not called from anywhere. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/include/kgd_pp_interface.h | 2 -- drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h index 1bbf531de5ed..ac05a12e71bd 100644 --- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h @@ -476,8 +476,6 @@ struct amd_pm_funcs { u32 (*get_mclk)(void *handle, bool low); int (*display_configuration_change)(void *handle, const struct amd_pp_display_configuration *input); - int (*get_display_power_level)(void *handle, - struct amd_pp_simple_clock_info *output); int (*get_current_clocks)(void *handle, struct amd_pp_clock_info *clocks); int (*get_clock_by_type)(void *handle, diff --git a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c index e558b81b25c9..40fa2768b772 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c +++ b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c @@ -1020,17 +1020,6 @@ static int pp_display_configuration_change(void *handle, return 0; } -static int pp_get_display_power_level(void *handle, - struct amd_pp_simple_clock_info *output) -{ - struct pp_hwmgr *hwmgr = handle; - - if (!hwmgr || !hwmgr->pm_en || !output) - return -EINVAL; - - return phm_get_dal_power_level(hwmgr, output); -} - static int pp_get_current_clocks(void *handle, struct amd_pp_clock_info *clocks) { @@ -1588,7 +1577,6 @@ static const struct amd_pm_funcs pp_dpm_funcs = { .get_sclk = pp_dpm_get_sclk, .get_mclk = pp_dpm_get_mclk, .display_configuration_change = pp_display_configuration_change, - .get_display_power_level = pp_get_display_power_level, .get_current_clocks = pp_get_current_clocks, .get_clock_by_type = pp_get_clock_by_type, .get_clock_by_type_with_latency = pp_get_clock_by_type_with_latency, From e181cde3771570902636fdf279c646972efbe606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:14 +0200 Subject: [PATCH 33/58] drm/amd/pm: Delete dummy get_dal_power_level implementations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These implementations did not actually return the DAL power level, so they were effectively a no-op. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c | 7 ------- .../drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 15 --------------- .../drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c | 16 ---------------- .../drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c | 17 ----------------- 4 files changed, 55 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c index 2e671b45f174..e02a50f208df 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c @@ -962,12 +962,6 @@ static int smu10_store_cc6_data(struct pp_hwmgr *hwmgr, uint32_t separation_time return 0; } -static int smu10_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info) -{ - return -EINVAL; -} - static int smu10_force_clock_level(struct pp_hwmgr *hwmgr, enum pp_clock_type type, uint32_t mask) { @@ -1663,7 +1657,6 @@ static const struct pp_hwmgr_func smu10_hwmgr_funcs = { .store_cc6_data = smu10_store_cc6_data, .force_clock_level = smu10_force_clock_level, .emit_clock_levels = smu10_emit_clock_levels, - .get_dal_power_level = smu10_get_dal_power_level, .get_performance_level = smu10_get_performance_level, .get_current_shallow_sleep_clocks = smu10_get_current_shallow_sleep_clocks, .get_clock_by_type_with_latency = smu10_get_clock_by_type_with_latency, diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c index d9899cf7020b..243a7a12a240 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -4386,20 +4386,6 @@ static uint32_t vega10_get_fan_control_mode(struct pp_hwmgr *hwmgr) return AMD_FAN_CTRL_AUTO; } -static int vega10_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info) -{ - struct phm_ppt_v2_information *table_info = - (struct phm_ppt_v2_information *)hwmgr->pptable; - struct phm_clock_and_voltage_limits *max_limits = - &table_info->max_clock_voltage_on_ac; - - info->engine_max_clock = max_limits->sclk; - info->memory_max_clock = max_limits->mclk; - - return 0; -} - static void vega10_get_sclks(struct pp_hwmgr *hwmgr, struct pp_clock_levels_with_latency *clocks) { @@ -5644,7 +5630,6 @@ static const struct pp_hwmgr_func vega10_hwmgr_funcs = { .set_fan_control_mode = vega10_set_fan_control_mode, .get_fan_control_mode = vega10_get_fan_control_mode, .read_sensor = vega10_read_sensor, - .get_dal_power_level = vega10_get_dal_power_level, .get_clock_by_type_with_latency = vega10_get_clock_by_type_with_latency, .get_clock_by_type_with_voltage = vega10_get_clock_by_type_with_voltage, .set_watermarks_for_clocks_ranges = vega10_set_watermarks_for_clocks_ranges, diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c index a9a85fd639b2..69a9074058ce 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c @@ -1822,21 +1822,6 @@ static uint32_t vega12_get_fan_control_mode(struct pp_hwmgr *hwmgr) return AMD_FAN_CTRL_AUTO; } -static int vega12_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info) -{ -#if 0 - struct phm_ppt_v2_information *table_info = - (struct phm_ppt_v2_information *)hwmgr->pptable; - struct phm_clock_and_voltage_limits *max_limits = - &table_info->max_clock_voltage_on_ac; - - info->engine_max_clock = max_limits->sclk; - info->memory_max_clock = max_limits->mclk; -#endif - return 0; -} - static int vega12_get_clock_ranges(struct pp_hwmgr *hwmgr, uint32_t *clock, PPCLK_e clock_select, @@ -2963,7 +2948,6 @@ static const struct pp_hwmgr_func vega12_hwmgr_funcs = { .set_fan_control_mode = vega12_set_fan_control_mode, .get_fan_control_mode = vega12_get_fan_control_mode, .read_sensor = vega12_read_sensor, - .get_dal_power_level = vega12_get_dal_power_level, .get_clock_by_type_with_latency = vega12_get_clock_by_type_with_latency, .get_clock_by_type_with_voltage = vega12_get_clock_by_type_with_voltage, .set_watermarks_for_clocks_ranges = vega12_set_watermarks_for_clocks_ranges, diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c index dab9b78a9fc8..7b8f4c1b80eb 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c @@ -2796,22 +2796,6 @@ static void vega20_set_fan_control_mode(struct pp_hwmgr *hwmgr, uint32_t mode) } } -static int vega20_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info) -{ -#if 0 - struct phm_ppt_v2_information *table_info = - (struct phm_ppt_v2_information *)hwmgr->pptable; - struct phm_clock_and_voltage_limits *max_limits = - &table_info->max_clock_voltage_on_ac; - - info->engine_max_clock = max_limits->sclk; - info->memory_max_clock = max_limits->mclk; -#endif - return 0; -} - - static int vega20_get_sclks(struct pp_hwmgr *hwmgr, struct pp_clock_levels_with_latency *clocks) { @@ -4446,7 +4430,6 @@ static const struct pp_hwmgr_func vega20_hwmgr_funcs = { /* export to DAL */ .get_sclk = vega20_dpm_get_sclk, .get_mclk = vega20_dpm_get_mclk, - .get_dal_power_level = vega20_get_dal_power_level, .get_clock_by_type_with_latency = vega20_get_clock_by_type_with_latency, .get_clock_by_type_with_voltage = vega20_get_clock_by_type_with_voltage, .set_watermarks_for_clocks_ranges = vega20_set_watermarks_for_clocks_ranges, From c58032823f9f67400d4aabc39602e5207d645972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:15 +0200 Subject: [PATCH 34/58] drm/amd/pm: Delete non-functional SMU8 get_dal_power_level implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function was effectively a no-op because it always returned the maximum possible power level, because the maximum voltage is in millivolts while the dependency table didn't contain actual voltages. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c index 5ad6ab3d2d37..7034041cef73 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c @@ -1521,27 +1521,6 @@ static int smu8_store_cc6_data(struct pp_hwmgr *hwmgr, uint32_t separation_time, return 0; } -static int smu8_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info) -{ - uint32_t i; - const struct phm_clock_voltage_dependency_table *table = - hwmgr->dyn_state.vddc_dep_on_dal_pwrl; - const struct phm_clock_and_voltage_limits *limits = - &hwmgr->dyn_state.max_clock_voltage_on_ac; - - info->engine_max_clock = limits->sclk; - info->memory_max_clock = limits->mclk; - - for (i = table->count - 1; i > 0; i--) { - if (limits->vddc >= table->entries[i].v) { - info->level = table->entries[i].clk; - return 0; - } - } - return -EINVAL; -} - static int smu8_force_clock_level(struct pp_hwmgr *hwmgr, enum pp_clock_type type, uint32_t mask) { @@ -2062,7 +2041,6 @@ static const struct pp_hwmgr_func smu8_hwmgr_funcs = { .store_cc6_data = smu8_store_cc6_data, .force_clock_level = smu8_force_clock_level, .emit_clock_levels = smu8_emit_clock_levels, - .get_dal_power_level = smu8_get_dal_power_level, .get_performance_level = smu8_get_performance_level, .get_current_shallow_sleep_clocks = smu8_get_current_shallow_sleep_clocks, .get_clock_by_type = smu8_get_clock_by_type, From d24cf9ce3f0607649a175f47c6f3d8f45dbf08ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:16 +0200 Subject: [PATCH 35/58] drm/amd/pm: Delete vddc_dep_on_dal_pwrl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was not used by anything anymore. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../amd/pm/powerplay/hwmgr/processpptables.c | 1 - .../drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c | 40 ------------------ .../drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c | 41 ------------------- .../drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 3 -- drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 2 - 5 files changed, 87 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c index 59af3314ffc4..bfd8fbb0b49d 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c @@ -1319,7 +1319,6 @@ static int init_clock_voltage_dependency(struct pp_hwmgr *hwmgr, hwmgr->dyn_state.vddc_dependency_on_sclk = NULL; hwmgr->dyn_state.vddci_dependency_on_mclk = NULL; hwmgr->dyn_state.vddc_dependency_on_mclk = NULL; - hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL; hwmgr->dyn_state.mvdd_dependency_on_mclk = NULL; hwmgr->dyn_state.vce_clock_voltage_dependency_table = NULL; hwmgr->dyn_state.uvd_clock_voltage_dependency_table = NULL; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c index e02a50f208df..15456c1c3614 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c @@ -127,41 +127,6 @@ static int smu10_construct_max_power_limits_table(struct pp_hwmgr *hwmgr, return 0; } -static int smu10_init_dynamic_state_adjustment_rule_settings( - struct pp_hwmgr *hwmgr) -{ - int count = 8; - struct phm_clock_voltage_dependency_table *table_clk_vlt; - - table_clk_vlt = kzalloc_flex(*table_clk_vlt, entries, count); - - if (NULL == table_clk_vlt) { - pr_err("Can not allocate memory!\n"); - return -ENOMEM; - } - - table_clk_vlt->count = count; - table_clk_vlt->entries[0].clk = PP_DAL_POWERLEVEL_0; - table_clk_vlt->entries[0].v = 0; - table_clk_vlt->entries[1].clk = PP_DAL_POWERLEVEL_1; - table_clk_vlt->entries[1].v = 1; - table_clk_vlt->entries[2].clk = PP_DAL_POWERLEVEL_2; - table_clk_vlt->entries[2].v = 2; - table_clk_vlt->entries[3].clk = PP_DAL_POWERLEVEL_3; - table_clk_vlt->entries[3].v = 3; - table_clk_vlt->entries[4].clk = PP_DAL_POWERLEVEL_4; - table_clk_vlt->entries[4].v = 4; - table_clk_vlt->entries[5].clk = PP_DAL_POWERLEVEL_5; - table_clk_vlt->entries[5].v = 5; - table_clk_vlt->entries[6].clk = PP_DAL_POWERLEVEL_6; - table_clk_vlt->entries[6].v = 6; - table_clk_vlt->entries[7].clk = PP_DAL_POWERLEVEL_7; - table_clk_vlt->entries[7].v = 7; - hwmgr->dyn_state.vddc_dep_on_dal_pwrl = table_clk_vlt; - - return 0; -} - static int smu10_get_system_info_data(struct pp_hwmgr *hwmgr) { struct smu10_hwmgr *smu10_data = (struct smu10_hwmgr *)hwmgr->backend; @@ -175,8 +140,6 @@ static int smu10_get_system_info_data(struct pp_hwmgr *hwmgr) smu10_construct_max_power_limits_table (hwmgr, &hwmgr->dyn_state.max_clock_voltage_on_ac); - smu10_init_dynamic_state_adjustment_rule_settings(hwmgr); - return 0; } @@ -611,9 +574,6 @@ static int smu10_hwmgr_backend_fini(struct pp_hwmgr *hwmgr) kfree(pinfo->vdd_dep_on_phyclk); pinfo->vdd_dep_on_phyclk = NULL; - kfree(hwmgr->dyn_state.vddc_dep_on_dal_pwrl); - hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL; - kfree(hwmgr->backend); hwmgr->backend = NULL; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c index 7034041cef73..8f82d7c07e8c 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c @@ -270,41 +270,6 @@ static int smu8_construct_max_power_limits_table(struct pp_hwmgr *hwmgr, return 0; } -static int smu8_init_dynamic_state_adjustment_rule_settings( - struct pp_hwmgr *hwmgr, - ATOM_CLK_VOLT_CAPABILITY *disp_voltage_table) -{ - struct phm_clock_voltage_dependency_table *table_clk_vlt; - - table_clk_vlt = kzalloc_flex(*table_clk_vlt, entries, 8); - - if (NULL == table_clk_vlt) { - pr_err("Can not allocate memory!\n"); - return -ENOMEM; - } - - table_clk_vlt->count = 8; - table_clk_vlt->entries[0].clk = PP_DAL_POWERLEVEL_0; - table_clk_vlt->entries[0].v = 0; - table_clk_vlt->entries[1].clk = PP_DAL_POWERLEVEL_1; - table_clk_vlt->entries[1].v = 1; - table_clk_vlt->entries[2].clk = PP_DAL_POWERLEVEL_2; - table_clk_vlt->entries[2].v = 2; - table_clk_vlt->entries[3].clk = PP_DAL_POWERLEVEL_3; - table_clk_vlt->entries[3].v = 3; - table_clk_vlt->entries[4].clk = PP_DAL_POWERLEVEL_4; - table_clk_vlt->entries[4].v = 4; - table_clk_vlt->entries[5].clk = PP_DAL_POWERLEVEL_5; - table_clk_vlt->entries[5].v = 5; - table_clk_vlt->entries[6].clk = PP_DAL_POWERLEVEL_6; - table_clk_vlt->entries[6].v = 6; - table_clk_vlt->entries[7].clk = PP_DAL_POWERLEVEL_7; - table_clk_vlt->entries[7].v = 7; - hwmgr->dyn_state.vddc_dep_on_dal_pwrl = table_clk_vlt; - - return 0; -} - static int smu8_get_system_info_data(struct pp_hwmgr *hwmgr) { struct smu8_hwmgr *data = hwmgr->backend; @@ -403,9 +368,6 @@ static int smu8_get_system_info_data(struct pp_hwmgr *hwmgr) smu8_construct_max_power_limits_table (hwmgr, &hwmgr->dyn_state.max_clock_voltage_on_ac); - smu8_init_dynamic_state_adjustment_rule_settings(hwmgr, - &info->sDISPCLK_Voltage[0]); - return result; } @@ -1149,9 +1111,6 @@ static int smu8_hwmgr_backend_init(struct pp_hwmgr *hwmgr) static int smu8_hwmgr_backend_fini(struct pp_hwmgr *hwmgr) { if (hwmgr != NULL) { - kfree(hwmgr->dyn_state.vddc_dep_on_dal_pwrl); - hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL; - kfree(hwmgr->backend); hwmgr->backend = NULL; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c index 243a7a12a240..4b92b52aba2b 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -814,9 +814,6 @@ static int vega10_set_private_data_based_on_pptable(struct pp_hwmgr *hwmgr) static int vega10_hwmgr_backend_fini(struct pp_hwmgr *hwmgr) { - kfree(hwmgr->dyn_state.vddc_dep_on_dal_pwrl); - hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL; - kfree(hwmgr->backend); hwmgr->backend = NULL; diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index 3ae45eac0c5c..1ee7e3044272 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -540,7 +540,6 @@ struct phm_ppt_v1_information { struct phm_clock_array *valid_dcefclk_values; struct phm_clock_and_voltage_limits max_clock_voltage_on_dc; struct phm_clock_and_voltage_limits max_clock_voltage_on_ac; - struct phm_clock_voltage_dependency_table *vddc_dep_on_dal_pwrl; struct phm_ppm_table *ppm_parameter_table; struct phm_cac_tdp_table *cac_dtp_table; struct phm_tdp_table *tdp_table; @@ -632,7 +631,6 @@ struct phm_dynamic_state_info { struct phm_clock_voltage_dependency_table *vddc_dependency_on_mclk; struct phm_clock_voltage_dependency_table *mvdd_dependency_on_mclk; struct phm_clock_voltage_dependency_table *vddc_dependency_on_display_clock; - struct phm_clock_voltage_dependency_table *vddc_dep_on_dal_pwrl; struct phm_clock_array *valid_sclk_values; struct phm_clock_array *valid_mclk_values; struct phm_clock_and_voltage_limits max_clock_voltage_on_dc; From e624a56f8a4c8c5a7d0a3937e929056e807d9562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:17 +0200 Subject: [PATCH 36/58] drm/amd/pm: Delete get_dal_power_level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not needed anymore. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 3 --- drivers/gpu/drm/amd/include/dm_pp_interface.h | 1 - drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c | 10 ---------- .../gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c | 10 ---------- drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h | 3 --- drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 2 -- 6 files changed, 29 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index a3ee580d8dd1..2cdb8fea504a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -288,7 +288,6 @@ bool dm_pp_get_clock_levels_by_type( DRM_INFO("DM_PPLIB: Warning: using default validation clocks!\n"); validation_clks.engine_max_clock = 72000; validation_clks.memory_max_clock = 80000; - validation_clks.level = 0; } DRM_INFO("DM_PPLIB: Validation clocks:\n"); @@ -296,8 +295,6 @@ bool dm_pp_get_clock_levels_by_type( validation_clks.engine_max_clock); DRM_INFO("DM_PPLIB: memory_max_clock: %d\n", validation_clks.memory_max_clock); - DRM_INFO("DM_PPLIB: level : %d\n", - validation_clks.level); /* Translate 10 kHz to kHz. */ validation_clks.engine_max_clock *= 10; diff --git a/drivers/gpu/drm/amd/include/dm_pp_interface.h b/drivers/gpu/drm/amd/include/dm_pp_interface.h index 349544504c93..10747a1ceda9 100644 --- a/drivers/gpu/drm/amd/include/dm_pp_interface.h +++ b/drivers/gpu/drm/amd/include/dm_pp_interface.h @@ -113,7 +113,6 @@ struct amd_pp_display_configuration { struct amd_pp_simple_clock_info { uint32_t engine_max_clock; uint32_t memory_max_clock; - uint32_t level; }; enum PP_DAL_POWERLEVEL { diff --git a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c index 40fa2768b772..ddf8822219fb 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c +++ b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c @@ -1023,7 +1023,6 @@ static int pp_display_configuration_change(void *handle, static int pp_get_current_clocks(void *handle, struct amd_pp_clock_info *clocks) { - struct amd_pp_simple_clock_info simple_clocks = { 0 }; struct pp_clock_info hw_clocks; struct pp_hwmgr *hwmgr = handle; int ret = 0; @@ -1031,8 +1030,6 @@ static int pp_get_current_clocks(void *handle, if (!hwmgr || !hwmgr->pm_en) return -EINVAL; - phm_get_dal_power_level(hwmgr, &simple_clocks); - if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_PowerContainment)) ret = phm_get_clock_info(hwmgr, &hwmgr->current_ps->hardware, @@ -1057,11 +1054,6 @@ static int pp_get_current_clocks(void *handle, clocks->max_engine_clock_in_sr = hw_clocks.max_eng_clk; clocks->min_engine_clock_in_sr = hw_clocks.min_eng_clk; - if (simple_clocks.level == 0) - clocks->max_clocks_state = PP_DAL_POWERLEVEL_7; - else - clocks->max_clocks_state = simple_clocks.level; - if (0 == phm_get_current_shallow_sleep_clocks(hwmgr, &hwmgr->current_ps->hardware, &hw_clocks)) { clocks->max_engine_clock_in_sr = hw_clocks.max_eng_clk; clocks->min_engine_clock_in_sr = hw_clocks.min_eng_clk; @@ -1138,8 +1130,6 @@ static int pp_get_display_mode_validation_clocks(void *handle, if (!hwmgr || !hwmgr->pm_en || !clocks) return -EINVAL; - clocks->level = PP_DAL_POWERLEVEL_7; - if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_DynamicPatchPowerState)) ret = phm_get_max_high_clocks(hwmgr, clocks); diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c index a59677cf8dfc..72c2d3b69a03 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c @@ -328,16 +328,6 @@ int phm_store_dal_configuration_data(struct pp_hwmgr *hwmgr, return 0; } -int phm_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info) -{ - PHM_FUNC_CHECK(hwmgr); - - if (info == NULL || hwmgr->hwmgr_func->get_dal_power_level == NULL) - return -EINVAL; - return hwmgr->hwmgr_func->get_dal_power_level(hwmgr, info); -} - int phm_set_cpu_power_state(struct pp_hwmgr *hwmgr) { PHM_FUNC_CHECK(hwmgr); diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h index 915f1b8e4dba..36dcad065fae 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h @@ -426,9 +426,6 @@ extern int phm_check_states_equal(struct pp_hwmgr *hwmgr, extern int phm_store_dal_configuration_data(struct pp_hwmgr *hwmgr, const struct amd_pp_display_configuration *display_config); -extern int phm_get_dal_power_level(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info); - extern int phm_set_cpu_power_state(struct pp_hwmgr *hwmgr); extern int phm_power_down_asic(struct pp_hwmgr *hwmgr); diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index 1ee7e3044272..fc1ffe1b2c97 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -292,8 +292,6 @@ struct pp_hwmgr_func { int (*store_cc6_data)(struct pp_hwmgr *hwmgr, uint32_t separation_time, bool cc6_disable, bool pstate_disable, bool pstate_switch_disable); - int (*get_dal_power_level)(struct pp_hwmgr *hwmgr, - struct amd_pp_simple_clock_info *info); int (*get_performance_level)(struct pp_hwmgr *, const struct pp_hw_power_state *, PHM_PerformanceLevelDesignation, uint32_t, PHM_PerformanceLevel *); int (*get_current_shallow_sleep_clocks)(struct pp_hwmgr *hwmgr, From 622f134d85099477cd79f15a450edef37057d15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 12:21:18 +0200 Subject: [PATCH 37/58] drm/amd/pm: Delete PP_DAL_POWERLEVEL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not used and not needed anymore. Acked-by: Christian König Signed-off-by: Timur Kristóf Reviewed-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/include/dm_pp_interface.h | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/drivers/gpu/drm/amd/include/dm_pp_interface.h b/drivers/gpu/drm/amd/include/dm_pp_interface.h index 10747a1ceda9..e3d40fb37103 100644 --- a/drivers/gpu/drm/amd/include/dm_pp_interface.h +++ b/drivers/gpu/drm/amd/include/dm_pp_interface.h @@ -115,23 +115,6 @@ struct amd_pp_simple_clock_info { uint32_t memory_max_clock; }; -enum PP_DAL_POWERLEVEL { - PP_DAL_POWERLEVEL_INVALID = 0, - PP_DAL_POWERLEVEL_ULTRALOW, - PP_DAL_POWERLEVEL_LOW, - PP_DAL_POWERLEVEL_NOMINAL, - PP_DAL_POWERLEVEL_PERFORMANCE, - - PP_DAL_POWERLEVEL_0 = PP_DAL_POWERLEVEL_ULTRALOW, - PP_DAL_POWERLEVEL_1 = PP_DAL_POWERLEVEL_LOW, - PP_DAL_POWERLEVEL_2 = PP_DAL_POWERLEVEL_NOMINAL, - PP_DAL_POWERLEVEL_3 = PP_DAL_POWERLEVEL_PERFORMANCE, - PP_DAL_POWERLEVEL_4 = PP_DAL_POWERLEVEL_3+1, - PP_DAL_POWERLEVEL_5 = PP_DAL_POWERLEVEL_4+1, - PP_DAL_POWERLEVEL_6 = PP_DAL_POWERLEVEL_5+1, - PP_DAL_POWERLEVEL_7 = PP_DAL_POWERLEVEL_6+1, -}; - struct amd_pp_clock_info { uint32_t min_engine_clock; uint32_t max_engine_clock; @@ -141,7 +124,6 @@ struct amd_pp_clock_info { uint32_t max_bus_bandwidth; uint32_t max_engine_clock_in_sr; uint32_t min_engine_clock_in_sr; - enum PP_DAL_POWERLEVEL max_clocks_state; }; enum amd_pp_clock_type { From 6d87e0199f7b83735b56e422d59f170a201897a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 10:41:54 +0200 Subject: [PATCH 38/58] drm/amd/pm/si: Disregard vblank time when no displays are connected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When no displays are connected, there is no vblank happening so the power management code shouldn't worry about it. This fixes a regression that caused the memory clock to be stuck at maximum when there were no displays connected to a SI GPU. Fixes: 9003a0746864 ("drm/amd/pm: Treat zero vblank time as too short in si_dpm (v3)") Fixes: 9d73b107a61b ("drm/amd/pm: Use pm_display_cfg in legacy DPM (v2)") Reviewed-by: Alex Deucher Tested-by: Jeremy Klarenbeek Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 36942467d4ad..c3aff5d0c53d 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -3076,6 +3076,10 @@ static bool si_dpm_vblank_too_short(void *handle) /* we never hit the non-gddr5 limit so disable it */ u32 switch_limit = adev->gmc.vram_type == AMDGPU_VRAM_TYPE_GDDR5 ? 450 : 0; + /* Disregard vblank time when there are no displays connected */ + if (!adev->pm.pm_display_cfg.num_display) + return false; + /* Consider zero vblank time too short and disable MCLK switching. * Note that the vblank time is set to maximum when no displays are attached, * so we'll still enable MCLK switching in that case. From ba1fcd1bac89d0403f910754e1d5ec22acb5bf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 10:41:55 +0200 Subject: [PATCH 39/58] drm/amd/pm: Rename enable_bapm() to notify_ac_dc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No functional changes, just change the name of this function pointer to be more generic. BAPM refers to a specific feature on KV, but other kinds of ASICs may also need the SMU to be notified on AC/DC changes. Also remove the argument and use adev->pm.ac_power instead. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/include/kgd_pp_interface.h | 2 +- drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 8 ++++---- drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h index ac05a12e71bd..6371f292f2d8 100644 --- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h @@ -417,7 +417,7 @@ struct amd_pm_funcs { void (*display_configuration_changed)(void *handle); void (*print_power_state)(void *handle, void *ps); bool (*vblank_too_short)(void *handle); - void (*enable_bapm)(void *handle, bool enable); + void (*notify_ac_dc)(void *handle); int (*check_state_equal)(void *handle, void *cps, void *rps, diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index feadf604b474..f76ba6753551 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -33,8 +33,8 @@ #include #include "amdgpu_smu.h" -#define amdgpu_dpm_enable_bapm(adev, e) \ - ((adev)->powerplay.pp_funcs->enable_bapm((adev)->powerplay.pp_handle, (e))) +#define amdgpu_dpm_notify_ac_dc(adev) \ + ((adev)->powerplay.pp_funcs->notify_ac_dc((adev)->powerplay.pp_handle)) #define amdgpu_dpm_is_legacy_dpm(adev) ((adev)->powerplay.pp_handle == (adev)) @@ -504,8 +504,8 @@ void amdgpu_pm_acpi_event_handler(struct amdgpu_device *adev) adev->pm.ac_power = false; if (adev->powerplay.pp_funcs && - adev->powerplay.pp_funcs->enable_bapm) - amdgpu_dpm_enable_bapm(adev, adev->pm.ac_power); + adev->powerplay.pp_funcs->notify_ac_dc) + amdgpu_dpm_notify_ac_dc(adev); if (is_support_sw_smu(adev)) smu_set_ac_dc(adev->powerplay.pp_handle); diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c index cdf3f8e6ec2f..b763fdf43c60 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c @@ -1233,14 +1233,14 @@ static void kv_update_requested_ps(struct amdgpu_device *adev, adev->pm.dpm.requested_ps = &pi->requested_rps; } -static void kv_dpm_enable_bapm(void *handle, bool enable) +static void kv_dpm_enable_bapm(void *handle) { struct amdgpu_device *adev = (struct amdgpu_device *)handle; struct kv_power_info *pi = kv_get_pi(adev); int ret; if (pi->bapm_enable) { - ret = amdgpu_kv_smc_bapm_enable(adev, enable); + ret = amdgpu_kv_smc_bapm_enable(adev, adev->pm.ac_power); if (ret) drm_err(adev_to_drm(adev), "amdgpu_kv_smc_bapm_enable failed\n"); } @@ -3341,7 +3341,7 @@ static const struct amd_pm_funcs kv_dpm_funcs = { .debugfs_print_current_performance_level = &kv_dpm_debugfs_print_current_performance_level, .force_performance_level = &kv_dpm_force_performance_level, .set_powergating_by_smu = kv_set_powergating_by_smu, - .enable_bapm = &kv_dpm_enable_bapm, + .notify_ac_dc = &kv_dpm_enable_bapm, .get_vce_clock_state = amdgpu_get_vce_clock_state, .check_state_equal = kv_check_state_equal, .read_sensor = &kv_dpm_read_sensor, From 96da0d86614e2e8ab34afd5b8578e8ee43963df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Tue, 19 May 2026 10:41:56 +0200 Subject: [PATCH 40/58] drm/amd/pm/smu7: Notify SMU7 of DC->AC switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ATOM_PP_PLATFORM_CAP_HARDWAREDC is set, the SMU has a GPIO pin for detecting AC/DC switch and everything works automatically. Otherwise when there is no GPIO pin, the SMU can automatically detect switching to DC, but needs to be notified of switching to AC. Use PPSMC_MSG_RunningOnAC to notify the SMC when switching to AC. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c | 12 ++++++++++++ .../gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 15 +++++++++++++++ drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 1 + 3 files changed, 28 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c index ddf8822219fb..5700bcc7ad9a 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c +++ b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c @@ -1529,6 +1529,17 @@ static void pp_pm_compute_clocks(void *handle) NULL); } +static void pp_dpm_notify_ac_dc(void *handle) +{ + struct pp_hwmgr *hwmgr = handle; + + if (!hwmgr || !hwmgr->pm_en) + return; + + if (hwmgr->hwmgr_func->notify_ac_dc) + hwmgr->hwmgr_func->notify_ac_dc(hwmgr); +} + 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, @@ -1593,4 +1604,5 @@ static const struct amd_pm_funcs pp_dpm_funcs = { .gfx_state_change_set = pp_gfx_state_change_set, .get_smu_prv_buf_details = pp_get_prv_buffer_details, .pm_compute_clocks = pp_pm_compute_clocks, + .notify_ac_dc = pp_dpm_notify_ac_dc, }; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c index 55e2375e1dad..95bf187f02a5 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -5854,6 +5854,20 @@ static int smu7_power_off_asic(struct pp_hwmgr *hwmgr) return result; } +static void smu7_notify_ac_dc(struct pp_hwmgr *hwmgr) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)(hwmgr->adev); + + /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ + if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, + PHM_PlatformCaps_AutomaticDCTransition)) + return; + + /* The SMU automatically notices DC, but needs to be notified when switching to AC. */ + if (adev->pm.ac_power) + smum_send_msg_to_smc(hwmgr, PPSMC_MSG_RunningOnAC, NULL); +} + static const struct pp_hwmgr_func smu7_hwmgr_funcs = { .backend_init = &smu7_hwmgr_backend_init, .backend_fini = &smu7_hwmgr_backend_fini, @@ -5916,6 +5930,7 @@ static const struct pp_hwmgr_func smu7_hwmgr_funcs = { .get_asic_baco_state = smu7_baco_get_state, .set_asic_baco_state = smu7_baco_set_state, .power_off_asic = smu7_power_off_asic, + .notify_ac_dc = smu7_notify_ac_dc, }; uint8_t smu7_get_sleep_divider_id_from_clock(uint32_t clock, diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index fc1ffe1b2c97..ca71efaa1656 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -362,6 +362,7 @@ struct pp_hwmgr_func { bool disable); ssize_t (*get_gpu_metrics)(struct pp_hwmgr *hwmgr, void **table); int (*gfx_state_change)(struct pp_hwmgr *hwmgr, uint32_t state); + void (*notify_ac_dc)(struct pp_hwmgr *hwmgr); }; struct pp_table_func { From e6c5d36756e7d4d260e2365fc4d01226f1973152 Mon Sep 17 00:00:00 2001 From: Jeremy Klarenbeek Date: Tue, 19 May 2026 10:41:57 +0200 Subject: [PATCH 41/58] drm/amd/pm/si: Fix updating clock limits from power states MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VBIOS can contain conflicting values between: - the maximum allowed clocks and voltages on AC or DC - the clocks and voltages in power states on AC or DC Update maximum clock (and voltage) limits for both AC/DC and take the highest value from the VBIOS limits and the performance/battery power states. Previously this was only done for AC, but is also needed for DC. This commit fixes the behaviour on some laptop GPUs, where the VBIOS limit was set to the lowest possible clock frequency, so the GPU was stuck on the lowest possible power level on battery. Some affected GPUs are: FirePro W4170M (Dell Precision M2800) Radeon HD 8790M (Dell Latitude E6540) and possibly other laptop GPUs. Reviewed-by: Alex Deucher Co-developed-by: Timur Kristóf Signed-off-by: Timur Kristóf Signed-off-by: Jeremy Klarenbeek Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 29 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index c3aff5d0c53d..be94d98c61b7 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -7240,6 +7240,7 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev, struct evergreen_power_info *eg_pi = evergreen_get_pi(adev); struct si_power_info *si_pi = si_get_pi(adev); struct si_ps *ps = si_get_ps(rps); + struct amdgpu_clock_and_voltage_limits *limits; u16 leakage_voltage; struct rv7xx_pl *pl = &ps->performance_levels[index]; int ret; @@ -7299,12 +7300,30 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev, si_pi->mvdd_bootup_value = mvdd; } + /* + * Update maximum allowed clock limits. + * VBIOS can contain conflicting values between: + * - the maximum allowed clocks and voltages on AC or DC + * - the clocks and voltages in power states on AC or DC + */ if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == - ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) { - adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.sclk = pl->sclk; - adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.mclk = pl->mclk; - adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.vddc = pl->vddc; - adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.vddci = pl->vddci; + ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) + limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_ac; + else if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == + ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) + limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_dc; + else + limits = NULL; + + if (limits) { + if (pl->sclk > limits->sclk) + limits->sclk = pl->sclk; + if (pl->mclk > limits->mclk) + limits->mclk = pl->mclk; + if (pl->vddc > limits->vddc) + limits->vddc = pl->vddc; + if (pl->vddci > limits->vddci) + limits->vddci = pl->vddci; } } From 2d071f6457af08f4692d340fcae030b5eabd6837 Mon Sep 17 00:00:00 2001 From: Jeremy Klarenbeek Date: Tue, 19 May 2026 10:41:58 +0200 Subject: [PATCH 42/58] drm/amd/pm/si: Notify the SMC when switching to AC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are some platforms that don't have a dedicated GPIO line to manage the AC/DC switch. In this case, the SI SMC automatically notices when switching to DC, but needs to be notified when switching to AC. Fixup and use si_notify_hw_of_powersource() which was previously hidden behind an "#if 0". This fixes some SI laptop GPUs to be able to use their performance power states after switching from DC to AC. Some affected GPUs are: FirePro W4170M - Dell Precision M2800 Radeon HD 8790M - Dell Latitude E6540 Reviewed-by: Alex Deucher Co-developed-by: Timur Kristóf Signed-off-by: Timur Kristóf Signed-off-by: Jeremy Klarenbeek Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index be94d98c61b7..8079da7c5335 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -3888,16 +3888,18 @@ static void si_notify_hardware_vpu_recovery_event(struct amdgpu_device *adev) } #endif -#if 0 -static int si_notify_hw_of_powersource(struct amdgpu_device *adev, bool ac_power) +static void si_notify_hw_of_powersource(void *handle) { - if (ac_power) - return (amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC) == PPSMC_Result_OK) ? - 0 : -EINVAL; + struct amdgpu_device *adev = (struct amdgpu_device *)handle; - return 0; + /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ + if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC) + return; + + /* The SMU automatically notices DC, but needs to be notified when switching to AC. */ + if (adev->pm.ac_power) + amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC); } -#endif static PPSMC_Result si_send_msg_to_smc_with_parameter(struct amdgpu_device *adev, PPSMC_Msg msg, u32 parameter) @@ -8163,6 +8165,7 @@ static const struct amd_pm_funcs si_dpm_funcs = { .get_vce_clock_state = amdgpu_get_vce_clock_state, .read_sensor = &si_dpm_read_sensor, .pm_compute_clocks = amdgpu_legacy_dpm_compute_clocks, + .notify_ac_dc = si_notify_hw_of_powersource, }; static const struct amdgpu_irq_src_funcs si_dpm_irq_funcs = { From ebbd73d089bd47021e67a43bed665178adb55b4b Mon Sep 17 00:00:00 2001 From: Chenglei Xie Date: Mon, 11 May 2026 15:24:29 -0400 Subject: [PATCH 43/58] drm/amdgpu: bound SR-IOV RAS CPER dump parsing against used_size The VF copies a PF-provided CPER telemetry blob and walks records using cper_dump->count and each entry's record_length. count is u64 while the loop used u32, so a large count could loop indefinitely. record_length was not limited to the kmemdup'd region, so the first iteration could read far past the allocation; record_length == 0 could spin forever on the same entry. Together that allowed a malicious hypervisor to leak heap past the blob into the CPER ring or hang the guest. Require used_size to cover the fixed header before buf and stay within the telemetry cap. Track remaining bytes in buf, cap iterations with u64 and CPER_MAX_ALLOWED_COUNT, and reject record_length outside [sizeof(cper_hdr), remaining] before writing to the ring. Signed-off-by: Chenglei Xie Reviewed-by: YiPeng Chai Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c index 3fcbd722ee3b..229eb6219de0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c @@ -1804,13 +1804,15 @@ amdgpu_virt_write_cpers_to_ring(struct amdgpu_device *adev, struct amd_sriov_ras_cper_dump *cper_dump = NULL; struct cper_hdr *entry = NULL; struct amdgpu_ring *ring = &adev->cper.ring_buf; - uint32_t checksum, used_size, i; + uint32_t checksum, used_size; + u64 remaining, cnt, i; int ret = 0; checksum = host_telemetry->header.checksum; used_size = host_telemetry->header.used_size; - if (used_size > (AMD_SRIOV_MSG_RAS_TELEMETRY_SIZE_KB_V1 << 10)) + if (used_size < offsetof(struct amd_sriov_ras_cper_dump, buf) || + used_size > (AMD_SRIOV_MSG_RAS_TELEMETRY_SIZE_KB_V1 << 10)) return -EINVAL; cper_dump = kmemdup(&host_telemetry->body.cper_dump, used_size, GFP_KERNEL); @@ -1835,11 +1837,19 @@ amdgpu_virt_write_cpers_to_ring(struct amdgpu_device *adev, } entry = (struct cper_hdr *)&cper_dump->buf[0]; + remaining = (u64)used_size - offsetof(struct amd_sriov_ras_cper_dump, buf); + cnt = min_t(u64, cper_dump->count, CPER_MAX_ALLOWED_COUNT); + + for (i = 0; i < cnt; i++) { + if (entry->record_length < sizeof(struct cper_hdr) || + entry->record_length > remaining) { + ret = -EINVAL; + goto out; + } - for (i = 0; i < cper_dump->count; i++) { amdgpu_cper_ring_write(ring, entry, entry->record_length); - entry = (struct cper_hdr *)((char *)entry + - entry->record_length); + remaining -= entry->record_length; + entry = (struct cper_hdr *)((char *)entry + entry->record_length); } if (cper_dump->overflow_count) From 26057fb8e17e4ac9b95a6988ae37089f1b0cef9e Mon Sep 17 00:00:00 2001 From: Chenglei Xie Date: Mon, 11 May 2026 14:13:45 -0400 Subject: [PATCH 44/58] drm/amdgpu: Fix TOCTOU on UniRAS command response size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest maps the PF response in shared VRAM (struct ras_cmd_ctx in the command buffer). After amdgpu_virt_send_remote_ras_cmd() returns, the code validated rcmd->output_size against the caller buffer, then copied rcmd->output_buff_raw using rcmd->output_size again. A malicious PF could change output_size between those reads so the memcpy length exceeds the caller’s output_size and overflows guest stack or heap buffers. Snapshot output_size with READ_ONCE() once, assign cmd->output_size from that value, and use the same snapshot for the bounds check and memcpy. Also read cmd_res once with READ_ONCE() so the error branch and cmd->cmd_res assignment do not observe different values from shared memory. Signed-off-by: Chenglei Xie Reviewed-by: YiPeng Chai Signed-off-by: Alex Deucher --- .../drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c index c2761f3d06a0..571b81cb0475 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c @@ -108,15 +108,19 @@ static int amdgpu_virt_ras_remote_ioctl_cmd(struct ras_core_context *ras_core, ret = amdgpu_virt_send_remote_ras_cmd(ras_core->dev, shared_mem.gpa, mem_len); if (!ret) { - if (rcmd->cmd_res) { - ret = rcmd->cmd_res; + uint32_t cmd_res = READ_ONCE(rcmd->cmd_res); + uint32_t osz; + + if (cmd_res) { + ret = cmd_res; goto out; } - cmd->cmd_res = rcmd->cmd_res; - cmd->output_size = rcmd->output_size; - if (rcmd->output_size && (rcmd->output_size <= output_size) && output_data) - memcpy(output_data, rcmd->output_buff_raw, rcmd->output_size); + osz = READ_ONCE(rcmd->output_size); + cmd->cmd_res = cmd_res; + cmd->output_size = osz; + if (osz && osz <= output_size && output_data) + memcpy(output_data, rcmd->output_buff_raw, osz); } out: From deb02080ca5d3f015cf71e56067a39ef2f141998 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Wed, 20 May 2026 16:13:09 +0530 Subject: [PATCH 45/58] drm/amdgpu/userq: remove amdgpu_userq_create/destroy_object wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the amdgpu_userq_create/destroy_object wrappers and use directly the kernel bo allocation function which does all the things which are done in wrapper. Signed-off-by: Sunil Khatri Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 67 ---------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 7 --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 29 +++++++--- 3 files changed, 21 insertions(+), 82 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 6840e2eb3c4f..1eceecc2fb59 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -465,74 +465,7 @@ amdgpu_userq_ensure_ev_fence(struct amdgpu_userq_mgr *uq_mgr, dma_fence_put(ev_fence); } -int amdgpu_userq_create_object(struct amdgpu_userq_mgr *uq_mgr, - struct amdgpu_userq_obj *userq_obj, - int size) -{ - struct amdgpu_device *adev = uq_mgr->adev; - struct amdgpu_bo_param bp; - int r; - memset(&bp, 0, sizeof(bp)); - bp.byte_align = PAGE_SIZE; - bp.domain = AMDGPU_GEM_DOMAIN_GTT; - bp.flags = AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS | - AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED; - bp.type = ttm_bo_type_kernel; - bp.size = size; - bp.resv = NULL; - bp.bo_ptr_size = sizeof(struct amdgpu_bo); - - r = amdgpu_bo_create(adev, &bp, &userq_obj->obj); - if (r) { - drm_file_err(uq_mgr->file, "Failed to allocate BO for userqueue (%d)", r); - return r; - } - - r = amdgpu_bo_reserve(userq_obj->obj, true); - if (r) { - drm_file_err(uq_mgr->file, "Failed to reserve BO to map (%d)", r); - goto free_obj; - } - - r = amdgpu_bo_pin(userq_obj->obj, AMDGPU_GEM_DOMAIN_GTT); - if (r) - goto unresv; - - r = amdgpu_ttm_alloc_gart(&(userq_obj->obj)->tbo); - if (r) { - drm_file_err(uq_mgr->file, "Failed to alloc GART for userqueue object (%d)", r); - goto unpin_bo; - } - - r = amdgpu_bo_kmap(userq_obj->obj, &userq_obj->cpu_ptr); - if (r) { - drm_file_err(uq_mgr->file, "Failed to map BO for userqueue (%d)", r); - goto unpin_bo; - } - - userq_obj->gpu_addr = amdgpu_bo_gpu_offset(userq_obj->obj); - amdgpu_bo_unreserve(userq_obj->obj); - memset(userq_obj->cpu_ptr, 0, size); - return 0; - -unpin_bo: - amdgpu_bo_unpin(userq_obj->obj); -unresv: - amdgpu_bo_unreserve(userq_obj->obj); -free_obj: - amdgpu_bo_unref(&userq_obj->obj); - - return r; -} - -void amdgpu_userq_destroy_object(struct amdgpu_userq_mgr *uq_mgr, - struct amdgpu_userq_obj *userq_obj) -{ - amdgpu_bo_kunmap(userq_obj->obj); - amdgpu_bo_unpin(userq_obj->obj); - amdgpu_bo_unref(&userq_obj->obj); -} static int amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 033b8a0de6b1..76ef5cfab52e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -151,13 +151,6 @@ void amdgpu_userq_mgr_cancel_reset_work(struct amdgpu_device *adev); void amdgpu_userq_mgr_cancel_resume(struct amdgpu_userq_mgr *userq_mgr); void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr); -int amdgpu_userq_create_object(struct amdgpu_userq_mgr *uq_mgr, - struct amdgpu_userq_obj *userq_obj, - int size); - -void amdgpu_userq_destroy_object(struct amdgpu_userq_mgr *uq_mgr, - struct amdgpu_userq_obj *userq_obj); - void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_ensure_ev_fence(struct amdgpu_userq_mgr *userq_mgr, diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 4eaf98ba757a..35af26b36d0d 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -192,12 +192,16 @@ static int mes_userq_create_ctx_space(struct amdgpu_userq_mgr *uq_mgr, * for the same. */ size = AMDGPU_USERQ_PROC_CTX_SZ + AMDGPU_USERQ_GANG_CTX_SZ; - r = amdgpu_userq_create_object(uq_mgr, ctx, size); + r = amdgpu_bo_create_kernel(uq_mgr->adev, size, 0, + AMDGPU_GEM_DOMAIN_GTT, + &ctx->obj, &ctx->gpu_addr, + &ctx->cpu_ptr); if (r) { DRM_ERROR("Failed to allocate ctx space bo for userqueue, err:%d\n", r); return r; } + memset(ctx->cpu_ptr, 0, size); return 0; } @@ -270,13 +274,19 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, return -ENOMEM; } - r = amdgpu_userq_create_object(uq_mgr, &queue->mqd, - AMDGPU_MQD_SIZE_ALIGN(mqd_hw_default->mqd_size)); + r = amdgpu_bo_create_kernel(adev, + AMDGPU_MQD_SIZE_ALIGN(mqd_hw_default->mqd_size), + 0, AMDGPU_GEM_DOMAIN_GTT, + &queue->mqd.obj, &queue->mqd.gpu_addr, + &queue->mqd.cpu_ptr); if (r) { DRM_ERROR("Failed to create MQD object for userqueue\n"); goto free_props; } + memset(queue->mqd.cpu_ptr, 0, + AMDGPU_MQD_SIZE_ALIGN(mqd_hw_default->mqd_size)); + /* Initialize the MQD BO with user given values */ userq_props->wptr_gpu_addr = mqd_user->wptr_va; userq_props->rptr_gpu_addr = mqd_user->rptr_va; @@ -432,10 +442,12 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, return 0; free_ctx: - amdgpu_userq_destroy_object(uq_mgr, &queue->fw_obj); + amdgpu_bo_free_kernel(&queue->fw_obj.obj, &queue->fw_obj.gpu_addr, + &queue->fw_obj.cpu_ptr); free_mqd: - amdgpu_userq_destroy_object(uq_mgr, &queue->mqd); + amdgpu_bo_free_kernel(&queue->mqd.obj, &queue->mqd.gpu_addr, + &queue->mqd.cpu_ptr); free_props: kfree(userq_props); @@ -445,11 +457,12 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, static void mes_userq_mqd_destroy(struct amdgpu_usermode_queue *queue) { - struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; - amdgpu_userq_destroy_object(uq_mgr, &queue->fw_obj); + amdgpu_bo_free_kernel(&queue->fw_obj.obj, &queue->fw_obj.gpu_addr, + &queue->fw_obj.cpu_ptr); kfree(queue->userq_prop); - amdgpu_userq_destroy_object(uq_mgr, &queue->mqd); + amdgpu_bo_free_kernel(&queue->mqd.obj, &queue->mqd.gpu_addr, + &queue->mqd.cpu_ptr); } static int mes_userq_preempt(struct amdgpu_usermode_queue *queue) From eb53125a7ad99c7fc2f249bffdc561afa002a46c Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 20 May 2026 10:46:17 -0500 Subject: [PATCH 46/58] drm/amd: Add dedicated helper for amdgpu_device_find_parent() There are a few cases that code walks up the topology to find the link partner of the integrated switch in a dGPU. Split this out to a helper and call in all places. This does have a functional change that amdgpu_device_gpu_bandwidth() doesn't cache the internal link but only the parent. Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 44 +++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 21a3fb574d53..480eeb8510f8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1913,6 +1913,19 @@ static void amdgpu_uid_fini(struct amdgpu_device *adev) adev->uid_info = NULL; } +static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) +{ + struct pci_dev *parent = adev->pdev; + + /* skip upstream/downstream switches internal to dGPU */ + while ((parent = pci_upstream_bridge(parent))) { + if (parent->vendor == PCI_VENDOR_ID_ATI) + continue; + } + + return parent; +} + /** * amdgpu_device_ip_early_init - run early init for hardware IPs * @@ -5921,8 +5934,6 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev, enum pci_bus_speed *speed, enum pcie_link_width *width) { - struct pci_dev *parent = adev->pdev; - if (!speed || !width) return; @@ -5930,13 +5941,11 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev, *width = PCIE_LNK_WIDTH_UNKNOWN; if (amdgpu_device_pcie_dynamic_switching_supported(adev)) { - while ((parent = pci_upstream_bridge(parent))) { - /* skip upstream/downstream switches internal to dGPU*/ - if (parent->vendor == PCI_VENDOR_ID_ATI) - continue; + struct pci_dev *parent = amdgpu_device_find_parent(adev); + + if (parent) { *speed = pcie_get_speed_cap(parent); *width = pcie_get_width_cap(parent); - break; } } else { /* use the current speeds rather than max if switching is not supported */ @@ -5963,22 +5972,15 @@ static void amdgpu_device_gpu_bandwidth(struct amdgpu_device *adev, if (!speed || !width) return; - parent = pci_upstream_bridge(parent); - if (parent && parent->vendor == PCI_VENDOR_ID_ATI) { - /* use the upstream/downstream switches internal to dGPU */ + /* use the device itself */ + *speed = pcie_get_speed_cap(adev->pdev); + *width = pcie_get_width_cap(adev->pdev); + + /* use the link outside the device */ + parent = amdgpu_device_find_parent(adev); + if (parent) { *speed = pcie_get_speed_cap(parent); *width = pcie_get_width_cap(parent); - while ((parent = pci_upstream_bridge(parent))) { - if (parent->vendor == PCI_VENDOR_ID_ATI) { - /* use the upstream/downstream switches internal to dGPU */ - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); - } - } - } else { - /* use the device itself */ - *speed = pcie_get_speed_cap(adev->pdev); - *width = pcie_get_width_cap(adev->pdev); } } From 2d57a0475f085c08b49312dfd8edcb461845f285 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Tue, 12 May 2026 10:19:52 -0400 Subject: [PATCH 47/58] drm/amdkfd: fix a vulnerability of integer overflow in kfd debugger get_queue_ids() computes array_size = num_queues * sizeof(uint32_t), which could overflow on 32-bit size_t build. using array_size() instead, it saturates to SIZE_MAX on overflow. Signed-off-by: Eric Huang Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 5cba592ba941..cbd6fe8340f7 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -3477,12 +3477,14 @@ static void copy_context_work_handler(struct work_struct *work) static uint32_t *get_queue_ids(uint32_t num_queues, uint32_t *usr_queue_id_array) { - size_t array_size = num_queues * sizeof(uint32_t); - if (!usr_queue_id_array) return NULL; - return memdup_user(usr_queue_id_array, array_size); + if (num_queues > KFD_MAX_NUM_OF_QUEUES_PER_PROCESS) + return ERR_PTR(-EINVAL); + + return memdup_user(usr_queue_id_array, + array_size(num_queues, sizeof(uint32_t))); } int resume_queues(struct kfd_process *p, From 4ad65d610096498c8e265615aba42b3c47441bb5 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Wed, 20 May 2026 16:25:50 +0530 Subject: [PATCH 48/58] drm/amdgpu/userq: move mqd_destroy to later stage to keep core obj valid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mqd_destroy cleans up queue core objects like mqd and fw_object which are needed for any pending fence to signal properly. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 1eceecc2fb59..165dd9fa7b5e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -417,12 +417,10 @@ static void amdgpu_userq_cleanup(struct amdgpu_usermode_queue *queue) { struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; struct amdgpu_device *adev = uq_mgr->adev; - const struct amdgpu_userq_funcs *uq_funcs = adev->userq_funcs[queue->queue_type]; /* Wait for mode-1 reset to complete */ down_read(&adev->reset_domain->sem); - uq_funcs->mqd_destroy(queue); /* Use interrupt-safe locking since IRQ handlers may access these XArrays */ xa_erase_irq(&adev->userq_doorbell_xa, queue->doorbell_index); amdgpu_userq_fence_driver_free(queue); @@ -541,15 +539,15 @@ static int amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_queue *queue) { struct amdgpu_device *adev = uq_mgr->adev; - struct amdgpu_fpriv *fpriv = uq_mgr_to_fpriv(uq_mgr); - struct amdgpu_vm *vm = &fpriv->vm; - + const struct amdgpu_userq_funcs *uq_funcs = adev->userq_funcs[queue->queue_type]; + struct amdgpu_vm *vm = queue->vm; int r = 0; cancel_delayed_work_sync(&uq_mgr->resume_work); /* Cancel any pending hang detection work and cleanup */ cancel_delayed_work_sync(&queue->hang_detect_work); + mutex_lock(&uq_mgr->userq_mutex); amdgpu_userq_wait_for_last_fence(queue); @@ -566,6 +564,7 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que amdgpu_userq_buffer_vas_list_cleanup(adev, queue); amdgpu_bo_unreserve(vm->root.bo); list_del(&queue->userq_va_list); + uq_funcs->mqd_destroy(queue); queue->userq_mgr = NULL; amdgpu_bo_reserve(queue->db_obj.obj, true); From ef7dc711a664b0c548ecfdf13a00436b7446b8e7 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Wed, 20 May 2026 16:39:49 +0530 Subject: [PATCH 49/58] drm/amdgpu/userq: use array instead of list for userq_vas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use arrays instead of list for userq_vas since we have fixed no of bos. Also, we dont have to worry to free that memory later since this array would be free along with queue only. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 88 ++++++---------------- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 20 +++-- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 14 ++-- 3 files changed, 45 insertions(+), 77 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 165dd9fa7b5e..5438d5fde878 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -215,33 +215,15 @@ void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell) xa_unlock_irqrestore(xa, flags); } -static int amdgpu_userq_buffer_va_list_add(struct amdgpu_usermode_queue *queue, - struct amdgpu_bo_va_mapping *va_map, u64 addr) -{ - struct amdgpu_userq_va_cursor *va_cursor; - struct userq_va_list; - - va_cursor = kzalloc_obj(*va_cursor); - if (!va_cursor) - return -ENOMEM; - - INIT_LIST_HEAD(&va_cursor->list); - va_cursor->gpu_addr = addr; - va_map->bo_va->userq_va_mapped = true; - list_add(&va_cursor->list, &queue->userq_va_list); - - return 0; -} - int amdgpu_userq_input_va_validate(struct amdgpu_device *adev, struct amdgpu_usermode_queue *queue, - u64 addr, u64 expected_size) + u64 addr, u64 expected_size, + u64 *va_out) { struct amdgpu_bo_va_mapping *va_map; struct amdgpu_vm *vm = queue->vm; u64 user_addr; u64 size; - int r = 0; /* Caller must hold vm->root.bo reservation */ dma_resv_assert_held(queue->vm->root.bo->tbo.base.resv); @@ -250,20 +232,18 @@ int amdgpu_userq_input_va_validate(struct amdgpu_device *adev, size = expected_size >> AMDGPU_GPU_PAGE_SHIFT; va_map = amdgpu_vm_bo_lookup_mapping(vm, user_addr); - if (!va_map) { - r = -EINVAL; - goto out_err; - } + if (!va_map) + return -EINVAL; + /* Only validate the userq whether resident in the VM mapping range */ if (user_addr >= va_map->start && va_map->last - user_addr + 1 >= size) { - amdgpu_userq_buffer_va_list_add(queue, va_map, user_addr); + va_map->bo_va->userq_va_mapped = true; + *va_out = user_addr; return 0; } - r = -EINVAL; -out_err: - return r; + return -EINVAL; } static bool amdgpu_userq_buffer_va_mapped(struct amdgpu_vm *vm, u64 addr) @@ -284,14 +264,16 @@ static bool amdgpu_userq_buffer_va_mapped(struct amdgpu_vm *vm, u64 addr) static bool amdgpu_userq_buffer_vas_mapped(struct amdgpu_usermode_queue *queue) { - struct amdgpu_userq_va_cursor *va_cursor, *tmp; - int r = 0; + int i, r = 0; - list_for_each_entry_safe(va_cursor, tmp, &queue->userq_va_list, list) { - r += amdgpu_userq_buffer_va_mapped(queue->vm, va_cursor->gpu_addr); + for (i = 0; i < ARRAY_SIZE(queue->userq_vas.va_array); i++) { + if (!queue->userq_vas.va_array[i]) + continue; + r += amdgpu_userq_buffer_va_mapped(queue->vm, + queue->userq_vas.va_array[i]); dev_dbg(queue->userq_mgr->adev->dev, "validate the userq mapping:%p va:%llx r:%d\n", - queue, va_cursor->gpu_addr, r); + queue, queue->userq_vas.va_array[i], r); } if (r != 0) @@ -300,24 +282,7 @@ static bool amdgpu_userq_buffer_vas_mapped(struct amdgpu_usermode_queue *queue) return false; } -static void amdgpu_userq_buffer_vas_list_cleanup(struct amdgpu_device *adev, - struct amdgpu_usermode_queue *queue) -{ - struct amdgpu_userq_va_cursor *va_cursor, *tmp; - struct amdgpu_bo_va_mapping *mapping; - /* Caller must hold vm->root.bo reservation */ - dma_resv_assert_held(queue->vm->root.bo->tbo.base.resv); - - list_for_each_entry_safe(va_cursor, tmp, &queue->userq_va_list, list) { - mapping = amdgpu_vm_bo_lookup_mapping(queue->vm, va_cursor->gpu_addr); - if (mapping) - dev_dbg(adev->dev, "delete the userq:%p va:%llx\n", - queue, va_cursor->gpu_addr); - list_del(&va_cursor->list); - kfree(va_cursor); - } -} static int amdgpu_userq_preempt_helper(struct amdgpu_usermode_queue *queue) { @@ -540,7 +505,6 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que { struct amdgpu_device *adev = uq_mgr->adev; const struct amdgpu_userq_funcs *uq_funcs = adev->userq_funcs[queue->queue_type]; - struct amdgpu_vm *vm = queue->vm; int r = 0; cancel_delayed_work_sync(&uq_mgr->resume_work); @@ -560,10 +524,6 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que mutex_unlock(&uq_mgr->userq_mutex); cancel_delayed_work_sync(&queue->hang_detect_work); - amdgpu_bo_reserve(vm->root.bo, true); - amdgpu_userq_buffer_vas_list_cleanup(adev, queue); - amdgpu_bo_unreserve(vm->root.bo); - list_del(&queue->userq_va_list); uq_funcs->mqd_destroy(queue); queue->userq_mgr = NULL; @@ -669,7 +629,6 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) } kref_init(&queue->refcount); - INIT_LIST_HEAD(&queue->userq_va_list); queue->doorbell_handle = args->in.doorbell_handle; queue->queue_type = args->in.ip_type; queue->vm = &fpriv->vm; @@ -690,14 +649,17 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) goto free_fence_drv; if (amdgpu_userq_input_va_validate(adev, queue, args->in.queue_va, - args->in.queue_size) || + args->in.queue_size, + &queue->userq_vas.va.queue_rb) || amdgpu_userq_input_va_validate(adev, queue, args->in.rptr_va, - AMDGPU_GPU_PAGE_SIZE) || + AMDGPU_GPU_PAGE_SIZE, + &queue->userq_vas.va.rptr) || amdgpu_userq_input_va_validate(adev, queue, args->in.wptr_va, - AMDGPU_GPU_PAGE_SIZE)) { + AMDGPU_GPU_PAGE_SIZE, + &queue->userq_vas.va.wptr)) { r = -EINVAL; amdgpu_bo_unreserve(fpriv->vm.root.bo); - goto clean_mapping; + goto free_fence_drv; } amdgpu_bo_unreserve(fpriv->vm.root.bo); @@ -709,7 +671,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) r = amdgpu_userq_get_doorbell_index(uq_mgr, &db_info, filp, &index); if (r) { drm_file_err(uq_mgr->file, "Failed to get doorbell for queue\n"); - goto clean_mapping; + goto free_fence_drv; } queue->doorbell_index = index; @@ -769,10 +731,6 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) amdgpu_bo_unpin(queue->db_obj.obj); amdgpu_bo_unreserve(queue->db_obj.obj); amdgpu_bo_unref(&queue->db_obj.obj); -clean_mapping: - amdgpu_bo_reserve(fpriv->vm.root.bo, true); - amdgpu_userq_buffer_vas_list_cleanup(adev, queue); - amdgpu_bo_unreserve(fpriv->vm.root.bo); free_fence_drv: amdgpu_userq_fence_driver_free(queue); free_queue: diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 76ef5cfab52e..28cfc6682333 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -48,11 +48,6 @@ struct amdgpu_userq_obj { struct amdgpu_bo *obj; }; -struct amdgpu_userq_va_cursor { - u64 gpu_addr; - struct list_head list; -}; - struct amdgpu_usermode_queue { int queue_type; enum amdgpu_userq_state state; @@ -93,7 +88,17 @@ struct amdgpu_usermode_queue { struct delayed_work hang_detect_work; struct kref refcount; - struct list_head userq_va_list; + union { + struct { + u64 queue_rb; + u64 wptr; + u64 rptr; + u64 eop; + u64 shadow; + u64 csa; + } va; + u64 va_array[6]; + } userq_vas; }; struct amdgpu_userq_funcs { @@ -174,7 +179,8 @@ void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell); int amdgpu_userq_input_va_validate(struct amdgpu_device *adev, struct amdgpu_usermode_queue *queue, - u64 addr, u64 expected_size); + u64 addr, u64 expected_size, u64 *va_out); + void amdgpu_userq_gem_va_unmap_validate(struct amdgpu_device *adev, struct amdgpu_bo_va_mapping *mapping, uint64_t saddr); diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 35af26b36d0d..09a6f50cc5f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -318,8 +318,9 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, kfree(compute_mqd); goto free_mqd; } - r = amdgpu_userq_input_va_validate(adev, queue, compute_mqd->eop_va, - 2048); + r = amdgpu_userq_input_va_validate(adev, queue, + compute_mqd->eop_va, 2048, + &queue->userq_vas.va.eop); amdgpu_bo_unreserve(queue->vm->root.bo); if (r) { kfree(compute_mqd); @@ -368,7 +369,8 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, goto free_mqd; } r = amdgpu_userq_input_va_validate(adev, queue, mqd_gfx_v11->shadow_va, - shadow_info.shadow_size); + shadow_info.shadow_size, + &queue->userq_vas.va.shadow); if (r) { amdgpu_bo_unreserve(queue->vm->root.bo); kfree(mqd_gfx_v11); @@ -376,7 +378,8 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, } r = amdgpu_userq_input_va_validate(adev, queue, mqd_gfx_v11->csa_va, - shadow_info.csa_size); + shadow_info.csa_size, + &queue->userq_vas.va.csa); amdgpu_bo_unreserve(queue->vm->root.bo); if (r) { kfree(mqd_gfx_v11); @@ -406,7 +409,8 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, goto free_mqd; } r = amdgpu_userq_input_va_validate(adev, queue, mqd_sdma_v11->csa_va, - 32); + 32, + &queue->userq_vas.va.csa); amdgpu_bo_unreserve(queue->vm->root.bo); if (r) { kfree(mqd_sdma_v11); From a71db9b8f77bab7b730b9f00a769d8f7a9e3dafd Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Mon, 11 May 2026 17:27:29 +0800 Subject: [PATCH 50/58] drm/amdgpu: init locals in umc_v12_0_convert_error_address row, col, col_lower, row_lower, row_high and bank could be read on code paths that never assign them. Initialize them to 0. Signed-off-by: Stanley.Yang Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c index db505ab32fa0..14092150336a 100644 --- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c @@ -285,7 +285,8 @@ static int umc_v12_0_convert_error_address(struct amdgpu_device *adev, struct ta_ras_query_address_output *addr_out, bool dump_addr) { - uint32_t col, col_lower, row, row_lower, row_high, bank; + uint32_t row = 0, row_lower = 0, row_high = 0; + uint32_t col = 0, col_lower = 0, bank = 0; uint32_t channel_index = 0, umc_inst = 0; uint32_t i, bit_num, retire_unit, *flip_bits; uint64_t soc_pa, column, err_addr; From 82502db448aa9eca2c8d8ee8b424f37aa9ce3ac0 Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Mon, 11 May 2026 19:44:16 +0800 Subject: [PATCH 51/58] drm/amd/ras: cap pending_ecc_list size Drop new entries once pending_ecc_count hits RAS_UMC_PENDING_ECC_MAX (8192) so an ECC storm or repeated UMC error injection cannot exhaust kernel memory. Dropped events are counted and reported via a rate-limited warning. Signed-off-by: Stanley.Yang Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h | 9 ++++++ drivers/gpu/drm/amd/ras/rascore/ras_umc.c | 35 +++++++++++++++++++++++ drivers/gpu/drm/amd/ras/rascore/ras_umc.h | 12 ++++++++ 3 files changed, 56 insertions(+) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h b/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h index 8156531a7b63..f34dda7ce87b 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h +++ b/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h @@ -46,6 +46,15 @@ printk(KERN_WARNING fmt, ##__VA_ARGS__); \ } while (0) +#define RAS_DEV_WARN_RATELIMITED(device, fmt, ...) \ + do { \ + if (device) \ + dev_warn_ratelimited(((struct amdgpu_device *)device)->dev, \ + fmt, ##__VA_ARGS__); \ + else \ + printk_ratelimited(KERN_WARNING fmt, ##__VA_ARGS__); \ + } while (0) + #define RAS_DEV_INFO(device, fmt, ...) \ do { \ if (device) \ diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c index 91dd730de3ce..f32ee2fecf53 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c @@ -193,12 +193,29 @@ static void ras_umc_reserve_eeprom_record(struct ras_core_context *ras_core, } /* When gpu reset is ongoing, ecc logging operations will be pended. + * + * The pending list is bounded by RAS_UMC_PENDING_ECC_MAX so that an ECC + * storm or repeated UMC error injection cannot make this list (and the + * kernel allocations behind it) grow without bound. Once the limit is + * reached, additional events are dropped and counted in + * pending_ecc_dropped, with a rate-limited warning emitted. */ int ras_umc_log_bad_bank_pending(struct ras_core_context *ras_core, struct ras_bank_ecc *bank) { struct ras_umc *ras_umc = &ras_core->ras_umc; struct ras_bank_ecc_node *ecc_node; + mutex_lock(&ras_umc->pending_ecc_lock); + if (ras_umc->pending_ecc_count >= RAS_UMC_PENDING_ECC_MAX) { + ras_umc->pending_ecc_dropped++; + mutex_unlock(&ras_umc->pending_ecc_lock); + RAS_DEV_WARN_RATELIMITED(ras_core->dev, + "pending ECC list full (%u), dropping bad bank event (total dropped:%u)\n", + RAS_UMC_PENDING_ECC_MAX, ras_umc->pending_ecc_dropped); + return -ENOSPC; + } + mutex_unlock(&ras_umc->pending_ecc_lock); + ecc_node = kzalloc_obj(*ecc_node); if (!ecc_node) return -ENOMEM; @@ -206,7 +223,15 @@ int ras_umc_log_bad_bank_pending(struct ras_core_context *ras_core, struct ras_b memcpy(&ecc_node->ecc, bank, sizeof(ecc_node->ecc)); mutex_lock(&ras_umc->pending_ecc_lock); + /* re-check under the lock to honor the cap across concurrent callers */ + if (ras_umc->pending_ecc_count >= RAS_UMC_PENDING_ECC_MAX) { + ras_umc->pending_ecc_dropped++; + mutex_unlock(&ras_umc->pending_ecc_lock); + kfree(ecc_node); + return -ENOSPC; + } list_add_tail(&ecc_node->node, &ras_umc->pending_ecc_list); + ras_umc->pending_ecc_count++; mutex_unlock(&ras_umc->pending_ecc_lock); return 0; @@ -225,8 +250,16 @@ int ras_umc_log_pending_bad_bank(struct ras_core_context *ras_core) if (!ras_umc_log_bad_bank(ras_core, &ecc_node->ecc)) { list_del(&ecc_node->node); kfree(ecc_node); + if (ras_umc->pending_ecc_count) + ras_umc->pending_ecc_count--; } } + if (ras_umc->pending_ecc_dropped) { + RAS_DEV_WARN(ras_core->dev, + "%u pending ECC bad-bank events were dropped during GPU reset\n", + ras_umc->pending_ecc_dropped); + ras_umc->pending_ecc_dropped = 0; + } mutex_unlock(&ras_umc->pending_ecc_lock); return 0; @@ -609,6 +642,8 @@ int ras_umc_sw_fini(struct ras_core_context *ras_core) list_del(&ecc_node->node); kfree(ecc_node); } + ras_umc->pending_ecc_count = 0; + ras_umc->pending_ecc_dropped = 0; mutex_unlock(&ras_umc->pending_ecc_lock); mutex_destroy(&ras_umc->tree_lock); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h index 1d3026be509b..237525b46b9b 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h @@ -139,8 +139,20 @@ struct ras_umc { struct mutex pending_ecc_lock; struct ras_umc_err_data umc_err_data; struct list_head pending_ecc_list; + /* number of entries currently queued on pending_ecc_list */ + u32 pending_ecc_count; + /* number of entries dropped because pending_ecc_list was full */ + u32 pending_ecc_dropped; }; +/* + * Upper bound on entries that can be queued on pending_ecc_list while a + * GPU reset is in progress. Beyond this, new ECC events are dropped to + * prevent unbounded kernel memory growth in case of an ECC storm or + * malicious/repeated UMC error injection. + */ +#define RAS_UMC_PENDING_ECC_MAX 8192 + int ras_umc_sw_init(struct ras_core_context *ras); int ras_umc_sw_fini(struct ras_core_context *ras); int ras_umc_hw_init(struct ras_core_context *ras); From c08972f555945cda57b0adb72272a37910153390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Wed, 18 Feb 2026 12:53:27 +0100 Subject: [PATCH 52/58] drm/amdgpu: fix amdgpu_hmm_range_get_pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The notifier sequence must only be read once or otherwise we could work with invalid pages. While at it also fix the coding style, e.g. drop the pre-initialized return value and use the common define for 2G range. Signed-off-by: Christian König Reviewed-by: Vitaly Prosyak Tested-by: Vitaly Prosyak Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c index f72990ac046e..0a9582da3a33 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c @@ -51,8 +51,6 @@ #include "amdgpu_amdkfd.h" #include "amdgpu_hmm.h" -#define MAX_WALK_BYTE (2UL << 30) - /** * amdgpu_hmm_invalidate_gfx - callback to notify about mm change * @@ -170,11 +168,13 @@ int amdgpu_hmm_range_get_pages(struct mmu_interval_notifier *notifier, void *owner, struct amdgpu_hmm_range *range) { - unsigned long end; + const u64 max_bytes = SZ_2G; + + struct hmm_range *hmm_range = &range->hmm_range; unsigned long timeout; unsigned long *pfns; - int r = 0; - struct hmm_range *hmm_range = &range->hmm_range; + unsigned long end; + int r; pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL); if (unlikely(!pfns)) { @@ -191,8 +191,9 @@ int amdgpu_hmm_range_get_pages(struct mmu_interval_notifier *notifier, end = start + npages * PAGE_SIZE; hmm_range->dev_private_owner = owner; + hmm_range->notifier_seq = mmu_interval_read_begin(notifier); do { - hmm_range->end = min(hmm_range->start + MAX_WALK_BYTE, end); + hmm_range->end = min(hmm_range->start + max_bytes, end); pr_debug("hmm range: start = 0x%lx, end = 0x%lx", hmm_range->start, hmm_range->end); @@ -200,7 +201,6 @@ int amdgpu_hmm_range_get_pages(struct mmu_interval_notifier *notifier, timeout = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT); retry: - hmm_range->notifier_seq = mmu_interval_read_begin(notifier); r = hmm_range_fault(hmm_range); if (unlikely(r)) { if (r == -EBUSY && !time_after(jiffies, timeout)) @@ -210,7 +210,7 @@ int amdgpu_hmm_range_get_pages(struct mmu_interval_notifier *notifier, if (hmm_range->end == end) break; - hmm_range->hmm_pfns += MAX_WALK_BYTE >> PAGE_SHIFT; + hmm_range->hmm_pfns += max_bytes >> PAGE_SHIFT; hmm_range->start = hmm_range->end; } while (hmm_range->end < end); From b6444d1bcbc34f6f2a31a3aab3059be082f3683e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Wed, 18 Feb 2026 12:31:29 +0100 Subject: [PATCH 53/58] drm/amdgpu: fix calling VM invalidation in amdgpu_hmm_invalidate_gfx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise we don't invalidate page tables on next CS. Signed-off-by: Christian König Reviewed-by: Vitaly Prosyak Tested-by: Vitaly Prosyak Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c index 0a9582da3a33..5bfa5a84b09c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c @@ -76,6 +76,7 @@ static bool amdgpu_hmm_invalidate_gfx(struct mmu_interval_notifier *mni, mmu_interval_set_seq(mni, cur_seq); + amdgpu_vm_bo_invalidate(bo, false); r = dma_resv_wait_timeout(bo->tbo.base.resv, DMA_RESV_USAGE_BOOKKEEP, false, MAX_SCHEDULE_TIMEOUT); mutex_unlock(&adev->notifier_lock); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index a99d01f9dc75..79efefd25a4f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -1600,6 +1600,7 @@ int amdgpu_vm_handle_moved(struct amdgpu_device *adev, { struct amdgpu_bo_va *bo_va, *tmp; struct dma_resv *resv; + struct amdgpu_bo *bo; bool clear, unlock; int r; @@ -1615,11 +1616,13 @@ int amdgpu_vm_handle_moved(struct amdgpu_device *adev, while (!list_empty(&vm->individual.moved)) { bo_va = list_first_entry(&vm->individual.moved, typeof(*bo_va), base.vm_status); - resv = bo_va->base.bo->tbo.base.resv; + bo = bo_va->base.bo; + resv = bo->tbo.base.resv; spin_unlock(&vm->individual_lock); /* Try to reserve the BO to avoid clearing its ptes */ - if (!adev->debug_vm && dma_resv_trylock(resv)) { + if (!adev->debug_vm && !amdgpu_ttm_tt_get_usermm(bo->tbo.ttm) && + dma_resv_trylock(resv)) { clear = false; unlock = true; /* The caller is already holding the reservation lock */ From d18ecfee5187e3b2dbff7e67dda8b2f54ff5231c Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Wed, 13 May 2026 17:53:57 -0400 Subject: [PATCH 54/58] drm/amd/display: Initialize dsc_caps to 0 [Why&How] If we don't do that we make DSC decisions based on random inputs, which might result in disallowing DSC when the monitor and HW support it. Reviewed-by: Harry Wentland Signed-off-by: Ivan Lipski Signed-off-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ba7f98a87808..c5a5e6ca4958 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -7473,7 +7473,7 @@ create_stream_for_sink(struct drm_connector *connector, int preferred_refresh = 0; enum color_transfer_func tf = TRANSFER_FUNC_UNKNOWN; #if defined(CONFIG_DRM_AMD_DC_FP) - struct dsc_dec_dpcd_caps dsc_caps; + struct dsc_dec_dpcd_caps dsc_caps = {0}; #endif struct dc_link *link = NULL; struct dc_sink *sink = NULL; From 8bdabc34f3f203348b405e4fefcd7516f8a82039 Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Sat, 23 May 2026 10:27:47 -0400 Subject: [PATCH 55/58] drm/radeon/radeon_connectors: use struct drm_edid instead of struct edid This was done with amdgpu, just bringing the same patch to radeon. The goal of this is to stop using the deprecated edid functions, specifically drm_connector_update_edid_property. Switch to struct drm_edid and the appropriate function replacements for the new type. Also, for audio, use the raw edid for SADB allocations and for equivalent drm_edid_is_digital expressions. Signed-off-by: Joshua Peisach Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_audio.c | 4 +-- drivers/gpu/drm/radeon/radeon_combios.c | 4 +-- drivers/gpu/drm/radeon/radeon_connectors.c | 30 +++++++++++----------- drivers/gpu/drm/radeon/radeon_mode.h | 4 +-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_audio.c b/drivers/gpu/drm/radeon/radeon_audio.c index 8d64ba18572e..52d0049738b1 100644 --- a/drivers/gpu/drm/radeon/radeon_audio.c +++ b/drivers/gpu/drm/radeon/radeon_audio.c @@ -311,7 +311,7 @@ static void radeon_audio_write_sad_regs(struct drm_encoder *encoder) if (!connector) return; - sad_count = drm_edid_to_sad(radeon_connector->edid, &sads); + sad_count = drm_edid_to_sad(drm_edid_raw(radeon_connector->edid), &sads); if (sad_count < 0) DRM_ERROR("Couldn't read SADs: %d\n", sad_count); if (sad_count <= 0) @@ -335,7 +335,7 @@ static void radeon_audio_write_speaker_allocation(struct drm_encoder *encoder) if (!connector) return; - sad_count = drm_edid_to_speaker_allocation(radeon_connector->edid, &sadb); + sad_count = drm_edid_to_speaker_allocation(drm_edid_raw(radeon_connector->edid), &sadb); if (sad_count < 0) { DRM_DEBUG("Couldn't read Speaker Allocation Data Block: %d\n", sad_count); diff --git a/drivers/gpu/drm/radeon/radeon_combios.c b/drivers/gpu/drm/radeon/radeon_combios.c index 6a4e268ffd99..b3e7a747f11e 100644 --- a/drivers/gpu/drm/radeon/radeon_combios.c +++ b/drivers/gpu/drm/radeon/radeon_combios.c @@ -390,10 +390,10 @@ bool radeon_combios_check_hardcoded_edid(struct radeon_device *rdev) } /* this is used for atom LCDs as well */ -struct edid * +const struct drm_edid * radeon_bios_get_hardcoded_edid(struct radeon_device *rdev) { - return drm_edid_duplicate(drm_edid_raw(rdev->mode_info.bios_hardcoded_edid)); + return drm_edid_dup(rdev->mode_info.bios_hardcoded_edid); } static struct radeon_i2c_bus_rec combios_setup_i2c_bus(struct radeon_device *rdev, diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 373f08a125ff..443e584ba844 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -271,8 +271,8 @@ static void radeon_connector_get_edid(struct drm_connector *connector) if ((radeon_connector_encoder_get_dp_bridge_encoder_id(connector) != ENCODER_OBJECT_ID_NONE) && radeon_connector->ddc_bus->has_aux) { - radeon_connector->edid = drm_get_edid(connector, - &radeon_connector->ddc_bus->aux.ddc); + radeon_connector->edid = drm_edid_read_ddc(connector, + &radeon_connector->ddc_bus->aux.ddc); } else if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { struct radeon_connector_atom_dig *dig = radeon_connector->con_priv; @@ -280,19 +280,19 @@ static void radeon_connector_get_edid(struct drm_connector *connector) if ((dig->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT || dig->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) && radeon_connector->ddc_bus->has_aux) - radeon_connector->edid = drm_get_edid(&radeon_connector->base, - &radeon_connector->ddc_bus->aux.ddc); + radeon_connector->edid = drm_edid_read_ddc(&radeon_connector->base, + &radeon_connector->ddc_bus->aux.ddc); else if (radeon_connector->ddc_bus) - radeon_connector->edid = drm_get_edid(&radeon_connector->base, - &radeon_connector->ddc_bus->adapter); + radeon_connector->edid = drm_edid_read_ddc(&radeon_connector->base, + &radeon_connector->ddc_bus->adapter); } else if (vga_switcheroo_handler_flags() & VGA_SWITCHEROO_CAN_SWITCH_DDC && connector->connector_type == DRM_MODE_CONNECTOR_LVDS && radeon_connector->ddc_bus) { - radeon_connector->edid = drm_get_edid_switcheroo(&radeon_connector->base, - &radeon_connector->ddc_bus->adapter); + radeon_connector->edid = drm_edid_read_switcheroo(&radeon_connector->base, + &radeon_connector->ddc_bus->adapter); } else if (radeon_connector->ddc_bus) { - radeon_connector->edid = drm_get_edid(&radeon_connector->base, - &radeon_connector->ddc_bus->adapter); + radeon_connector->edid = drm_edid_read_ddc(&radeon_connector->base, + &radeon_connector->ddc_bus->adapter); } if (!radeon_connector->edid) { @@ -328,11 +328,11 @@ static int radeon_ddc_get_modes(struct drm_connector *connector) int ret; if (radeon_connector->edid) { - drm_connector_update_edid_property(connector, radeon_connector->edid); - ret = drm_add_edid_modes(connector, radeon_connector->edid); + drm_edid_connector_update(connector, radeon_connector->edid); + ret = drm_edid_connector_add_modes(connector); return ret; } - drm_connector_update_edid_property(connector, NULL); + drm_edid_connector_update(connector, NULL); return 0; } @@ -1016,7 +1016,7 @@ radeon_vga_detect(struct drm_connector *connector, bool force) ret = connector_status_connected; } else { radeon_connector->use_digital = - !!(radeon_connector->edid->input & DRM_EDID_INPUT_DIGITAL); + drm_edid_is_digital(radeon_connector->edid); /* some oems have boards with separate digital and analog connectors * with a shared ddc line (often vga + hdmi) @@ -1271,7 +1271,7 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) } } else { radeon_connector->use_digital = - !!(radeon_connector->edid->input & DRM_EDID_INPUT_DIGITAL); + drm_edid_is_digital(radeon_connector->edid); /* some oems have boards with separate digital and analog connectors * with a shared ddc line (often vga + hdmi) diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index ae1ecdc2e189..031a3bf6fe0a 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -525,7 +525,7 @@ struct radeon_connector { bool use_digital; /* we need to mind the EDID between detect and get modes due to analog/digital/tvencoder */ - struct edid *edid; + const struct drm_edid *edid; void *con_priv; bool dac_load_detect; bool detected_by_load; /* if the connection status was determined by load */ @@ -839,7 +839,7 @@ radeon_get_crtc_scanout_position(struct drm_crtc *crtc, bool in_vblank_irq, const struct drm_display_mode *mode); extern bool radeon_combios_check_hardcoded_edid(struct radeon_device *rdev); -extern struct edid * +extern const struct drm_edid * radeon_bios_get_hardcoded_edid(struct radeon_device *rdev); extern bool radeon_atom_get_clock_info(struct drm_device *dev); extern bool radeon_combios_get_clock_info(struct drm_device *dev); From c9b2ad100b10dabe0bea691001b0808b4f0fcc55 Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Sat, 23 May 2026 10:27:48 -0400 Subject: [PATCH 56/58] drm/radeon/radeon_connectors: remove radeon_connector_free_edid Since we are using struct drm_edid, we can call drm_edid_free directly. Also make sure to set the pointer to NULL afterwards. Signed-off-by: Joshua Peisach Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_connectors.c | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 443e584ba844..16b048f58c9b 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -314,14 +314,6 @@ static void radeon_connector_get_edid(struct drm_connector *connector) } } -static void radeon_connector_free_edid(struct drm_connector *connector) -{ - struct radeon_connector *radeon_connector = to_radeon_connector(connector); - - kfree(radeon_connector->edid); - radeon_connector->edid = NULL; -} - static int radeon_ddc_get_modes(struct drm_connector *connector) { struct radeon_connector *radeon_connector = to_radeon_connector(connector); @@ -895,7 +887,9 @@ static void radeon_connector_destroy(struct drm_connector *connector) { struct radeon_connector *radeon_connector = to_radeon_connector(connector); - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; + kfree(radeon_connector->con_priv); drm_connector_unregister(connector); drm_connector_cleanup(connector); @@ -1007,7 +1001,8 @@ radeon_vga_detect(struct drm_connector *connector, bool force) dret = radeon_ddc_probe(radeon_connector, false); if (dret) { radeon_connector->detected_by_load = false; - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; radeon_connector_get_edid(connector); if (!radeon_connector->edid) { @@ -1022,7 +1017,8 @@ radeon_vga_detect(struct drm_connector *connector, bool force) * with a shared ddc line (often vga + hdmi) */ if (radeon_connector->use_digital && radeon_connector->shared_ddc) { - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; ret = connector_status_disconnected; } else { ret = connector_status_connected; @@ -1251,7 +1247,8 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) } if (dret) { radeon_connector->detected_by_load = false; - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; radeon_connector_get_edid(connector); if (!radeon_connector->edid) { @@ -1277,7 +1274,8 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) * with a shared ddc line (often vga + hdmi) */ if ((!radeon_connector->use_digital) && radeon_connector->shared_ddc) { - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; ret = connector_status_disconnected; } else { ret = connector_status_connected; @@ -1301,7 +1299,8 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) if (list_connector->connector_type != DRM_MODE_CONNECTOR_VGA) { /* hpd is our only option in this case */ if (!radeon_hpd_sense(rdev, radeon_connector->hpd.hpd)) { - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; ret = connector_status_disconnected; } } @@ -1635,7 +1634,8 @@ radeon_dp_detect(struct drm_connector *connector, bool force) goto out; } - radeon_connector_free_edid(connector); + drm_edid_free(radeon_connector->edid); + radeon_connector->edid = NULL; if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { From 989bdec8ca04a4f4855981a68ad295dcc257291f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Fri, 22 May 2026 11:22:10 +0200 Subject: [PATCH 57/58] drm/amdgpu: fix amdgpu_vm_bo_reset_state_machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Can't splice the list but need to handle each entry individually. Otherwise we run into issues after a GPU reset. Fixes: 4cdbba5a16aa ("drm/amdgpu: restructure VM state machine v4") Signed-off-by: Christian König Reviewed-by: Jesse Zhang Tested-by: Jesse Zhang Reviewed-by: Sunil Khatri Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 79efefd25a4f..4558a5663c66 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -266,12 +266,23 @@ static void amdgpu_vm_bo_idle(struct amdgpu_vm_bo_base *vm_bo) */ static void amdgpu_vm_bo_reset_state_machine(struct amdgpu_vm *vm) { + struct amdgpu_vm_bo_base *vm_bo, *tmp; + + /* + * Don't use list splice here, we need the special handling for the root + * PD and set the moved flag appropriately. + */ amdgpu_vm_assert_locked(vm); - list_splice_init(&vm->kernel.idle, &vm->kernel.moved); - list_splice_init(&vm->always_valid.idle, &vm->always_valid.moved); + list_for_each_entry_safe(vm_bo, tmp, &vm->kernel.idle, vm_status) + amdgpu_vm_bo_moved(vm_bo); + list_for_each_entry_safe(vm_bo, tmp, &vm->always_valid.idle, vm_status) + amdgpu_vm_bo_moved(vm_bo); spin_lock(&vm->individual_lock); - list_splice_init(&vm->individual.idle, &vm->individual.moved); + list_for_each_entry_safe(vm_bo, tmp, &vm->individual.idle, vm_status) { + vm_bo->moved = true; + list_move(&vm_bo->vm_status, &vm->individual.moved); + } spin_unlock(&vm->individual_lock); } From 470d1ae31d29f90b8998c5c08ee0b267a05fe378 Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Wed, 27 May 2026 14:57:17 -0400 Subject: [PATCH 58/58] drm/amd/display: Add a default case for dc_status_to_str [Why&How] If a parsed dc_status case is not covered by the dc_status_to_str, the switch case is skipped, and the function returns "Unexpected status error". This causes build failures when new dc_status enums are introduced. Changing the 'return "Unexpected status error"' into default resolves it. Signed-off-by: Ivan Lipski Reviewed-by: Alex Deucher Reviewed-by: Alex Hung Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_debug.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_debug.c b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c index bbce751b485f..44028ba88f80 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_debug.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c @@ -272,9 +272,9 @@ char *dc_status_to_str(enum dc_status status) return "Fail DP Tunnel BW validation"; case DC_ERROR_UNEXPECTED: return "Unexpected error"; + default: + return "Unexpected status error"; } - - return "Unexpected status error"; } char *dc_pixel_encoding_to_str(enum dc_pixel_encoding pixel_encoding)