From 658422c7ba2e78c8fb4d30c1d5475cc444709e74 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Oct 2025 16:38:10 -0400 Subject: [PATCH 001/155] drm/amdgpu: update mmhub 4.2.0 client list Update to the proper client list for mmhub 4.2.0. v2: fix typo (Alex) Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c | 65 ++++++++++++----------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c b/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c index 49b7f16a941f..5827c758b373 100644 --- a/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c @@ -36,40 +36,41 @@ static const char *mmhub_client_ids_v4_2_0[][2] = { [0][0] = "VMC", - [4][0] = "DCEDMC", - [5][0] = "DCEVGA", - [6][0] = "MP0", - [7][0] = "MP1", + [2][0] = "MPNHT", + [7][0] = "MPIFOE", [8][0] = "MPIO", - [16][0] = "HDP", - [17][0] = "LSDMA", - [18][0] = "JPEG", - [19][0] = "VCNU0", - [21][0] = "VSCH", - [22][0] = "VCNU1", - [23][0] = "VCN1", - [32+20][0] = "VCN0", - [2][1] = "DBGUNBIO", - [3][1] = "DCEDWB", - [4][1] = "DCEDMC", - [5][1] = "DCEVGA", - [6][1] = "MP0", - [7][1] = "MP1", + [11][0] = "JPEG0", + [12][0] = "VCN0", + [13][0] = "VCNU0", + [14][0] = "VSCH0", + [15][0] = "LSDMA", + [32+5][0] = "MPRAS", + [32+6][0] = "MP1", + [32+7][0] = "MP0", + [32+11][0] = "JPEG1", + [32+12][0] = "VCN1", + [32+13][0] = "VCNU1", + [32+14][0] = "VSCH1", + [2][1] = "MPNHT", + [3][1] = "DBGU0", + [7][1] = "MPIFOE", [8][1] = "MPIO", - [10][1] = "DBGU0", - [11][1] = "DBGU1", - [12][1] = "DBGU2", - [13][1] = "DBGU3", - [14][1] = "XDP", - [15][1] = "OSSSYS", - [16][1] = "HDP", - [17][1] = "LSDMA", - [18][1] = "JPEG", - [19][1] = "VCNU0", - [20][1] = "VCN0", - [21][1] = "VSCH", - [22][1] = "VCNU1", - [23][1] = "VCN1", + [10][1] = "UTCL2_NHT", + [11][1] = "JPEG0", + [12][1] = "VCN0", + [13][1] = "VCNU0", + [14][1] = "VSCH0", + [15][1] = "LSDMA", + [32+3][1] = "DBGU1", + [32+4][1] = "DBGU2", + [32+5][1] = "MPRAS", + [32+6][1] = "MP1", + [32+7][1] = "MP0", + [32+8][1] = "IH", + [32+11][1] = "JPEG1", + [32+12][1] = "VCN1", + [32+13][1] = "VCNU1", + [32+14][1] = "VSCH1", }; static int mmhub_v4_2_0_get_xgmi_info(struct amdgpu_device *adev) From f8ee6447e7ec1d75d6663c817e45566dd01f440b Mon Sep 17 00:00:00 2001 From: Roman Li Date: Wed, 13 May 2026 21:49:15 -0400 Subject: [PATCH 002/155] drm/amdgpu/discovery: Fix device family for DCN42 GC 11.7.0 and 11.7.1 should map to AMDGPU_FAMILY_GC_11_5_4 for DCN42. Fixes: cf591e67c095 ("drm/amdgpu: add support for GC IP version 11.7.0") Fixes: a928d8d81ec5 ("drm/amdgpu: add support for GC IP version 11.7.1") Signed-off-by: Roman Li Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index d9edac5f321d..44e7d2e3e6df 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -3367,9 +3367,11 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + adev->family = AMDGPU_FAMILY_GC_11_5_0; + break; case IP_VERSION(11, 7, 0): case IP_VERSION(11, 7, 1): - adev->family = AMDGPU_FAMILY_GC_11_5_0; + adev->family = AMDGPU_FAMILY_GC_11_5_4; break; case IP_VERSION(12, 0, 0): case IP_VERSION(12, 0, 1): From b48b1cad84609ed422f8d4086205cc06d425487e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 16 Jun 2026 09:34:09 -0600 Subject: [PATCH 003/155] drm/amd/display: Fix KUnit backlight tests for CACP [WHY] A change was added in amdgpu_dm_should_create_sysfs() to check link->panel_type instead of caps->aux_support, and removed the OLED exclusion from the ABM property attach path in amdgpu_dm_setup_backlight_device(). This broke three KUnit tests: - dm_test_should_create_sysfs_no_backlight_index and dm_test_should_create_sysfs_pwm_backlight returned false because kzalloc zeroes panel_type but PANEL_TYPE_LCD is 1. - dm_test_setup_backlight_device_oled_success crashed with a NULL pointer dereference in drm_object_attach_property since abm_level_property was NULL. [WHY] Set panel_type to PANEL_TYPE_LCD in the two sysfs tests and skip the ABM property attach path in the OLED setup test by setting amdgpu_dm_abm_level to 0. Cc: Chenyu Chen Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index fff50c1325c6..53df4b668f2e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -1026,6 +1026,7 @@ static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_LCD; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1063,6 +1064,7 @@ static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_LCD; fixture.adev->dm.backlight_caps[0].aux_support = false; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1147,11 +1149,13 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) struct dm_backlight_connector_fixture fixture = {}; struct amdgpu_display_manager *dm; int saved_backlight = amdgpu_dm_get_backlight_param(); + int saved_abm_level = amdgpu_dm_get_abm_level_param(); amdgpu_dm_set_backlight_param(-1); + /* Skip ABM property attach (requires full DRM object setup) */ + amdgpu_dm_set_abm_level_param(0); setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); fixture.link->type = dc_connection_single; - /* OLED panel avoids the ABM property attach path */ fixture.link->dpcd_sink_ext_caps.bits.oled = 1; dm = &fixture.adev->dm; dm->adev = fixture.adev; @@ -1166,6 +1170,7 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) KUNIT_EXPECT_TRUE(test, dm->backlight_caps[0].aux_support); amdgpu_dm_set_backlight_param(saved_backlight); + amdgpu_dm_set_abm_level_param(saved_abm_level); } static struct kunit_case dm_backlight_test_cases[] = { From 5b69770e945caa1bef6c4848bb279ec09d3e9d4f Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Tue, 23 Jun 2026 15:42:34 -0400 Subject: [PATCH 004/155] drm/amd/display: Generalize DMUB memory flushes from host [WHY&HOW] Add DMUB service context to aid in platform abstraction when flushing memory from the host. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Dillon Varone Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 5 +- drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c | 4 +- .../gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c | 4 +- drivers/gpu/drm/amd/display/dmub/dmub_srv.h | 9 ++- .../gpu/drm/amd/display/dmub/src/dmub_srv.c | 55 +++++++++++-------- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 2692d1890ba2..00ea0dbd0f3f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -147,7 +147,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) struct dmub_srv_hw_params hw_params; enum dmub_status status; const unsigned char *fw_inst_const, *fw_bss_data; - u32 i, fw_inst_const_size, fw_bss_data_size; + u32 fw_inst_const_size, fw_bss_data_size; bool has_hw_support; if (!dmub_srv) @@ -243,8 +243,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) if (dmcu) hw_params.psp_version = dmcu->psp_version; - for (i = 0; i < fb_info->num_fb; ++i) - hw_params.fb[i] = &fb_info->fb[i]; + hw_params.fb_info = fb_info; /* Enable usb4 dpia in the FW APU */ if (dc->caps.is_apu && diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c index 68ed0e16639d..80a21d7cae90 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c @@ -1908,6 +1908,8 @@ static void dc_dmub_srv_ib_based_fams2_update_config(struct dc *dc, config->global.features.bits.enable = enable && context->bw_ctx.bw.dcn.fams2_global_config.features.bits.enable; config->global.features.bits.enable_ppt_check = dc->debug.fams2_config.bits.enable_ppt_check; + dmub_srv_flush_buffer_mem(dc->ctx->dmub_srv->dmub, &dc->ctx->dmub_srv->dmub->ib_mem_gart); + dm_execute_dmub_cmd_list(dc->ctx, 1, &cmd, DM_DMUB_WAIT_TYPE_WAIT); } @@ -2073,7 +2075,7 @@ bool dc_dmub_srv_ips_query_residency_info(const struct dc_context *ctx, uint8_t union dmub_rb_cmd cmd; uint32_t bytes = sizeof(struct dmub_ips_residency_info); - dmub_flush_buffer_mem(&ctx->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(ctx->dmub_srv->dmub, &ctx->dmub_srv->dmub->scratch_mem_fb); memset(&cmd, 0, sizeof(cmd)); cmd.ips_query_residency_info.header.type = DMUB_CMD__IPS; diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c index 2f4d368bd3fd..f60e43ba6060 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c @@ -166,7 +166,7 @@ void dmub_abm_init_config(struct abm *abm, uint8_t panel_mask = 0x01 << inst; // TODO: Optimize by only reading back final 4 bytes - dmub_flush_buffer_mem(&dc->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); // Copy iramtable into cw7 memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)src, bytes); @@ -227,7 +227,7 @@ bool dmub_abm_save_restore( unsigned int bytes = sizeof(struct abm_save_restore); // TODO: Optimize by only reading back final 4 bytes - dmub_flush_buffer_mem(&dc->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); // Copy iramtable into cw7 memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)pData, bytes); diff --git a/drivers/gpu/drm/amd/display/dmub/dmub_srv.h b/drivers/gpu/drm/amd/display/dmub/dmub_srv.h index f4d05dcfef29..94d4b40ee16a 100644 --- a/drivers/gpu/drm/amd/display/dmub/dmub_srv.h +++ b/drivers/gpu/drm/amd/display/dmub/dmub_srv.h @@ -329,7 +329,7 @@ struct dmub_soc_fb_info { * @load_inst_const: true if DMUB should load inst const fw */ struct dmub_srv_hw_params { - struct dmub_fb *fb[DMUB_WINDOW_TOTAL]; + struct dmub_srv_fb_info *fb_info; struct dmub_soc_fb_info soc_fb_info; uint32_t psp_version; bool load_inst_const; @@ -592,9 +592,11 @@ struct dmub_srv { void *user_ctx; uint32_t fw_version; bool is_virtual; + bool no_ext_reg_access; struct dmub_fb scratch_mem_fb; struct dmub_fb ib_mem_gart; struct dmub_fb cursor_offload_fb; + const struct dmub_srv_fb_info *fb_info; volatile struct dmub_shared_state_feature_block *shared_state; volatile struct dmub_cursor_offload_v1 *cursor_offload_v1; volatile const struct dmub_fw_state *fw_state; @@ -954,14 +956,15 @@ enum dmub_status dmub_srv_get_gpint_dataout(struct dmub_srv *dmub, uint32_t *dataout); /** - * dmub_flush_buffer_mem() - Read back entire frame buffer region. + * dmub_srv_flush_buffer_mem() - Read back entire frame buffer region. * This ensures that the write from x86 has been flushed and will not * hang the DMCUB. + * @dmub: the dmub service * @fb: frame buffer to flush * * Can be called after software initialization. */ -void dmub_flush_buffer_mem(const struct dmub_fb *fb); +void dmub_srv_flush_buffer_mem(struct dmub_srv *dmub, const struct dmub_fb *fb); /** * dmub_srv_get_fw_boot_status() - Returns the DMUB boot status bits. diff --git a/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c b/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c index 7463d2ae5055..5a1a2276c432 100644 --- a/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c @@ -96,7 +96,7 @@ static inline uint32_t dmub_align(uint32_t val, uint32_t factor) return (val + factor - 1) / factor * factor; } -void dmub_flush_buffer_mem(const struct dmub_fb *fb) +void dmub_srv_flush_buffer_mem(struct dmub_srv *dmub, const struct dmub_fb *fb) { const uint8_t *base = (const uint8_t *)fb->cpu_addr; uint8_t buf[64]; @@ -114,6 +114,8 @@ void dmub_flush_buffer_mem(const struct dmub_fb *fb) /* Read anything leftover into the buffer. */ if (end < fb->size) dmub_memcpy(buf, base + pos, fb->size - end); + + (void)dmub; } static const struct dmub_fw_meta_info * @@ -748,31 +750,38 @@ enum dmub_status dmub_srv_is_hw_init(struct dmub_srv *dmub, bool *is_hw_init) enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, const struct dmub_srv_hw_params *params) { - struct dmub_fb *inst_fb = params->fb[DMUB_WINDOW_0_INST_CONST]; - struct dmub_fb *stack_fb = params->fb[DMUB_WINDOW_1_STACK]; - struct dmub_fb *data_fb = params->fb[DMUB_WINDOW_2_BSS_DATA]; - struct dmub_fb *bios_fb = params->fb[DMUB_WINDOW_3_VBIOS]; - struct dmub_fb *mail_fb = params->fb[DMUB_WINDOW_4_MAILBOX]; - struct dmub_fb *tracebuff_fb = params->fb[DMUB_WINDOW_5_TRACEBUFF]; - struct dmub_fb *fw_state_fb = params->fb[DMUB_WINDOW_6_FW_STATE]; - struct dmub_fb *shared_state_fb = params->fb[DMUB_WINDOW_SHARED_STATE]; + struct dmub_fb *inst_fb; + struct dmub_fb *stack_fb; + struct dmub_fb *data_fb; + struct dmub_fb *bios_fb; + struct dmub_fb *mail_fb; + struct dmub_fb *tracebuff_fb; + struct dmub_fb *fw_state_fb; + struct dmub_fb *shared_state_fb; struct dmub_rb_init_params rb_params, outbox0_rb_params; struct dmub_window cw0, cw1, cw2, cw3, cw4, cw5, cw6, region6; struct dmub_region inbox1, outbox1, outbox0; - uint32_t i; - if (!dmub->sw_init) return DMUB_STATUS_INVALID; - for (i = 0; i < DMUB_WINDOW_TOTAL; ++i) { - if (!params->fb[i]) { - ASSERT(0); - return DMUB_STATUS_INVALID; - } + if (!params->fb_info || params->fb_info->num_fb < DMUB_WINDOW_TOTAL) { + ASSERT(0); + return DMUB_STATUS_INVALID; } + inst_fb = ¶ms->fb_info->fb[DMUB_WINDOW_0_INST_CONST]; + stack_fb = ¶ms->fb_info->fb[DMUB_WINDOW_1_STACK]; + data_fb = ¶ms->fb_info->fb[DMUB_WINDOW_2_BSS_DATA]; + bios_fb = ¶ms->fb_info->fb[DMUB_WINDOW_3_VBIOS]; + mail_fb = ¶ms->fb_info->fb[DMUB_WINDOW_4_MAILBOX]; + tracebuff_fb = ¶ms->fb_info->fb[DMUB_WINDOW_5_TRACEBUFF]; + fw_state_fb = ¶ms->fb_info->fb[DMUB_WINDOW_6_FW_STATE]; + shared_state_fb = ¶ms->fb_info->fb[DMUB_WINDOW_SHARED_STATE]; + + dmub->fb_info = params->fb_info; + memcpy(&dmub->soc_fb_info, ¶ms->soc_fb_info, sizeof(params->soc_fb_info)); dmub->psp_version = params->psp_version; @@ -800,7 +809,7 @@ enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, * flushed yet. This only occurs in backdoor loading. */ if (params->mem_access_type == DMUB_MEMORY_ACCESS_CPU) - dmub_flush_buffer_mem(inst_fb); + dmub_srv_flush_buffer_mem(dmub, inst_fb); if (params->fw_in_system_memory && dmub->hw_funcs.backdoor_load_zfb_mode) dmub->hw_funcs.backdoor_load_zfb_mode(dmub, &cw0, &cw1); @@ -851,10 +860,10 @@ enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, dmub->shared_state = shared_state_fb->cpu_addr; - dmub->scratch_mem_fb = *params->fb[DMUB_WINDOW_7_SCRATCH_MEM]; - dmub->ib_mem_gart = *params->fb[DMUB_WINDOW_IB_MEM]; + dmub->scratch_mem_fb = params->fb_info->fb[DMUB_WINDOW_7_SCRATCH_MEM]; + dmub->ib_mem_gart = params->fb_info->fb[DMUB_WINDOW_IB_MEM]; - dmub->cursor_offload_fb = *params->fb[DMUB_WINDOW_CURSOR_OFFLOAD]; + dmub->cursor_offload_fb = params->fb_info->fb[DMUB_WINDOW_CURSOR_OFFLOAD]; dmub->cursor_offload_v1 = (struct dmub_cursor_offload_v1 *)dmub->cursor_offload_fb.cpu_addr; if (dmub->hw_funcs.setup_windows) @@ -1014,13 +1023,14 @@ enum dmub_status dmub_srv_wait_for_hw_pwr_up(struct dmub_srv *dmub, enum dmub_status dmub_srv_wait_for_auto_load(struct dmub_srv *dmub, uint32_t timeout_us) { + const uint32_t delay_us = 100; uint32_t i; bool hw_on = true; if (!dmub->hw_init) return DMUB_STATUS_INVALID; - for (i = 0; i <= timeout_us; i += 100) { + for (i = 0; i <= timeout_us; i += delay_us) { union dmub_fw_boot_status status = dmub->hw_funcs.get_fw_status(dmub); if (dmub->hw_funcs.is_hw_powered_up) @@ -1029,7 +1039,7 @@ enum dmub_status dmub_srv_wait_for_auto_load(struct dmub_srv *dmub, if (status.bits.dal_fw && status.bits.mailbox_rdy && hw_on) return DMUB_STATUS_OK; - udelay(100); + udelay(delay_us); } return DMUB_STATUS_TIMEOUT; @@ -1258,6 +1268,7 @@ bool dmub_srv_get_diagnostic_data(struct dmub_srv *dmub) if (!dmub || !dmub->hw_funcs.get_diagnostic_data) return false; dmub->hw_funcs.get_diagnostic_data(dmub); + return true; } From 7b0cc308521696107828b0c6c72c5793fa279cbd Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Thu, 5 Mar 2026 20:11:50 +0800 Subject: [PATCH 005/155] drm/amd/display: Enable CACP on Linux [Why] Enable OLED panels to save more power on the Linux platform by enabling CACP support on Linux. [How] Update abm_feature_support() to return ABM_CACP_SUPPORT for OLED panels. Enable ABM property for all eDP panels including OLED to support CACP via set_abm_level. Reviewed-by: Sun peng (Leo) Li Signed-off-by: Chenyu Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 23 +- .../display/amdgpu_dm/amdgpu_dm_connector.c | 53 ++++- drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/dc_types.h | 8 + drivers/gpu/drm/amd/display/dc/dce/Makefile | 4 +- drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c | 73 +++++- .../drm/amd/display/dc/dce/dmub_abm_cacp.c | 213 ++++++++++++++++++ .../drm/amd/display/dc/dce/dmub_abm_cacp.h | 20 ++ .../amd/display/dc/hwss/dcn21/dcn21_hwseq.c | 40 +++- .../amd/display/dc/hwss/dcn21/dcn21_hwseq.h | 2 + .../amd/display/dc/hwss/dcn31/dcn31_hwseq.c | 8 + drivers/gpu/drm/amd/display/dc/inc/hw/abm.h | 3 + .../drm/amd/display/modules/inc/mod_power.h | 11 + .../drm/amd/display/modules/power/power_abm.c | 126 +++++++++++ .../amd/display/modules/power/power_helpers.h | 10 + 15 files changed, 566 insertions(+), 29 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c create mode 100644 drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 33f4be403a65..11d54897a894 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -559,7 +559,6 @@ EXPORT_IF_KUNIT(amdgpu_dm_update_connector_ext_caps); void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector) { - struct amdgpu_dm_backlight_caps *caps; struct dc_link *link = aconnector->dc_link; int bl_idx = dm->num_of_edps; @@ -579,10 +578,12 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, dm->num_of_edps++; amdgpu_dm_update_connector_ext_caps(aconnector); - caps = &dm->backlight_caps[aconnector->bl_idx]; - /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ - if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) + /* Offer ABM property when user didn't turn off by module parameter. + * OLED panels are included to support CACP (Content Adaptive + * Contrast and Power) feature via set_abm_level. + */ + if (amdgpu_dm_abm_level < 0) drm_object_attach_property(&aconnector->base.base, dm->adev->mode_info.abm_level_property, ABM_SYSFS_CONTROL); @@ -666,22 +667,16 @@ const struct attribute_group amdgpu_group = { bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) { + struct dc_link *link = amdgpu_dm_connector->dc_link; + if (amdgpu_dm_abm_level >= 0) return false; if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) return false; - /* check for OLED panels */ - if (amdgpu_dm_connector->bl_idx >= 0) { - struct drm_device *drm = amdgpu_dm_connector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct amdgpu_dm_backlight_caps *caps; - - caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; - if (caps->aux_support) - return false; - } + if (link->panel_type != PANEL_TYPE_LCD && !link->panel_config.cacp.cacp_supported) + return false; return true; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 88f938a8f24f..57be01c60f7d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -392,32 +392,31 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } -static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_display_info *display_info = &connector->display_info; struct dc_link *link = aconnector->dc_link; struct amdgpu_device *adev; + enum dc_panel_type panel_type = PANEL_TYPE_NONE; adev = drm_to_adev(connector->dev); - link->panel_type = PANEL_TYPE_NONE; - switch (display_info->amd_vsdb.panel_type) { case AMD_VSDB_PANEL_TYPE_OLED: - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; break; case AMD_VSDB_PANEL_TYPE_MINILED: - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; break; } /* If VSDB didn't determine panel type, check DPCD ext caps */ - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { if (link->dpcd_sink_ext_caps.bits.miniled == 1) - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; if (link->dpcd_sink_ext_caps.bits.oled == 1) - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; } /* If VSDB and DPCD didn't determine panel type, check DID */ @@ -428,7 +427,7 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->panel_type = PANEL_TYPE_OLED; } - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; u32 lum1_max = vsdb->luminance_range1.max_luminance; u32 lum2_max = vsdb->luminance_range2.max_luminance; @@ -437,9 +436,14 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->local_sink->edid_caps.manufacturer_id == DDC_MANUFACTURERNAME_SAMSUNG && lum1_max >= ((lum2_max * 3) / 2)) - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; } + if (panel_type != PANEL_TYPE_NONE) + link->panel_type = panel_type; + else + link->panel_type = PANEL_TYPE_LCD; + if (link->panel_type == PANEL_TYPE_OLED) drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, @@ -456,6 +460,32 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } +static void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) +{ + struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); + struct dc_link *link = aconnector->dc_link; + + link->panel_config.cacp.cacp_supported = true; + + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 1, 4) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(3, 1, 6)) { + link->panel_config.cacp.cacp_supported = false; + return; + } + + if (link->connector_signal != SIGNAL_TYPE_EDP && + link->connector_signal != SIGNAL_TYPE_LVDS) { + link->panel_config.cacp.cacp_supported = false; + return; + } + + if (link->panel_type == PANEL_TYPE_LCD) + link->panel_config.cacp.cacp_supported = false; + + drm_dbg_kms(aconnector->base.dev, "cacp_supported: %d\n", + link->panel_config.cacp.cacp_supported); +} + DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) void amdgpu_dm_update_connector_after_detect( @@ -581,7 +611,8 @@ void amdgpu_dm_update_connector_after_detect( amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); amdgpu_dm_update_connector_ext_caps(aconnector); - dm_set_panel_type(aconnector); + amdgpu_dm_set_panel_type(aconnector); + amdgpu_dm_update_cacp_caps(aconnector); if (aconnector->hdmi_comp_auto) { if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 13c1f7cd9d7d..2b59a0e9ca38 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -632,6 +632,7 @@ enum visual_confirm { VISUAL_CONFIRM_SMARTMUX_DGPU = 10, VISUAL_CONFIRM_REPLAY = 12, VISUAL_CONFIRM_SUBVP = 14, + VISUAL_CONFIRM_ABM = 15, VISUAL_CONFIRM_MCLK_SWITCH = 16, VISUAL_CONFIRM_FAMS2 = 19, VISUAL_CONFIRM_HW_CURSOR = 20, diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 90dd1ae7e953..5ba7a2fffcf0 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -1347,6 +1347,14 @@ struct dc_panel_config { struct ilr { bool optimize_edp_link_rate; /* eDP ILR */ } ilr; + /* CACP*/ + struct cacp { + unsigned int cacp_supported; + unsigned int cacp_control_mode; + unsigned int strscl_valid; + unsigned int strscl_sdr[4]; + unsigned int strscl_hdr[4]; + } cacp; /* Adaptive VariBright*/ struct adaptive_vb { bool disable_adaptive_vb; diff --git a/drivers/gpu/drm/amd/display/dc/dce/Makefile b/drivers/gpu/drm/amd/display/dc/dce/Makefile index 986e0e7abbc2..72bf70d89b74 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dce/Makefile @@ -23,14 +23,14 @@ # Makefile for common 'dce' logic # HW object file under this folder follow similar pattern for HW programming # - register offset and/or shift + mask stored in the dec_hw struct -# - register programming through common macros that look up register +# - register programming through common macros that look up register # offset/shift/mask stored in dce_hw struct DCE = dce_audio.o dce_stream_encoder.o dce_link_encoder.o \ dce_mem_input.o dce_clock_source.o dce_scl_filters.o dce_transform.o \ dce_opp.o dce_dmcu.o dce_abm.o dce_ipp.o dce_aux.o \ dce_i2c.o dce_i2c_hw.o dce_i2c_sw.o dmub_psr.o dmub_abm.o dmub_abm_lcd.o dce_panel_cntl.o \ -dmub_hw_lock_mgr.o dmub_outbox.o dmub_replay.o +dmub_hw_lock_mgr.o dmub_outbox.o dmub_replay.o dmub_abm_cacp.o AMD_DAL_DCE = $(addprefix $(AMDDALPATH)/dc/dce/,$(DCE)) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c index 93550c5e4d02..2df6e82b794f 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c @@ -25,6 +25,7 @@ #include "dmub_abm.h" #include "dmub_abm_lcd.h" +#include "dmub_abm_cacp.h" #include "dc.h" #include "core_types.h" #include "dmub_cmd.h" @@ -36,6 +37,7 @@ #define ABM_FEATURE_NO_SUPPORT 0 #define ABM_LCD_SUPPORT 1 +#define ABM_CACP_SUPPORT 2 static unsigned int abm_feature_support(struct abm *abm, unsigned int panel_inst) { @@ -52,15 +54,48 @@ static unsigned int abm_feature_support(struct abm *abm, unsigned int panel_inst } if (i < edp_num) { - ret = ABM_LCD_SUPPORT; + if (edp_links[panel_inst]->panel_config.cacp.cacp_supported) + ret = ABM_CACP_SUPPORT; + else if ((edp_links[panel_inst]->panel_type == PANEL_TYPE_LCD) || + (edp_links[panel_inst]->panel_type == PANEL_TYPE_MINILED)) + ret = ABM_LCD_SUPPORT; } return ret; } +static enum dc_panel_type abm_get_paneltype(struct abm *abm, unsigned int panel_inst) +{ + struct dc_context *dc = abm->ctx; + struct dc_link *edp_links[MAX_NUM_EDP]; + unsigned int i, edp_num; + enum dc_panel_type ret = PANEL_TYPE_NONE; + + dc_get_edp_links(dc->dc, edp_links, &edp_num); + + for (i = 0; i < edp_num; i++) { + if (edp_links[i]->link_status.link_active + && panel_inst == i) + break; + } + + if (i < edp_num) + ret = edp_links[panel_inst]->panel_type; + + return ret; +} + static void dmub_abm_init_ex(struct abm *abm, uint32_t backlight, uint32_t user_level) { + unsigned int i = 0; + uint8_t panel_mask = 0; + dmub_abm_init(abm, backlight, user_level); + for (i = 0; i < MAX_NUM_EDP; i++) + panel_mask |= (0x01 << i); + + if (panel_mask) + dmub_cacp_enable_fractional_pwm(abm, panel_mask); } static unsigned int dmub_abm_get_current_backlight_ex(struct abm *abm) @@ -82,17 +117,23 @@ static bool dmub_abm_set_level_ex(struct abm *abm, uint32_t level) bool ret = false; unsigned int feature_support, i; uint8_t panel_mask0 = 0; + uint8_t panel_mask1 = 0; for (i = 0; i < MAX_NUM_EDP; i++) { feature_support = abm_feature_support(abm, i); if (feature_support == ABM_LCD_SUPPORT) panel_mask0 |= (0x01 << i); + else if (feature_support == ABM_CACP_SUPPORT) + panel_mask1 |= (0x01 << i); } if (panel_mask0) ret = dmub_abm_set_level(abm, level, panel_mask0); + if (panel_mask1) + ret = dmub_cacp_set_level(abm, level, panel_mask1); + return ret; } @@ -107,6 +148,8 @@ static bool dmub_abm_init_config_ex(struct abm *abm, if (feature_support == ABM_LCD_SUPPORT) dmub_abm_init_config(abm, src, bytes, inst); + else if (feature_support == ABM_CACP_SUPPORT) + dmub_cacp_init(abm, src, bytes, inst); return true; } @@ -120,6 +163,8 @@ static bool dmub_abm_set_pause_ex(struct abm *abm, bool pause, unsigned int pane if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_pause(abm, pause, panel_inst, stream_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_pause(abm, pause, panel_inst, stream_inst); return ret; } @@ -163,6 +208,25 @@ static bool dmub_abm_set_pipe_ex(struct abm *abm, if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_pipe(abm, otg_inst, option, panel_inst, pwrseq_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_pipe(abm, otg_inst, option, panel_inst, pwrseq_inst); + + return ret; +} + +static bool dmub_abm_set_event_ex(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int panel_inst) +{ + bool ret = false; + unsigned int feature_support; + + feature_support = abm_feature_support(abm, panel_inst); + + if (feature_support == ABM_LCD_SUPPORT) + ret = dmub_abm_set_event(abm, scaling_enable, scaling_strength_map, panel_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_event(abm, full_screen, trans_info, hdr_mode, scaling_enable, panel_inst); return ret; } @@ -176,11 +240,17 @@ static bool dmub_abm_set_backlight_level_pwm_ex(struct abm *abm, (void)controller_id; bool ret = false; unsigned int feature_support; + enum dc_panel_type panel_type = PANEL_TYPE_NONE; feature_support = abm_feature_support(abm, panel_inst); if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_backlight_level(abm, backlight_pwm_u16_16, frame_ramp, panel_inst); + else if (feature_support == ABM_CACP_SUPPORT) { + panel_type = abm_get_paneltype(abm, panel_inst); + if (panel_type == PANEL_TYPE_MINILED) + ret = dmub_cacp_set_backlight_level(abm, backlight_pwm_u16_16, frame_ramp, panel_inst); + } return ret; } @@ -194,6 +264,7 @@ static const struct abm_funcs abm_funcs = { .set_abm_pause = dmub_abm_set_pause_ex, .save_restore = dmub_abm_save_restore_ex, .set_pipe_ex = dmub_abm_set_pipe_ex, + .set_abm_event = dmub_abm_set_event_ex, .set_backlight_level_pwm = dmub_abm_set_backlight_level_pwm_ex, }; diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c new file mode 100644 index 000000000000..9f58fa2e25fd --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c @@ -0,0 +1,213 @@ +/* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. */ + +#include "dmub_abm.h" +#include "dmub_abm_cacp.h" +#include "dce_abm.h" +#include "dc.h" +#include "dc_dmub_srv.h" +#include "dmub/dmub_srv.h" +#include "core_types.h" + +#define CACP_LEVEL_NUM 4 + +void dmub_cacp_init(struct abm *abm, const char *src, unsigned int bytes, unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + uint8_t panel_mask = 0x01 << panel_inst; + struct dc_link *edp_links[MAX_NUM_EDP]; + unsigned int i, edp_num; + + // TODO: Optimize by only reading back final 4 bytes + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); + + // Copy iramtable into cw7 + memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)src, bytes); + + memset(&cmd, 0, sizeof(cmd)); + // Fw will copy from cw7 to fw_state + cmd.cacp_init_config.header.type = DMUB_CMD__CACP; + cmd.cacp_init_config.header.sub_type = DMUB_CMD__CACP_INIT_CONFIG; + cmd.cacp_init_config.cacp_init_config_data.src.quad_part = dc->dmub_srv->dmub->scratch_mem_fb.gpu_addr; + cmd.cacp_init_config.cacp_init_config_data.bytes = (uint16_t)bytes; + cmd.cacp_init_config.cacp_init_config_data.panel_mask = panel_mask; + cmd.cacp_init_config.cacp_init_config_data.visual_confirm = + (dc->dc->debug.visual_confirm == VISUAL_CONFIRM_ABM) ? true : false; + + cmd.cacp_init_config.header.payload_bytes = sizeof(struct dmub_cmd_cacp_init_config_data); + + dc_get_edp_links(dc->dc, edp_links, &edp_num); + for (i = 0; i < edp_num; i++) { + if (panel_inst == i) + break; + } + + if (i < edp_num) { + cmd.cacp_init_config.cacp_init_config_data.strscl_valid = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_valid; + cmd.cacp_init_config.cacp_init_config_data.mode = + edp_links[panel_inst]->panel_config.cacp.cacp_control_mode ? + DMUB_CMD_CACP_CONTROL_MODE_1 : DMUB_CMD_CACP_CONTROL_MODE_0; + for (int j = 0; j < CACP_LEVEL_NUM; j++) { + cmd.cacp_init_config.cacp_init_config_data.strscl_sdr[j] = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_sdr[j]; + cmd.cacp_init_config.cacp_init_config_data.strscl_hdr[j] = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_hdr[j]; + } + } + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); +} + +bool dmub_cacp_set_level(struct abm *abm, unsigned int abm_level, unsigned char panel_mask) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_level.header.type = DMUB_CMD__CACP; + cmd.cacp_set_level.header.sub_type = DMUB_CMD__CACP_SET_LEVEL; + + cmd.cacp_set_level.cacp_set_level_data.level = abm_level; + cmd.cacp_set_level.cacp_set_level_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_level.cacp_set_level_data.panel_mask = panel_mask; + + cmd.cacp_set_level.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_level_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +bool dmub_cacp_set_pipe(struct abm *abm, unsigned int otg_inst, + unsigned int pipe_option, unsigned int panel_inst, unsigned int pwrseq_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pipe.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pipe.header.sub_type = DMUB_CMD__CACP_SET_PIPE; + + cmd.cacp_set_pipe.cacp_set_pipe_data.otg_inst = (uint8_t)otg_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.panel_inst = (uint8_t)panel_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.set_pipe_option = (uint8_t)pipe_option; + cmd.cacp_set_pipe.cacp_set_pipe_data.pwrseq_inst = (uint8_t)pwrseq_inst; + cmd.cacp_set_pipe.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pipe_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +bool dmub_cacp_set_event(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_event.header.type = DMUB_CMD__CACP; + cmd.cacp_set_event.header.sub_type = DMUB_CMD__CACP_SET_EVENT; + + //TODO: + cmd.cacp_set_event.cacp_set_event_data.full_screen_mode = (uint8_t)full_screen; + cmd.cacp_set_event.cacp_set_event_data.trans_info = trans_info; + cmd.cacp_set_event.cacp_set_event_data.hdr_mode = (uint8_t)hdr_mode; + cmd.cacp_set_event.cacp_set_event_data.vb_scaling_enable = (uint8_t)scaling_enable; + cmd.cacp_set_event.cacp_set_event_data.panel_mask = (1<ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_pause.header.type = DMUB_CMD__CACP; + cmd.cacp_pause.header.sub_type = DMUB_CMD__CACP_PAUSE; + + cmd.cacp_pause.cacp_pause_data.panel_mask = (1<ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_backlight.header.type = DMUB_CMD__CACP; + cmd.cacp_set_backlight.header.sub_type = DMUB_CMD__CACP_SET_BACKLIGHT; + cmd.cacp_set_backlight.cacp_set_backlight_data.frame_ramp = frame_ramp; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_user_level = backlight_pwm_u16_16; + cmd.cacp_set_backlight.cacp_set_backlight_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_backlight.cacp_set_backlight_data.panel_mask = (0x01 << panel_inst); + cmd.cacp_set_backlight.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_backlight_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +void dmub_cacp_enable_fractional_pwm(struct abm *abm, uint8_t panel_mask) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + uint32_t fractional_pwm = (dc->dc->config.disable_fractional_pwm == false) ? 1 : 0; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pwm_frac.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pwm_frac.header.sub_type = DMUB_CMD__CACP_SET_PWM_FRAC; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.fractional_pwm = fractional_pwm; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.panel_mask = panel_mask; + cmd.cacp_set_pwm_frac.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pwm_frac_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); +} + +bool dmub_cacp_get_histogram( + struct dc_context *dc, + unsigned int panel_inst, + unsigned int *histogram, + enum dmub_abm_histogram_type histogram_type, + unsigned int size) +{ + union dmub_rb_cmd cmd; + + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_get_histogram.header.type = DMUB_CMD__CACP; + cmd.cacp_get_histogram.header.sub_type = DMUB_CMD__CACP_GET_HISTOGRAM; + + cmd.cacp_get_histogram.dest.quad_part = dc->dmub_srv->dmub->scratch_mem_fb.gpu_addr; + cmd.cacp_get_histogram.bytes = (uint16_t)size; + cmd.cacp_get_histogram.panel_inst = (uint8_t)panel_inst; + cmd.cacp_get_histogram.histogram_type = histogram_type; + cmd.cacp_get_histogram.header.payload_bytes = sizeof(struct dmub_rb_cmd_cacp_get_histogram); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + memcpy((void *)histogram, dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, size); + + return true; +} + diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h new file mode 100644 index 000000000000..3b3b712c1242 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h @@ -0,0 +1,20 @@ +/* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. */ + +#include "dmub_cmd.h" + +#ifndef __DMUB_ABM_CACP_H__ +#define __DMUB_ABM_CACP_H__ + +void dmub_cacp_init(struct abm *abm, const char *src, unsigned int bytes, unsigned int panel_inst); +bool dmub_cacp_set_level(struct abm *abm, unsigned int cacp_level, unsigned char panel_mask); +bool dmub_cacp_set_pipe(struct abm *abm, unsigned int otg_inst, unsigned int pipe_option, + unsigned int panel_inst, unsigned int pwrseq_inst); +bool dmub_cacp_set_event(struct abm *abm, unsigned int full_screen, unsigned int trans_info, unsigned int hdr_mode, + unsigned int scaling_enable, unsigned int panel_inst); +bool dmub_cacp_set_pause(struct abm *abm, bool pause, unsigned int panel_inst, unsigned int otg_inst); +bool dmub_cacp_set_backlight_level(struct abm *abm, unsigned int backlight_pwm_u16_16, unsigned int frame_ramp, + unsigned int panel_inst); +void dmub_cacp_enable_fractional_pwm(struct abm *abm, uint8_t panel_mask); +bool dmub_cacp_get_histogram(struct dc_context *dc, unsigned int panel_inst, unsigned int *histogram, + enum dmub_abm_histogram_type histogram_type, unsigned int size); +#endif diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c index 476c2112afec..2be68f8dd0fc 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c @@ -136,6 +136,26 @@ void dcn21_PLAT_58856_wa(struct dc_state *context, struct pipe_ctx *pipe_ctx) pipe_ctx->stream->dpms_off = true; } +bool dcn21_dmub_cacp_set_pipe(struct abm *abm, uint32_t otg_inst, + uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pipe.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pipe.header.sub_type = DMUB_CMD__CACP_SET_PIPE; + cmd.cacp_set_pipe.cacp_set_pipe_data.otg_inst = (uint8_t)otg_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.pwrseq_inst = (uint8_t)pwrseq_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.set_pipe_option = (uint8_t)option; + cmd.cacp_set_pipe.cacp_set_pipe_data.panel_inst = (uint8_t)panel_inst; + cmd.cacp_set_pipe.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pipe_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + bool dcn21_dmub_abm_set_pipe(struct abm *abm, uint32_t otg_inst, uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst) { @@ -181,10 +201,12 @@ void dcn21_set_abm_immediate_disable(struct pipe_ctx *pipe_ctx) uint32_t otg_inst = pipe_ctx->stream_res.tg->inst; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; struct dmcu *dmcu = pipe_ctx->stream->ctx->dc->res_pool->dmcu; + struct dc_link *link = pipe_ctx->stream->link; // make a short term w/a for an issue that backlight ramping unexpectedly paused in the middle, // will decouple backlight from ABM and redefine DMUB interface, then this w/a could be removed - if (pipe_ctx->stream->abm_level == 0 || pipe_ctx->stream->abm_level == ABM_LEVEL_IMMEDIATE_DISABLE) { + if ((pipe_ctx->stream->abm_level == 0 || pipe_ctx->stream->abm_level == ABM_LEVEL_IMMEDIATE_DISABLE) + && (link && !link->panel_config.cacp.cacp_supported)) { return; } @@ -197,6 +219,9 @@ void dcn21_set_abm_immediate_disable(struct pipe_ctx *pipe_ctx) if (abm->funcs && abm->funcs->set_pipe_ex) { abm->funcs->set_pipe_ex(abm, otg_inst, SET_ABM_PIPE_IMMEDIATELY_DISABLE, panel_cntl->inst, panel_cntl->pwrseq_inst); + } else if (link && link->panel_config.cacp.cacp_supported) { + dcn21_dmub_cacp_set_pipe(abm, otg_inst, SET_CACP_PIPE_IMMEDIATELY_DISABLE, + panel_cntl->inst, panel_cntl->pwrseq_inst); } else { dcn21_dmub_abm_set_pipe(abm, otg_inst, @@ -214,6 +239,7 @@ void dcn21_set_pipe(struct pipe_ctx *pipe_ctx) struct timing_generator *tg = pipe_ctx->stream_res.tg; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; struct dmcu *dmcu = pipe_ctx->stream->ctx->dc->res_pool->dmcu; + struct dc_link *link = pipe_ctx->stream->link; uint32_t otg_inst; if (!abm || !tg || !panel_cntl) @@ -233,6 +259,11 @@ void dcn21_set_pipe(struct pipe_ctx *pipe_ctx) panel_cntl->inst, panel_cntl->pwrseq_inst); } else { + + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, SET_CACP_PIPE_NORMAL, + panel_cntl->inst, panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, panel_cntl->inst, @@ -250,6 +281,7 @@ bool dcn21_set_backlight_level(struct pipe_ctx *pipe_ctx, uint32_t otg_inst; uint32_t backlight_pwm_u16_16 = backlight_level_params->backlight_pwm_u16_16; uint32_t frame_ramp = backlight_level_params->frame_ramp; + struct dc_link *link = pipe_ctx->stream->link; if (!abm || !tg || !panel_cntl) return false; @@ -268,6 +300,12 @@ bool dcn21_set_backlight_level(struct pipe_ctx *pipe_ctx, panel_cntl->inst, panel_cntl->pwrseq_inst); } else { + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, + SET_CACP_PIPE_NORMAL, + panel_cntl->inst, + panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h index f72a27ac1bf1..aae13447cfa8 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h @@ -47,6 +47,8 @@ void dcn21_optimize_pwr_state( void dcn21_PLAT_58856_wa(struct dc_state *context, struct pipe_ctx *pipe_ctx); +bool dcn21_dmub_cacp_set_pipe(struct abm *abm, uint32_t otg_inst, + uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst); bool dcn21_dmub_abm_set_pipe(struct abm *abm, uint32_t otg_inst, uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst); void dcn21_set_pipe(struct pipe_ctx *pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index 43e4edfe9182..e72e10c276de 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c @@ -54,6 +54,7 @@ #include "dce/dce_i2c_hw.h" #include "dce/dmub_abm_lcd.h" #include "dio/dcn10/dcn10_dio.h" +#include "dce/dmub_abm_cacp.h" #define DC_LOGGER_INIT(logger) @@ -726,12 +727,19 @@ bool dcn31_set_backlight_level(struct pipe_ctx *pipe_ctx, struct timing_generator *tg = pipe_ctx->stream_res.tg; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; uint32_t otg_inst; + struct dc_link *link = pipe_ctx->stream->link; if (!abm || !tg || !panel_cntl) return false; otg_inst = tg->inst; + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, + SET_CACP_PIPE_NORMAL, + panel_cntl->inst, + panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h b/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h index 3f0161d64675..6ae7cf109565 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h @@ -66,6 +66,9 @@ struct abm_funcs { unsigned int option, unsigned int panel_inst, unsigned int pwrseq_inst); + bool (*set_abm_event)(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int panel_inst); }; #endif diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h index f9814cf7bbdb..02bee3b1956d 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h @@ -328,6 +328,17 @@ bool mod_power_is_abm_active(struct mod_power *mod_power, const struct dc_link *link, unsigned int inst); +bool mod_power_is_abm_supported(struct mod_power *mod_power, + unsigned int inst); + +bool mod_power_abm_set_event(struct mod_power *mod_power, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst); + +bool mod_power_abm_set_strength(struct mod_power *mod_power, + unsigned int strength, + unsigned int inst); bool mod_power_set_psr_event(struct mod_power *mod_power, struct dc_stream_state *stream, bool set_event, diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index b9447cb7485b..5b972f04bf7a 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -1453,6 +1453,76 @@ bool mod_power_is_abm_active(struct mod_power *mod_power, return is_active; } +bool mod_power_is_abm_supported(struct mod_power *mod_power, + unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_is_abm_supported(dc->res_pool, inst)) + return false; + } else + return false; + + return true; +} + +bool mod_power_abm_set_event(struct mod_power *mod_power, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_set_abm_event(dc->res_pool, full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst)) + return false; + } else + return false; + + return true; +} + +bool mod_power_abm_set_strength(struct mod_power *mod_power, + unsigned int strength, + unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_set_abm_strength(dc->res_pool, strength, inst)) + return false; + } else + return false; + + return true; +} + static void fill_backlight_transform_table(struct dmcu_iram_parameters params, struct iram_table_v_2 *table) { @@ -1990,6 +2060,62 @@ bool dmub_init_abm_config(struct resource_pool *res_pool, return result; } +bool dmub_is_abm_supported(struct resource_pool *res_pool, unsigned int inst) +{ + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + return true; +} + +bool dmub_set_abm_event(struct resource_pool *res_pool, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int inst) +{ + bool result = false; + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + if (res_pool->multiple_abms[inst]) { + if (res_pool->multiple_abms[inst]->funcs->set_abm_event) + result = res_pool->multiple_abms[inst]->funcs->set_abm_event( + res_pool->multiple_abms[inst], full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst); + } else { + if (res_pool->abm->funcs->set_abm_event) + result = res_pool->abm->funcs->set_abm_event( + res_pool->abm, full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst); + } + + return result; +} + +bool dmub_set_abm_strength(struct resource_pool *res_pool, + unsigned int strength, + unsigned int inst) +{ + bool result = false; + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + if (res_pool->multiple_abms[inst]) { + if (res_pool->multiple_abms[inst]->funcs->set_abm_level) + result = res_pool->multiple_abms[inst]->funcs->set_abm_level( + res_pool->multiple_abms[inst], strength); + } else { + if (res_pool->abm->funcs->set_abm_level) + result = res_pool->abm->funcs->set_abm_level( + res_pool->abm, strength); + } + + return result; +} + bool dmcu_load_iram(struct dmcu *dmcu, struct dmcu_iram_parameters params) { diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h index 600da3e33126..548c8ff6ddb4 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h +++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h @@ -146,6 +146,16 @@ bool dmub_init_abm_config(struct resource_pool *res_pool, struct dmcu_iram_parameters params, unsigned int inst); +bool dmub_is_abm_supported(struct resource_pool *res_pool, + unsigned int inst); +bool dmub_set_abm_event(struct resource_pool *res_pool, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst); +bool dmub_set_abm_strength(struct resource_pool *res_pool, + unsigned int strength, + unsigned int inst); + void init_replay_config(struct dc_link *link, struct replay_config *pr_config); void set_replay_coasting_vtotal(struct dc_link *link, enum replay_coasting_vtotal_type type, From 329baa1c5f62bdce36b77d463b5c23c62ee59e4a Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Fri, 5 Jun 2026 16:21:29 -0400 Subject: [PATCH 006/155] drm/amd/display: Enable PSR and Replay on DCN4 variant [Part 2] [Why] Enable PSR and Panel Replay on a DCN4 variant for display power savings. [How] Enable PSR and Replay in the DCN4 variant panel config defaults. Fixes: 68737239e891 ("drm/amd/display: Enable PSR and Replay on DCN4 variant and fix AUX instance") Reviewed-by: George Zhang Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 60cbaf4f6fdf..330e82b0974b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -1016,9 +1016,9 @@ static struct hubp *dcn42b_hubp_create( } static const struct dc_panel_config dcn42b_panel_config_defaults = { .psr = { - .disable_psr = true, + .disable_psr = false, .disallow_psrsu = true, - .disallow_replay = true, + .disallow_replay = false, }, .ilr = { .optimize_edp_link_rate = true, From c52d32e6e7932c17ad9f202f6c86f5898a405b7c Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 7 Jul 2026 11:38:12 +0200 Subject: [PATCH 007/155] drm/amdgpu: Change system_unbound_wq with system_dfl_wq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit system_wq (per-CPU) and system_unbound_wq (unbound) are the older workqueue name, replaced by system_{percpu|dfl}_wq. The new workqueues have been introduced by: 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") Usage of older workqueues will now trigger a pr_warn_once() because they are marked as deprecated as per commit: 64d8eae3f895 ("workqueue: Add warnings and fallback if system_{unbound}_wq is used") So change the used workqueue with the newer, keeping the same behavior. Reviewed-by: Timur Kristóf Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c index 53be764968e4..95cceed4e971 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -545,7 +545,7 @@ void amdgpu_irq_delegate(struct amdgpu_device *adev, unsigned int num_dw) { amdgpu_ih_ring_write(adev, &adev->irq.ih_soft, entry->iv_entry, num_dw); - queue_work(system_unbound_wq, &adev->irq.ih_soft_work); + queue_work(system_dfl_wq, &adev->irq.ih_soft_work); } /** From b3f2f83e84b88a04efbfb63f7b9e0ac20ab64103 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 18 Jun 2026 16:39:46 -0400 Subject: [PATCH 008/155] drm/amdgpu/gfx12.1: Add ip dump support Add support for dumping IP register state. v2: fixes suggested by Mukul Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 277 +++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index b19a4956c222..1f7e243d2ad9 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -69,6 +69,127 @@ MODULE_FIRMWARE("amdgpu/gc_12_1_0_rlc_1.bin"); (SH_MEM_ALIGNMENT_MODE_UNALIGNED_GFX12_1_0 << SH_MEM_CONFIG__ALIGNMENT_MODE__SHIFT) | \ (3 << SH_MEM_CONFIG__INITIAL_INST_PREFETCH__SHIFT)) + +static const struct amdgpu_hwip_reg_entry gc_reg_list_12_1[] = { + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS2), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS3), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT3), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_BUSY_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_BUSY_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_ERROR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_HPD_STATUS0), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_CMD_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_CMD_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BASE_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BASE_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCPF_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCPC_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCPG_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regIA_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regIA_UTCL1_STATUS_2), + SOC15_REG_ENTRY_STR(GC, 0, regPA_CL_CNTL_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regRMI_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regSQC_CACHES), + SOC15_REG_ENTRY_STR(GC, 0, regSQG_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regWD_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_CNTL2), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_STATUS_LO32), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_STATUS_HI32), + SOC15_REG_ENTRY_STR(GC, 0, regCP_DEBUG), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_CNTL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_CNTL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_ME_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_PFP_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_RS64_INSTR_PNTR0), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_RS64_INSTR_PNTR1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_RS64_INSTR_PNTR), + /* cp header registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + /* SE status registers */ + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS_SE0), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS_SE1), +}; + +static const struct amdgpu_hwip_reg_entry gc_cp_reg_list_12_1[] = { + /* compute registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_VMID), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PERSISTENT_STATE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PIPE_PRIORITY), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_QUEUE_PRIORITY), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_QUANTUM), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_POLL_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_POLL_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_BASE_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_DEQUEUE_REQUEST), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_BASE_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_EVENTS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_BASE_ADDR_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CNTL_STACK_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CNTL_STACK_SIZE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_WG_STATE_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_SIZE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_GDS_RESOURCE_STATE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_ERROR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_WPTR_MEM), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_CNTL_STACK_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_CNTL_STACK_DW_CNT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_WG_STATE_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_DEQUEUE_STATUS), + /* cp header registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), +}; + static void gfx_v12_1_xcc_disable_gpa_mode(struct amdgpu_device *adev, int xcc_id); static void gfx_v12_1_set_ring_funcs(struct amdgpu_device *adev); static void gfx_v12_1_set_irq_funcs(struct amdgpu_device *adev); @@ -1149,6 +1270,155 @@ static int gfx_v12_1_rlc_backdoor_autoload_enable(struct amdgpu_device *adev) return 0; } +static void gfx_v12_1_alloc_ip_dump(struct amdgpu_device *adev) +{ + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + uint32_t *ptr, inst, num_xcc; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + + ptr = kcalloc(reg_count * num_xcc, sizeof(uint32_t), GFP_KERNEL); + if (!ptr) { + DRM_ERROR("Failed to allocate memory for GFX IP Dump\n"); + adev->gfx.ip_dump_core = NULL; + } else { + adev->gfx.ip_dump_core = ptr; + } + + /* Allocate memory for compute queue registers for all the instances */ + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + + ptr = kcalloc(reg_count * inst * num_xcc, sizeof(uint32_t), GFP_KERNEL); + if (!ptr) { + DRM_ERROR("Failed to allocate memory for Compute Queues IP Dump\n"); + adev->gfx.ip_dump_compute_queues = NULL; + } else { + adev->gfx.ip_dump_compute_queues = ptr; + } +} + +static void gfx_v12_1_ip_print(struct amdgpu_ip_block *ip_block, + struct drm_printer *p) +{ + struct amdgpu_device *adev = ip_block->adev; + uint32_t i, j, k; + uint32_t xcc_id, xcc_offset, inst_offset; + uint32_t num_xcc, reg, num_inst; + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + + if (!adev->gfx.ip_dump_core) + return; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + drm_printf(p, "Number of Instances:%d\n", num_xcc); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count; + drm_printf(p, "\nInstance id:%d\n", xcc_id); + for (i = 0; i < reg_count; i++) + drm_printf(p, "%-50s \t 0x%08x\n", + gc_reg_list_12_1[i].reg_name, + adev->gfx.ip_dump_core[xcc_offset + i]); + } + + /* print compute queue registers for all instances */ + if (!adev->gfx.ip_dump_compute_queues) + return; + + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + drm_printf(p, "\nnum_xcc: %d num_mec: %d num_pipe: %d num_queue: %d\n", + num_xcc, + adev->gfx.mec.num_mec, + adev->gfx.mec.num_pipe_per_mec, + adev->gfx.mec.num_queue_per_pipe); + + num_inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count * num_inst; + inst_offset = 0; + for (i = 0; i < adev->gfx.mec.num_mec; i++) { + for (j = 0; j < adev->gfx.mec.num_pipe_per_mec; j++) { + for (k = 0; k < adev->gfx.mec.num_queue_per_pipe; k++) { + drm_printf(p, + "\nxcc:%d mec:%d, pipe:%d, queue:%d\n", + xcc_id, i, j, k); + for (reg = 0; reg < reg_count; reg++) { + drm_printf(p, + "%-50s \t 0x%08x\n", + gc_cp_reg_list_12_1[reg].reg_name, + adev->gfx.ip_dump_compute_queues + [xcc_offset + inst_offset + + reg]); + } + inst_offset += reg_count; + } + } + } + } +} + +static void gfx_v12_1_ip_dump(struct amdgpu_ip_block *ip_block) +{ + struct amdgpu_device *adev = ip_block->adev; + uint32_t i, j, k; + uint32_t num_xcc, reg, num_inst; + uint32_t xcc_id, xcc_offset, inst_offset; + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + + if (!adev->gfx.ip_dump_core) + return; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + + amdgpu_gfx_off_ctrl(adev, false); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count; + for (i = 0; i < reg_count; i++) + adev->gfx.ip_dump_core[xcc_offset + i] = + RREG32(SOC15_REG_ENTRY_OFFSET_INST(gc_reg_list_12_1[i], + GET_INST(GC, xcc_id))); + } + amdgpu_gfx_off_ctrl(adev, true); + + /* dump compute queue registers for all instances */ + if (!adev->gfx.ip_dump_compute_queues) + return; + + num_inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + amdgpu_gfx_off_ctrl(adev, false); + mutex_lock(&adev->srbm_mutex); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count * num_inst; + inst_offset = 0; + for (i = 0; i < adev->gfx.mec.num_mec; i++) { + for (j = 0; j < adev->gfx.mec.num_pipe_per_mec; j++) { + for (k = 0; k < adev->gfx.mec.num_queue_per_pipe; k++) { + /* ME0 is for GFX so start from 1 for CP */ + soc_v1_0_grbm_select(adev, 1 + i, j, k, 0, + GET_INST(GC, xcc_id)); + + for (reg = 0; reg < reg_count; reg++) { + adev->gfx.ip_dump_compute_queues + [xcc_offset + + inst_offset + reg] = + RREG32(SOC15_REG_ENTRY_OFFSET_INST( + gc_cp_reg_list_12_1[reg], + GET_INST(GC, xcc_id))); + } + inst_offset += reg_count; + } + } + } + } + soc_v1_0_grbm_select(adev, 0, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_off_ctrl(adev, true); +} + static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) { uint16_t major_ver, minor_ver; @@ -1287,6 +1557,8 @@ static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; + gfx_v12_1_alloc_ip_dump(adev); + mutex_init(&adev->gfx.mec.reset_mutex); return 0; @@ -1326,6 +1598,9 @@ static int gfx_v12_1_sw_fini(struct amdgpu_ip_block *ip_block) gfx_v12_1_free_microcode(adev); amdgpu_gfx_sysfs_fini(adev); + kfree(adev->gfx.ip_dump_core); + kfree(adev->gfx.ip_dump_compute_queues); + return 0; } @@ -3912,6 +4187,8 @@ static const struct amd_ip_funcs gfx_v12_1_ip_funcs = { .set_clockgating_state = gfx_v12_1_set_clockgating_state, .set_powergating_state = gfx_v12_1_set_powergating_state, .get_clockgating_state = gfx_v12_1_get_clockgating_state, + .dump_ip_state = gfx_v12_1_ip_dump, + .print_ip_state = gfx_v12_1_ip_print, }; static const struct amdgpu_ring_funcs gfx_v12_1_ring_funcs_compute = { From c8a1066ccc9130edcbd2f092231cb71e283abdf5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:53:26 -0400 Subject: [PATCH 009/155] drm/amdgpu/gfx9: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 556e5cb3f4cd..1cad6dc9476e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -2442,12 +2442,12 @@ static int gfx_v9_0_sw_init(struct amdgpu_ip_block *ip_block) return -EINVAL; } - gfx_v9_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v9_0_alloc_ip_dump(adev); + return 0; } From c2b218866214bce2a0a3518fc03d976aacfba98e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:55:01 -0400 Subject: [PATCH 010/155] drm/amdgpu/gfx10: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 55342962422b..a998ce77da40 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4988,12 +4988,12 @@ static int gfx_v10_0_sw_init(struct amdgpu_ip_block *ip_block) gfx_v10_0_gpu_early_init(adev); - gfx_v10_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v10_0_alloc_ip_dump(adev); + return 0; } From 601f7a11802ce16207f518b558384bbe16450029 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:55:14 -0400 Subject: [PATCH 011/155] drm/amdgpu/gfx11: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 2ef6bc818aba..0cbbdc3694f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1920,12 +1920,12 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) return -EINVAL; } - gfx_v11_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v11_0_alloc_ip_dump(adev); + adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; From 4ef372319332843c4c99b18d4a943a2122f0ad67 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:55:25 -0400 Subject: [PATCH 012/155] drm/amdgpu/gfx12: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 6fedb6df8db9..b3887c5262a0 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1597,12 +1597,12 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; - gfx_v12_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v12_0_alloc_ip_dump(adev); + adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; From 6cefc59d36667b0ed6bea849e2a77e57a69a3139 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Mon, 8 Jun 2026 15:39:04 -0400 Subject: [PATCH 013/155] drm/amd/display: Enable IPS support for DCN4 Variant [Why] DCN4 variant had IPS completely disabled via ips_support = false and dc->config.disable_ips = DMUB_IPS_DISABLE_ALL. Enabling IPS for D3 allows DMUB to manage idle power savings when the display subsystem is inactive, reducing platform power without affecting active display operation. [How] In DCN4 variant resource construct: - Set dc->caps.ips_support = true - Remove dc->config.disable_ips = DMUB_IPS_DISABLE_ALL override This enables DMUB to dynamically manage IPS entry/exit during D3 power state transitions. Also enable zstate by default. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 330e82b0974b..1bc2452b271f 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -1988,11 +1988,10 @@ static bool dcn42b_resource_construct( dc->caps.dmcub_support = true; dc->caps.is_apu = true; dc->caps.seamless_odm = true; - dc->caps.zstate_support = false; - dc->caps.ips_support = false; + dc->caps.zstate_support = true; + dc->caps.ips_support = true; dc->caps.max_v_total = (1 << 15) - 1; dc->caps.vtotal_limited_by_fp2 = true; - dc->config.disable_ips = DMUB_IPS_DISABLE_ALL; /* Color pipeline capabilities */ dc->caps.color.dpp.dcn_arch = 1; From b7d69145907cdefcbd39a70a31eefd30919af9f1 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Thu, 4 Jun 2026 11:36:09 -0400 Subject: [PATCH 014/155] drm/amd/display: Fix DCN42B null registers & register masks [why] DCN42B is missing some register masks, which are causing errors in dmesg. [how] Make DCN42B reuse the DCN42 register lists, and add the missing defines manually. Fixes: 64142f9d51af ("drm/amd/display: Fix DCN42 null registers & register masks") Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../dc/resource/dcn42b/dcn42b_resource.c | 20 ++- .../dc/resource/dcn42b/dcn42b_resource.h | 116 +----------------- 2 files changed, 20 insertions(+), 116 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 1bc2452b271f..18a7fd9136af 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -22,6 +22,7 @@ #include "dcn35/dcn35_resource.h" #include "dcn321/dcn321_resource.h" #include "dcn401/dcn401_resource.h" +#include "dcn42/dcn42_resource.h" #include "dcn42/dcn42_resource_fpu.h" #include "dcn10/dcn10_ipp.h" @@ -116,6 +117,23 @@ #define regAPG9_APG_DBG_GEN_CONTROL 0x38ae #define regAPG9_APG_DBG_GEN_CONTROL_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG_DB 0x05f8 +#define regHUBP0_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG 0x05f9 +#define regHUBP0_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG_DB 0x06d4 +#define regHUBP1_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG 0x06d5 +#define regHUBP1_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG_DB 0x07b0 +#define regHUBP2_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG 0x07b1 +#define regHUBP2_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG_DB 0x088c +#define regHUBP3_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG 0x088d +#define regHUBP3_HUBPREQ_DEBUG_BASE_IDX 2 + enum dcn401_clk_src_array_id { DCN401_CLK_SRC_PLL0, DCN401_CLK_SRC_PLL1, @@ -461,7 +479,7 @@ static const struct dcn_optc_mask optc_mask = { OPTC_COMMON_MASK_SH_LIST_DCN42B(_MASK)}; #define hubp_regs_init(id) \ - HUBP_REG_LIST_DCN42B_RI(id) + HUBP_REG_LIST_DCN42_RI(id) static struct dcn_hubp2_registers hubp_regs[4]; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 2da3e3c8304a..2824a0e1acc9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -344,7 +344,6 @@ * DCCG_SRII(PHASE, DP_DTO, 3), * DCCG_SRII(MODULO, DP_DTO, 3), * SR(DSCCLK3_DTO_PARAM), - * SR(HDMISTREAMCLK_CNTL), * SR(SYMCLKD_CLOCK_ENABLE), * SR(SYMCLKE_CLOCK_ENABLE) */ @@ -360,6 +359,7 @@ SR(PHYBSYMCLK_CLOCK_CNTL), \ SR(PHYCSYMCLK_CLOCK_CNTL), \ SR(DPSTREAMCLK_CNTL), \ + SR(HDMISTREAMCLK_CNTL), \ SR(SYMCLK32_SE_CNTL), \ SR(SYMCLK32_LE_CNTL), \ DCCG_SRII(PIXEL_RATE_CNTL, OTG, 0), \ @@ -542,120 +542,6 @@ SRI_ARR(DC_ABM1_ACE_OFFSET_SLOPE_DATA, ABM, id), \ SRI_ARR(DC_ABM1_ACE_PWL_CNTL, ABM, id) -/* HUBP */ -/* Not in DCN42B: HUBPREQ_DEBUG_DB and HUBPREQ_DEBUG */ -#define HUBP_REG_LIST_DCN42B_RI(id) \ - SRI_ARR(DCN_DMDATA_VM_CNTL, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_6, HUBPREQ, id), \ - HUBP_REG_LIST_DCN_VM_RI(id), \ - SRI_ARR(PREFETCH_SETTINGS, HUBPREQ, id), \ - SRI_ARR(PREFETCH_SETTINGS_C, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_LOW_ADDR, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_HIGH_ADDR, HUBPREQ, id), \ - SRI_ARR(CURSOR_SETTINGS, HUBPREQ, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS, CURSOR0_, id), \ - SRI_ARR(CURSOR_SIZE, CURSOR0_, id), \ - SRI_ARR(CURSOR_CONTROL, CURSOR0_, id), \ - SRI_ARR(CURSOR_POSITION, CURSOR0_, id), \ - SRI_ARR(CURSOR_HOT_SPOT, CURSOR0_, id), \ - SRI_ARR(CURSOR_DST_OFFSET, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_LOW, CURSOR0_, id), \ - SRI_ARR(DMDATA_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_QOS_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_DATA, CURSOR0_, id), \ - SRI_ARR(DMDATA_STATUS, CURSOR0_, id), \ - SRI_ARR(FLIP_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL2, HUBPREQ, id), \ - SRI_ARR(VMID_SETTINGS_0, HUBPREQ, id), \ - SRI_ARR(DCHUBP_CNTL, HUBP, id), \ - SRI_ARR(DCSURF_ADDR_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_TILING_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_FLIP_INTERRUPT, HUBPREQ, id), \ - SRI_ARR(HUBPRET_CONTROL, HUBPRET, id), \ - SRI_ARR(HUBPRET_READ_LINE_STATUS, HUBPRET, id), \ - SRI_ARR(DCN_EXPANSION_MODE, HUBPREQ, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG_C, HUBP, id), \ - SRI_ARR(BLANK_OFFSET_0, HUBPREQ, id), \ - SRI_ARR(BLANK_OFFSET_1, HUBPREQ, id), \ - SRI_ARR(DST_DIMENSIONS, HUBPREQ, id), \ - SRI_ARR(DST_AFTER_SCALER, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(REF_FREQ_TO_PIX_FREQ, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY_PRE, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_7, HUBPREQ, id), \ - SRI_ARR(DCN_TTU_QOS_WM, HUBPREQ, id), \ - SRI_ARR(DCN_GLOBAL_TTU_CNTL, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(HUBP_CLK_CNTL, HUBP, id), \ - SRI_ARR(HUBPRET_READ_LINE_VALUE, HUBPRET, id), \ - SRI_ARR(DCHUBP_MALL_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_VMPG_CONFIG, HUBP, id), \ - SRI_ARR(UCLK_PSTATE_FORCE, HUBPREQ, id), \ - SRI_ARR(HUBP_3DLUT_DLG_PARAM, CURSOR0_, id), \ - HUBP_3DLUT_FL_REG_LIST_DCN401(id) struct dcn42b_resource_pool { struct resource_pool base; }; From 219fb1f4c5b91a58b2db55ff2fe2d06412e39888 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Wed, 10 Jun 2026 11:42:50 -0400 Subject: [PATCH 015/155] drm/amd/display: Enable zstate support and fix seamless boot [Why & How] Port seamless boot fix from DCN42 to DCN42B and enable zstate support for DCN42B. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 18a7fd9136af..172836dfd679 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -253,10 +253,10 @@ static struct bios_registers bios_regs; static struct dce110_clk_src_regs clk_src_regs[5]; static const struct dce110_clk_src_shift cs_shift = { - CS_COMMON_MASK_SH_LIST_DCN3_2(__SHIFT) + CS_COMMON_MASK_SH_LIST_DCN4_0_1(__SHIFT) }; static const struct dce110_clk_src_mask cs_mask = { - CS_COMMON_MASK_SH_LIST_DCN3_2(_MASK) + CS_COMMON_MASK_SH_LIST_DCN4_0_1(_MASK) }; #define abm_regs_init(id) \ ABM_DCN42B_REG_LIST_RI(id) @@ -801,7 +801,7 @@ static const struct dc_debug_options debug_defaults_drv = { } }, .seamless_boot_odm_combine = DML_FAIL_SOURCE_PIXEL_FORMAT, - .enable_z9_disable_interface = false, /* Allow support for the PMFW interface for disable Z9*/ + .enable_z9_disable_interface = true, /* Allow support for the PMFW interface for disable Z9*/ .minimum_z8_residency_time = 1, /* Always allow when other conditions are met */ .support_eDP1_5 = true, .use_max_lb = true, @@ -823,7 +823,7 @@ static const struct dc_debug_options debug_defaults_drv = { .disable_timeout = true, .min_disp_clk_khz = 50000, .static_screen_wait_frames = 2, - .disable_z10 = true, + .disable_z10 = false, .ignore_pg = true, .disable_stutter_for_wm_program = true, .min_deep_sleep_dcfclk_khz = 8000, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 2824a0e1acc9..1a6e0e7be0fa 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -518,7 +518,8 @@ SRII_ARR_2(MODULO, DP_DTO, 2, index), \ SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 0, index), \ SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 1, index), \ - SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 2, index) + SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 2, index), \ + SR_ARR(OTG_PIXEL_RATE_DIV, index) /* ABM */ #define ABM_DCN42B_REG_LIST_RI(id) \ From ffa0cfa9d616aeea2adf182bda8e4dabc95f4531 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 24 Jun 2026 16:34:03 -0400 Subject: [PATCH 016/155] drm/amd/display: Add passive dongle handling in force_to_use_aux case [why] Need special handling for passive HDMI dongles with I2C over AUX. Reviewed-by: Aurabindo Pillai Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/link/link_detection.c | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 281a7c5acaca..2abe9d3a68d5 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -497,6 +497,12 @@ static void query_hdcp_capability(enum signal_type signal, struct dc_link *link) msg22.data = link->hdcp_caps.rx_caps.raw; msg22.length = sizeof(link->hdcp_caps.rx_caps.raw); msg22.msg_id = HDCP_MESSAGE_ID_RX_CAPS; + if (link->force_to_use_aux && (signal == SIGNAL_TYPE_HDMI_TYPE_A)) { + // case with passive dongle with i2c over aux + msg22.data = &link->hdcp_caps.rx_caps.fields.version; + msg22.length = sizeof(link->hdcp_caps.rx_caps.fields.version); + msg22.msg_id = HDCP_MESSAGE_ID_HDCP2VERSION; + } } else { msg22.data = &link->hdcp_caps.rx_caps.fields.version; msg22.length = sizeof(link->hdcp_caps.rx_caps.fields.version); @@ -597,7 +603,24 @@ static bool detect_dp(struct dc_link *link, if (sink_caps->transaction_type == DDC_TRANSACTION_TYPE_I2C_OVER_AUX) { sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT; if (!detect_dp_sink_caps(link)) { - return false; + if (link->force_to_use_aux) { + sink_caps->signal = dp_passive_dongle_detection(link->ddc, sink_caps, audio_support); + link->dpcd_caps.dongle_type = sink_caps->dongle_type; + link->dpcd_caps.is_dongle_type_one = sink_caps->is_dongle_type_one; + link->dpcd_caps.dpcd_rev.raw = 0; + /* Type 1 dongles do not work with I2C over Aux and also some of + * Type 2 dongles do not support I2C over Aux well, so for these + * cases when native Aux transactons fails and I2C over Aux fails + * report that nothing is connected, as we can't tell is it bad + * DP sink or bad passive dongle. + */ + if (sink_caps->dongle_type == DISPLAY_DONGLE_NONE) + return false; + else + return true; + } else { + return false; + } } if (is_dp_branch_device(link)) From c96d79332efa760fd18d9be90b9162fb6acfac89 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Mon, 22 Jun 2026 14:58:52 +0530 Subject: [PATCH 017/155] drm/amd/display: Check dc_link before applying DSC policy apply_dsc_policy_for_stream() decides whether Display Stream Compression (DSC) should be enabled for a display stream based on display capabilities, link bandwidth and user overrides. Smatch reports that apply_dsc_policy_for_stream() dereferences aconnector->dc_link before checking whether it is NULL. Add an early NULL check for aconnector->dc_link before the first dereference and remove the later redundant NULL check in the HDMI_FRL path. Fixes: 0e967e086e75 ("drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector") Reported-by: Dan Carpenter Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Cc: Aurabindo Pillai Reviewed-by: George Zhang Signed-off-by: Srinivasan Shanmugam Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 57be01c60f7d..ab14f811a85d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1258,6 +1258,9 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, drm_connector->display_info.max_dsc_bpp; struct dc_dsc_config_options dsc_options = {0}; + if (!aconnector->dc_link) + return; + dc_dsc_get_default_config_option(dc, &dsc_options); dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; @@ -1319,7 +1322,7 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); } } - } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { struct dc_dsc_policy dsc_policy = {0}; frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); From 2a5810782369b2128394e783d30728393c7bd62d Mon Sep 17 00:00:00 2001 From: James Lin Date: Mon, 22 Jun 2026 13:38:02 +0800 Subject: [PATCH 018/155] drm/amd/display: program dither on all OPP heads under ODM combine [why] The IGT test kms_plane_alpha_blend@coverage-vs-premult-vs-constant failed on DCN with ODM 2:1 combine enabled. The test compares the CRC of a coverage-blended plane against a premultiplied-blended plane and expects them to be identical. With ODM combine the whole-frame CRC mismatched (left segment matched, right segment differed). Root cause: dc_stream_set_dither_option() iterated the pipe_ctx array and broke out at the first pipe matching the stream (the OTG master / OPP instance 0), then programmed bit-depth reduction (dither) on that single OPP only. Under ODM combine there is more than one OPP head, so the secondary ODM segment (OPP instance 1) was never reprogrammed. When CRC capture requested dither off, only the left OPP got updated while the right OPP kept its previous dither setting, producing a different CRC for the right segment and thus a whole-frame CRC mismatch. [how] Use resource_get_otg_master_for_stream() and resource_get_opp_heads_for_otg_master() to retrieve every OPP head of the stream, then loop over all of them and call transform_set_pixel_storage_depth() and opp_program_bit_depth_reduction() on each. This keeps all OPP heads in sync under ODM combine, so dither is applied (or cleared) identically across every segment and the CRCs match. Reviewed-by: Wayne Lin Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 53 +++++++++++++++--------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 28339e4b6d67..ec5d6ddeefbe 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -769,22 +769,23 @@ void dc_stream_set_dither_option(struct dc_stream_state *stream, { struct bit_depth_reduction_params params; struct dc_link *link = stream->link; - struct pipe_ctx *pipes = NULL; + struct resource_context *res_ctx = &link->dc->current_state->res_ctx; + struct pipe_ctx *otg_master; + struct pipe_ctx *opp_heads[MAX_PIPES]; + int opp_cnt; int i; - for (i = 0; i < MAX_PIPES; i++) { - if (link->dc->current_state->res_ctx.pipe_ctx[i].stream == - stream) { - pipes = &link->dc->current_state->res_ctx.pipe_ctx[i]; - break; - } - } - - if (!pipes) + otg_master = resource_get_otg_master_for_stream(res_ctx, stream); + if (!otg_master) return; if (option > DITHER_OPTION_MAX) return; + opp_cnt = resource_get_opp_heads_for_otg_master(otg_master, res_ctx, opp_heads); + + if (opp_cnt == 0) + return; + dc_exit_ips_for_hw_access(stream->ctx->dc); stream->dither_option = option; @@ -793,16 +794,30 @@ void dc_stream_set_dither_option(struct dc_stream_state *stream, resource_build_bit_depth_reduction_params(stream, ¶ms); stream->bit_depth_params = params; - if (pipes->plane_res.xfm && - pipes->plane_res.xfm->funcs->transform_set_pixel_storage_depth) { - pipes->plane_res.xfm->funcs->transform_set_pixel_storage_depth( - pipes->plane_res.xfm, - pipes->plane_res.scl_data.lb_params.depth, - &stream->bit_depth_params); - } + /* + * Program bit-depth reduction (dither) on every OPP head of the + * stream. Under ODM combine there is more than one OPP head and they + * must all be kept in sync, otherwise (e.g. when CRC capture requests + * dither off) a secondary ODM segment can keep dither enabled and + * produce a different CRC than the primary segment. + */ + for (i = 0; i < opp_cnt; i++) { + struct pipe_ctx *opp_head = opp_heads[i]; - pipes->stream_res.opp->funcs-> - opp_program_bit_depth_reduction(pipes->stream_res.opp, ¶ms); + if (opp_head->plane_res.xfm && + opp_head->plane_res.xfm->funcs->transform_set_pixel_storage_depth) { + opp_head->plane_res.xfm->funcs->transform_set_pixel_storage_depth( + opp_head->plane_res.xfm, + opp_head->plane_res.scl_data.lb_params.depth, + &stream->bit_depth_params); + } + + if (opp_head->stream_res.opp && + opp_head->stream_res.opp->funcs->opp_program_bit_depth_reduction) { + opp_head->stream_res.opp->funcs->opp_program_bit_depth_reduction( + opp_head->stream_res.opp, ¶ms); + } + } } bool dc_stream_set_gamut_remap(struct dc *dc, const struct dc_stream_state *stream) From 8fa813b7fd1eccbed13e126166cf764f1f11a7d3 Mon Sep 17 00:00:00 2001 From: Dominik Kaszewski Date: Fri, 19 Jun 2026 11:22:07 +0200 Subject: [PATCH 019/155] drm/amd/display: Remove sink usage from DPMS [Why] stream->sink is optional and can be null, so should always be checked before dereference. Additionally, most of its usage in DPMS sequences is for stream->sink->link, which can be replaced with stream->link, as the two should always be the same. [How] * Replace stream->sink->link in DPMS on/off * Add assert to USB4 BW allocation where sink is required * Avoid inconsistencies in resource access, e.g. don't repeat stream->link after it was already saved to a local variable * Pull out effective VPG calculation to helper getter * Formatting fixes Reviewed-by: Nicholas Kazlauskas Signed-off-by: Dominik Kaszewski Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/link/link_dpms.c | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 42e7f0e9b3ce..479a8fbfdc5d 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -2296,6 +2296,8 @@ static enum dc_status enable_link( static bool allocate_usb4_bandwidth_for_stream(struct dc_stream_state *stream, int stream_bw) { + ASSERT(stream->sink); + struct dc_link *link = stream->sink->link; int req_bw = stream_bw; @@ -2342,6 +2344,8 @@ static bool allocate_usb4_bandwidth_for_stream(struct dc_stream_state *stream, i static bool allocate_usb4_bandwidth(struct dc_stream_state *stream) { + ASSERT(stream->sink); + bool ret; int bw = dc_bandwidth_in_kbps_from_timing(&stream->timing, @@ -2361,38 +2365,43 @@ static bool deallocate_usb4_bandwidth(struct dc_stream_state *stream) return ret; } +static struct vpg *get_vpg(struct pipe_ctx *pipe_ctx) +{ + if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + return pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; + else if (dp_is_128b_132b_signal(pipe_ctx)) + return pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; + else + return pipe_ctx->stream_res.stream_enc->vpg; +} + void link_set_dpms_off(struct pipe_ctx *pipe_ctx) { - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; - struct vpg *vpg = pipe_ctx->stream_res.stream_enc->vpg; - enum dp_panel_mode panel_mode_dp = dp_get_panel_mode(link); - DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->link; + struct vpg *vpg = get_vpg(pipe_ctx); + enum dp_panel_mode panel_mode_dp = dp_get_panel_mode(link); ASSERT(is_master_pipe_for_link(link, pipe_ctx)); - if (dp_is_128b_132b_signal(pipe_ctx)) - vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; - if (dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (dc_is_virtual_signal(stream->signal)) return; - if (pipe_ctx->stream->sink) { - if (pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && - pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_NONE) { + if (stream->sink) { + if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && + stream->sink->sink_signal != SIGNAL_TYPE_NONE) { DC_LOG_DC("%s pipe_ctx dispname=%s signal=%x link=%d sink_count=%d\n", __func__, - pipe_ctx->stream->sink->edid_caps.display_name, - pipe_ctx->stream->signal, link->link_index, link->sink_count); + stream->sink->edid_caps.display_name, + stream->signal, link->link_index, link->sink_count); } } link_wait_for_unlocked(link); - if (!pipe_ctx->stream->sink->edid_caps.panel_patch.skip_avmute) { - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) + if (stream->sink && !stream->sink->edid_caps.panel_patch.skip_avmute) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, true); } @@ -2402,15 +2411,15 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) dc->hwss.blank_stream(pipe_ctx); if (pipe_ctx->link_config.dp_tunnel_settings.should_use_dp_bw_allocation) - deallocate_usb4_bandwidth(pipe_ctx->stream); + deallocate_usb4_bandwidth(stream); - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) deallocate_mst_payload(pipe_ctx); - else if (dc_is_dp_sst_signal(pipe_ctx->stream->signal) && + else if (dc_is_dp_sst_signal(stream->signal) && dp_is_128b_132b_signal(pipe_ctx)) update_sst_payload(pipe_ctx, false); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) { struct ext_hdmi_settings settings = {0}; enum engine_id eng_id = pipe_ctx->stream_res.stream_enc->id; @@ -2433,7 +2442,7 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) } } - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT && + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT && !dp_is_128b_132b_signal(pipe_ctx)) { /* In DP1.x SST mode, our encoder will go to TPS1 @@ -2443,18 +2452,18 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) * state machine. * In DP2 or MST mode, our encoder will stay video active */ - disable_link(pipe_ctx->stream->link, &pipe_ctx->link_res, pipe_ctx->stream->signal); + disable_link(link, &pipe_ctx->link_res, stream->signal); dc->hwss.disable_stream(pipe_ctx); } else { dc->hwss.disable_stream(pipe_ctx); - disable_link(pipe_ctx->stream->link, &pipe_ctx->link_res, pipe_ctx->stream->signal); + disable_link(link, &pipe_ctx->link_res, stream->signal); } edp_set_panel_assr(link, pipe_ctx, &panel_mode_dp, false); - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, false); - else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + else if (dc_is_hdmi_frl_signal(stream->signal)) link_set_dsc_on_stream(pipe_ctx, false); } if (dp_is_128b_132b_signal(pipe_ctx)) { @@ -2468,7 +2477,7 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) /* for psp not exist case */ if (link->connector_signal == SIGNAL_TYPE_EDP && dc->debug.psp_disabled_wa) { /* reset internal save state to default since eDP is off */ - enum dp_panel_mode panel_mode = dp_get_panel_mode(pipe_ctx->stream->link); + enum dp_panel_mode panel_mode = dp_get_panel_mode(link); /* since current psp not loaded, we need to reset it to default */ link->panel_mode = panel_mode; } @@ -2478,62 +2487,57 @@ void link_set_dpms_on( struct dc_state *state, struct pipe_ctx *pipe_ctx) { - struct dc *dc = pipe_ctx->stream->ctx->dc; + DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->link; enum dc_status status; struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc; enum otg_out_mux_dest otg_out_dest = OUT_MUX_DIO; - struct vpg *vpg = pipe_ctx->stream_res.stream_enc->vpg; + struct vpg *vpg = get_vpg(pipe_ctx); const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res); bool apply_edp_fast_boot_optimization = - pipe_ctx->stream->apply_edp_fast_boot_optimization; - - DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + stream->apply_edp_fast_boot_optimization; ASSERT(is_master_pipe_for_link(link, pipe_ctx)); - if (dp_is_128b_132b_signal(pipe_ctx)) - vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; - if (dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (dc_is_virtual_signal(stream->signal)) return; - if (pipe_ctx->stream->sink) { - if (pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && - pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_NONE) { + if (stream->sink) { + if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && + stream->sink->sink_signal != SIGNAL_TYPE_NONE) { DC_LOG_DC("%s pipe_ctx dispname=%s signal=%x link=%d sink_count=%d\n", __func__, - pipe_ctx->stream->sink->edid_caps.display_name, - pipe_ctx->stream->signal, + stream->sink->edid_caps.display_name, + stream->signal, link->link_index, link->sink_count); } } - link_wait_for_unlocked(stream->link); + link_wait_for_unlocked(link); if (!dc->config.unify_link_enc_assignment) link_enc = link_enc_cfg_get_link_enc(link); ASSERT(link_enc); - if (!dc_is_virtual_signal(pipe_ctx->stream->signal) - && !dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) + if (!dc_is_virtual_signal(stream->signal) + && !dc_is_hdmi_frl_signal(stream->signal) && !dp_is_128b_132b_signal(pipe_ctx)) { if (link_enc) link_enc->funcs->setup( link_enc, - pipe_ctx->stream->signal); + stream->signal); } - pipe_ctx->stream->link->link_state_valid = true; + link->link_state_valid = true; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - hdmi_frl_decide_link_settings(stream, &stream->link->frl_link_settings, &pipe_ctx->dsc_padding_params); + if (dc_is_hdmi_frl_signal(stream->signal)) + hdmi_frl_decide_link_settings(stream, &link->frl_link_settings, &pipe_ctx->dsc_padding_params); if (pipe_ctx->stream_res.tg->funcs->set_out_mux) { if (dp_is_128b_132b_signal(pipe_ctx)) otg_out_dest = OUT_MUX_HPO_DP; - else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + else if (dc_is_hdmi_frl_signal(stream->signal)) otg_out_dest = OUT_MUX_HPO_FRL; else otg_out_dest = OUT_MUX_DIO; @@ -2542,7 +2546,7 @@ void link_set_dpms_on( link_hwss->setup_stream_attribute(pipe_ctx); - pipe_ctx->stream->apply_edp_fast_boot_optimization = false; + stream->apply_edp_fast_boot_optimization = false; // Enable VPG before building infoframe if (vpg && vpg->funcs->vpg_poweron) @@ -2551,15 +2555,15 @@ void link_set_dpms_on( resource_build_info_frame(pipe_ctx); dc->hwss.update_info_frame(pipe_ctx); - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) dp_trace_source_sequence(link, DPCD_SOURCE_SEQ_AFTER_UPDATE_INFO_FRAME); /* Do not touch link on seamless boot optimization. */ - if (pipe_ctx->stream->apply_seamless_boot_optimization) { - pipe_ctx->stream->dpms_off = false; + if (stream->apply_seamless_boot_optimization) { + stream->dpms_off = false; /* Still enable stream features & audio on seamless boot for DP external displays */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT) { + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT) { enable_stream_features(pipe_ctx); dc->hwss.enable_audio_stream(pipe_ctx); } @@ -2569,11 +2573,11 @@ void link_set_dpms_on( } /* eDP lit up by bios already, no need to enable again. */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_EDP && + if (stream->signal == SIGNAL_TYPE_EDP && apply_edp_fast_boot_optimization && - !pipe_ctx->stream->timing.flags.DSC && + !stream->timing.flags.DSC && !pipe_ctx->next_odm_pipe) { - pipe_ctx->stream->dpms_off = false; + stream->dpms_off = false; update_psp_stream_config(pipe_ctx, false); if (link->is_dds) { @@ -2586,7 +2590,7 @@ void link_set_dpms_on( return; } - if (pipe_ctx->stream->dpms_off) + if (stream->dpms_off) return; /* For Dp tunneling link, a pending HPD means that we have a race condition between processing @@ -2604,9 +2608,9 @@ void link_set_dpms_on( * will be automatically set at a later time when the video is enabled * (DP_VID_STREAM_EN = 1). */ - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal) || - dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal) || + dc_is_virtual_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, true); } @@ -2617,7 +2621,7 @@ void link_set_dpms_on( if (status != DC_OK) { DC_LOG_WARNING("enabling link %u failed: %d\n", - pipe_ctx->stream->link->link_index, + link->link_index, status); /* Abort stream enable *unless* the failure was due to @@ -2627,19 +2631,18 @@ void link_set_dpms_on( */ if ((status != DC_FAIL_DP_LINK_TRAINING && status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || - pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { - if (false == stream->link->link_status.link_active) - disable_link(stream->link, &pipe_ctx->link_res, - pipe_ctx->stream->signal); + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { + if (false == link->link_status.link_active) + disable_link(link, &pipe_ctx->link_res, + stream->signal); BREAK_TO_DEBUGGER(); return; } } - if (pipe_ctx->stream->timing.flags.DSC && - dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - //TODO: bring HDMI FRL in line with DP - link_set_dsc_on_stream(pipe_ctx, true); + if (stream->timing.flags.DSC && dc_is_hdmi_frl_signal(stream->signal)) + //TODO: bring HDMI FRL in line with DP + link_set_dsc_on_stream(pipe_ctx, true); /* turn off otg test pattern if enable */ if (pipe_ctx->stream_res.tg->funcs->set_test_pattern) @@ -2651,37 +2654,37 @@ void link_set_dpms_on( * as a workaround for the incorrect value being applied * from transmitter control. */ - if (!(dc_is_virtual_signal(pipe_ctx->stream->signal) || - dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) || + if (!(dc_is_virtual_signal(stream->signal) || + dc_is_hdmi_frl_signal(stream->signal) || dp_is_128b_132b_signal(pipe_ctx))) { if (link_enc) link_enc->funcs->setup( link_enc, - pipe_ctx->stream->signal); + stream->signal); } dc->hwss.enable_stream(pipe_ctx); /* Set DPS PPS SDP (AKA "info frames") */ - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal) || - dc_is_virtual_signal(pipe_ctx->stream->signal)) { + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal) || + dc_is_virtual_signal(stream->signal)) { dp_set_dsc_on_rx(pipe_ctx, true); link_set_dsc_pps_packet(pipe_ctx, true, true); } } - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) dp_set_hblank_reduction_on_rx(pipe_ctx); if (pipe_ctx->link_config.dp_tunnel_settings.should_use_dp_bw_allocation) - allocate_usb4_bandwidth(pipe_ctx->stream); + allocate_usb4_bandwidth(stream); - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) allocate_mst_payload(pipe_ctx); - else if (dc_is_dp_sst_signal(pipe_ctx->stream->signal) && + else if (dc_is_dp_sst_signal(stream->signal) && dp_is_128b_132b_signal(pipe_ctx)) update_sst_payload(pipe_ctx, true); @@ -2690,23 +2693,22 @@ void link_set_dpms_on( * training and stream unblank resolves the corruption issue. * This is workaround. */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_EDP && + if (stream->signal == SIGNAL_TYPE_EDP && link->is_display_mux_present) msleep(20); dc->hwss.unblank_stream(pipe_ctx, - &pipe_ctx->stream->link->cur_link_settings); + &link->cur_link_settings); if (stream->sink_patches.delay_ignore_msa > 0) msleep(stream->sink_patches.delay_ignore_msa); - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) enable_stream_features(pipe_ctx); update_psp_stream_config(pipe_ctx, false); dc->hwss.enable_audio_stream(pipe_ctx); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, false); - } } From 560cb7437c877329e74f2ae660f1b84992c54ea5 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Mon, 22 Jun 2026 16:25:09 +0800 Subject: [PATCH 020/155] drm/amd/display: Fix dead-code panel type detection from DID [Why] In amdgpu_dm_set_panel_type() the DID fallback branch tested and wrote link->panel_type, while the rest of the function tracked the result in the local variable panel_type. The final assignment unconditionally overwrites link->panel_type from panel_type, so the value derived from DID was always discarded, making the DID branch dead code. [How] Use the local panel_type variable in the DID fallback branch so that the DID result participates in the source priority (VSDB -> DPCD -> DID -> vendor luminance heuristic -> LCD default) and is preserved by the final assignment. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Chenyu Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index ab14f811a85d..bbcd65e1a7c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -420,11 +420,11 @@ static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) } /* If VSDB and DPCD didn't determine panel type, check DID */ - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { if (display_info->panel_type == DRM_MODE_PANEL_TYPE_LCD) - link->panel_type = PANEL_TYPE_LCD; + panel_type = PANEL_TYPE_LCD; else if (display_info->panel_type == DRM_MODE_PANEL_TYPE_OLED) - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; } if (panel_type == PANEL_TYPE_NONE) { From 9dfd0da5e3caecee5dac3954997ae5acc8ad737e Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Mon, 22 Jun 2026 16:31:53 +0800 Subject: [PATCH 021/155] drm/amd/display: Add KUnit tests for CACP on Linux [Why] The CACP enablement on Linux added panel-type detection and CACP capability logic that was not covered by KUnit tests. [How] Export amdgpu_dm_set_panel_type() and amdgpu_dm_update_cacp_caps() for KUnit via STATIC_IFN_KUNIT/EXPORT_IF_KUNIT and add unit tests covering: - amdgpu_dm_update_cacp_caps(): IP version gating (including the 3.1.6 exclusion), eDP/LVDS signal handling, non-eDP signals, and OLED vs LCD panel types. - amdgpu_dm_set_panel_type(): VSDB OLED/MINILED, DPCD oled/miniled bits, the DID path (OLED and LCD), the vendor luminance heuristic, and the LCD default. Update the should_create_sysfs backlight tests to reflect the new OLED/CACP behavior (OLED with/without CACP and LCD eDP panels). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Chenyu Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 6 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 2 + .../tests/amdgpu_dm_backlight_test.c | 43 ++- .../tests/amdgpu_dm_connector_test.c | 351 ++++++++++++++++++ 4 files changed, 392 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index bbcd65e1a7c6..f77af83ade39 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -392,7 +392,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } -static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_display_info *display_info = &connector->display_info; @@ -459,8 +459,9 @@ static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } +EXPORT_IF_KUNIT(amdgpu_dm_set_panel_type); -static void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) { struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); struct dc_link *link = aconnector->dc_link; @@ -485,6 +486,7 @@ static void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "cacp_supported: %d\n", link->panel_config.cacp.cacp_supported); } +EXPORT_IF_KUNIT(amdgpu_dm_update_cacp_caps); DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index c5b8b13f8f06..23843ac99642 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -158,5 +158,7 @@ enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in); void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled); +void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector); #endif #endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 53df4b668f2e..adb896022a27 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -1034,10 +1034,13 @@ static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) } /** - * dm_test_should_create_sysfs_aux_backlight - Test AUX backlight disables sysfs + * dm_test_should_create_sysfs_oled_no_cacp - Test OLED without CACP disables sysfs * @test: The KUnit test context + * + * A non-LCD panel that does not support CACP must not expose the sysfs + * backlight interface. */ -static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) +static void dm_test_should_create_sysfs_oled_no_cacp(struct kunit *test) { struct dm_backlight_connector_fixture fixture = {}; int saved_abm_level = amdgpu_dm_get_abm_level_param(); @@ -1045,7 +1048,8 @@ static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; - fixture.adev->dm.backlight_caps[0].aux_support = true; + fixture.link->panel_type = PANEL_TYPE_OLED; + fixture.link->panel_config.cacp.cacp_supported = false; KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1053,10 +1057,33 @@ static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) } /** - * dm_test_should_create_sysfs_pwm_backlight - Test PWM backlight enables sysfs + * dm_test_should_create_sysfs_oled_cacp - Test OLED with CACP enables sysfs + * @test: The KUnit test context + * + * An OLED panel that supports CACP must expose the sysfs backlight + * interface so the ABM/CACP level can be controlled. + */ +static void dm_test_should_create_sysfs_oled_cacp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_OLED; + fixture.link->panel_config.cacp.cacp_supported = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_lcd_panel - Test LCD eDP panel enables sysfs * @test: The KUnit test context */ -static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) +static void dm_test_should_create_sysfs_lcd_panel(struct kunit *test) { struct dm_backlight_connector_fixture fixture = {}; int saved_abm_level = amdgpu_dm_get_abm_level_param(); @@ -1065,7 +1092,6 @@ static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; fixture.link->panel_type = PANEL_TYPE_LCD; - fixture.adev->dm.backlight_caps[0].aux_support = false; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1225,8 +1251,9 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_should_create_sysfs_abm_forced), KUNIT_CASE(dm_test_should_create_sysfs_non_edp), KUNIT_CASE(dm_test_should_create_sysfs_no_backlight_index), - KUNIT_CASE(dm_test_should_create_sysfs_aux_backlight), - KUNIT_CASE(dm_test_should_create_sysfs_pwm_backlight), + KUNIT_CASE(dm_test_should_create_sysfs_oled_no_cacp), + KUNIT_CASE(dm_test_should_create_sysfs_oled_cacp), + KUNIT_CASE(dm_test_should_create_sysfs_lcd_panel), /* amdgpu_dm_setup_backlight_device */ KUNIT_CASE(dm_test_setup_backlight_device_non_edp), KUNIT_CASE(dm_test_setup_backlight_device_connection_none), diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index aa451064b30c..45210c64a000 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -2015,6 +2015,341 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); } +/* Tests for amdgpu_dm_update_cacp_caps() */ + +struct dm_cacp_fixture { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static void setup_cacp_fixture(struct kunit *test, + struct dm_cacp_fixture *fixture, + enum signal_type signal, + enum dc_panel_type panel_type) +{ + fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL); + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), + GFP_KERNEL); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + + fixture->aconnector->dc_link = fixture->link; + fixture->aconnector->base.dev = &fixture->adev->ddev; + fixture->link->connector_signal = signal; + fixture->link->panel_type = panel_type; +} + +/** + * dm_test_cacp_caps_unsupported_ip - Test CACP disabled on old DCE IP + * @test: The KUnit test context + * + * A DCE IP version below 3.1.4 does not support CACP, so cacp_supported + * must remain false regardless of signal or panel type. + */ +static void dm_test_cacp_caps_unsupported_ip(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 2); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_excluded_ip_316 - Test CACP disabled on DCE IP 3.1.6 + * @test: The KUnit test context + * + * DCE IP version 3.1.6 is explicitly excluded from CACP support. + */ +static void dm_test_cacp_caps_excluded_ip_316(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 6); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_edp_oled_supported - Test CACP enabled on eDP OLED + * @test: The KUnit test context + * + * A supported DCE IP version on an eDP OLED panel must enable CACP. + */ +static void dm_test_cacp_caps_edp_oled_supported(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lvds_oled_supported - Test CACP enabled on LVDS OLED + * @test: The KUnit test context + * + * LVDS is an accepted connector signal for CACP support. + */ +static void dm_test_cacp_caps_lvds_oled_supported(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_LVDS, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_non_edp_signal - Test CACP disabled on non-eDP/LVDS signal + * @test: The KUnit test context + * + * External DisplayPort is neither eDP nor LVDS, so CACP must be disabled. + */ +static void dm_test_cacp_caps_non_edp_signal(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_DISPLAY_PORT, + PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lcd_panel - Test CACP disabled on LCD panel + * @test: The KUnit test context + * + * Plain LCD panels do not benefit from CACP, so it must be disabled even + * on a supported IP version and eDP signal. + */ +static void dm_test_cacp_caps_lcd_panel(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_LCD); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/* Tests for amdgpu_dm_set_panel_type() */ + +struct dm_panel_type_fixture { + struct amdgpu_device *adev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct dc_sink *sink; +}; + +static void setup_panel_type_fixture(struct kunit *test, + struct dm_panel_type_fixture *fixture) +{ + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + fixture->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*fixture->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->drm); + fixture->adev = drm_to_adev(fixture->drm); + + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + fixture->sink = kunit_kzalloc(test, sizeof(*fixture->sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->sink); + + fixture->aconnector->dc_link = fixture->link; + drmm_connector_init(fixture->drm, &fixture->aconnector->base, + &dm_test_connector_funcs, DRM_MODE_CONNECTOR_eDP, + NULL); +} + +/** + * dm_test_set_panel_type_vsdb_oled - Test VSDB OLED maps to PANEL_TYPE_OLED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_vsdb_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.amd_vsdb.panel_type = + AMD_VSDB_PANEL_TYPE_OLED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_vsdb_miniled - Test VSDB MINILED maps to PANEL_TYPE_MINILED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_vsdb_miniled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.amd_vsdb.panel_type = + AMD_VSDB_PANEL_TYPE_MINILED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_dpcd_oled - Test DPCD oled bit maps to PANEL_TYPE_OLED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_dpcd_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_dpcd_miniled - Test DPCD miniled bit maps to PANEL_TYPE_MINILED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_dpcd_miniled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.link->dpcd_sink_ext_caps.bits.miniled = 1; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_did_oled - Test DID OLED maps to PANEL_TYPE_OLED + * @test: The KUnit test context + * + * When VSDB and DPCD do not identify the panel, a DID panel type of + * DRM_MODE_PANEL_TYPE_OLED must map to PANEL_TYPE_OLED. + */ +static void dm_test_set_panel_type_did_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.panel_type = + DRM_MODE_PANEL_TYPE_OLED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_did_lcd - Test DID LCD maps to PANEL_TYPE_LCD + * @test: The KUnit test context + * + * When VSDB and DPCD do not identify the panel, a DID panel type of + * DRM_MODE_PANEL_TYPE_LCD must map to PANEL_TYPE_LCD. + */ +static void dm_test_set_panel_type_did_lcd(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.panel_type = + DRM_MODE_PANEL_TYPE_LCD; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_LCD); +} + +/** + * dm_test_set_panel_type_vendor_lum_heuristic - Test vendor luminance heuristic maps to MINILED + * @test: The KUnit test context + * + * A panel from the specific vendor whose first luminance range is at least + * 1.5x the second is treated as a mini-LED panel. + */ +static void dm_test_set_panel_type_vendor_lum_heuristic(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + struct drm_amd_vsdb_info *vsdb; + + setup_panel_type_fixture(test, &fixture); + fixture.link->local_sink = fixture.sink; + fixture.sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + + vsdb = &fixture.aconnector->base.display_info.amd_vsdb; + vsdb->version = 1; + vsdb->luminance_range1.max_luminance = 3000; + vsdb->luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_defaults_to_lcd - Test undetermined panel defaults to LCD + * @test: The KUnit test context + * + * When no source identifies the panel, the type now defaults to + * PANEL_TYPE_LCD instead of remaining PANEL_TYPE_NONE. + */ +static void dm_test_set_panel_type_defaults_to_lcd(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_LCD); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2143,6 +2478,22 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode), KUNIT_CASE(dm_test_is_freesync_video_mode_match), KUNIT_CASE(dm_test_is_freesync_video_mode_no_match), + /* amdgpu_dm_update_cacp_caps */ + KUNIT_CASE(dm_test_cacp_caps_unsupported_ip), + KUNIT_CASE(dm_test_cacp_caps_excluded_ip_316), + KUNIT_CASE(dm_test_cacp_caps_edp_oled_supported), + KUNIT_CASE(dm_test_cacp_caps_lvds_oled_supported), + KUNIT_CASE(dm_test_cacp_caps_non_edp_signal), + KUNIT_CASE(dm_test_cacp_caps_lcd_panel), + /* amdgpu_dm_set_panel_type */ + KUNIT_CASE(dm_test_set_panel_type_vsdb_oled), + KUNIT_CASE(dm_test_set_panel_type_vsdb_miniled), + KUNIT_CASE(dm_test_set_panel_type_dpcd_oled), + KUNIT_CASE(dm_test_set_panel_type_dpcd_miniled), + KUNIT_CASE(dm_test_set_panel_type_did_oled), + KUNIT_CASE(dm_test_set_panel_type_did_lcd), + KUNIT_CASE(dm_test_set_panel_type_vendor_lum_heuristic), + KUNIT_CASE(dm_test_set_panel_type_defaults_to_lcd), {} }; From 78d36c5b53e5c7c3cb7ffb31e0ff6251fd1dcacb Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Tue, 9 Jun 2026 19:01:13 -0400 Subject: [PATCH 022/155] drm/amd/display: Add Debug Option To Enable Per-DPM De-rate Usage [Why] DML has been updated to use per-DPM derates when provided but per-DPM de-rates have not been finalized. Need to validate to see what values should be stored in the bounding box. [How] Add debug options to set custom derates per DPM (starting at DPM0) and their values Each entry in the custom derate expects the derates to be stored in the following format: bits 0-7: dram_derate_percent_pixel bits 8-15: fclk_derate_percent bits 16-23: dcfclk_derate_percent bits 24-31 are unused. e.g. Using the value 0x414020 will set the following derates for DPM0 DPM0: 0x20, 0x40, 0x41 for dram, fclk, and dcfclk respectively Note that global derate value will be used if the per-DPM derate is 0. Reviewed-by: Dillon Varone Signed-off-by: Austin Zheng Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 ++ .../dcn401/dcn401_soc_and_ip_translator.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 2b59a0e9ca38..c78fdfc0ef4b 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1289,6 +1289,8 @@ struct dc_debug_options { bool enable_replay_esd_recovery; uint8_t iommu_mismatch_temp_wka; bool disable_dynamic_expansion_for_test_pattern; + uint32_t dml21_custom_derate_num_dpms; + uint32_t dml21_custom_derate_at_dpm[DML2_MAX_NUM_DPM_LVL]; }; diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c index 89f7ccd7f81f..0c8e652c3532 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c @@ -269,6 +269,22 @@ void dcn401_update_soc_bb_with_values_from_software_policy(struct dml2_soc_bb *s if (dc->bb_overrides.sr_enter_plus_exit_z8_time_ns) soc_bb->power_management_parameters.z8_stutter_enter_plus_exit_latency_us = dc->bb_overrides.sr_enter_plus_exit_z8_time_ns / 1000.0; + + /* Override per-dpm derates based on a custom derate table. + * Global derate value will be used for derates that aren't populated + * 3 derates for a single DPM level: + * bits 0-7: dram_derate_percent_pixel + * bits 8-15: fclk_derate_percent + * bits 16-23: dcfclk_derate_percent + */ + for (unsigned int i = 0; i < dc->debug.dml21_custom_derate_num_dpms; i++) { + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[i] + = dc->debug.dml21_custom_derate_at_dpm[i] & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 8) & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 16) & 0xFF; + } } static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) From d52de56938c38e70b4b77250912ee9e816cc145e Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Thu, 4 Jun 2026 09:26:22 -0400 Subject: [PATCH 023/155] drm/amd/display: update memclk clock table read for dcn42 Change memclk table to match dcfclk size to avoid fine grained mapping in dml2.1 Reviewed-by: Charlene Liu Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c index d01e5969a670..3baac7fa313a 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c @@ -999,15 +999,21 @@ void dcn42_get_smu_clocks(struct clk_mgr_internal *clk_mgr_int) clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_fclk_levels = dpm_clks->NumFclkLevelsEnabled; clk_mgr_base->bw_params->clk_table.num_entries = dpm_clks->NumFclkLevelsEnabled; - /* Memory Pstate table is in reverse order*/ + /* Memory Pstate table is in reverse order for dcn42. Proper way to map this is for pmfw to provide an fclk indexed uclk key since we consume fclk matched pairs.*/ ASSERT(dpm_clks->NumMemPstatesEnabled <= NUM_MEM_PSTATE_LEVELS); if (dpm_clks->NumMemPstatesEnabled > NUM_MEM_PSTATE_LEVELS) dpm_clks->NumMemPstatesEnabled = NUM_MEM_PSTATE_LEVELS; - for (i = 0; i < dpm_clks->NumMemPstatesEnabled; i++) { - clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; - clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio) ; + for (i = 0; i < dpm_clks->NumDcfClkLevelsEnabled; i++) { + if (i < dpm_clks->NumMemPstatesEnabled) { + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio); + } else { + clk_mgr_base->bw_params->clk_table.entries[i].memclk_mhz = dpm_clks->MemPstateTable[0].MemClk; + clk_mgr_base->bw_params->clk_table.entries[i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[0].WckRatio); + } } - clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumMemPstatesEnabled; + + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumDcfClkLevelsEnabled; /* DTBCLK*/ clk_mgr_base->bw_params->clk_table.entries[0].dtbclk_mhz = 600; /* Fixed on platform */ From 5a22cc7c724dc4ced818b2eae1db7433f467a761 Mon Sep 17 00:00:00 2001 From: Bhuvanachandra Pinninti Date: Mon, 22 Jun 2026 16:12:07 +0530 Subject: [PATCH 024/155] drm/amd/display: Fix dc_stream_remove_writeback dropping wrong writeback entries. [why] The matching entry was disabled but still copied during compaction, so a removed pipe could survive and overwrite a valid entry, leaving num_wb_info wrong. [how] Skip every entry matching dwb_pipe_inst and compact only survivors. Covered by test_dc_stream_writeback_drc_and_remove.And added new test cases for coverage. Reviewed-by: Ilya Bakoulin Signed-off-by: Bhuvanachandra Pinninti Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/core/dc_stream.c | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index 7666cdc78f4e..dbc12640b01c 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -720,20 +720,17 @@ bool dc_stream_remove_writeback(struct dc *dc, return false; } - /* remove writeback info for disabled writeback pipes from stream */ + /* remove writeback info for the requested writeback pipe from stream */ for (i = 0, j = 0; i < stream->num_wb_info; i++) { - if (stream->writeback_info[i].wb_enabled) { + /* drop every entry that targets the pipe being removed */ + if (stream->writeback_info[i].dwb_pipe_inst == dwb_pipe_inst) + continue; - if (stream->writeback_info[i].dwb_pipe_inst == dwb_pipe_inst) - stream->writeback_info[i].wb_enabled = false; - - /* trim the array */ - if (j < i) { - memcpy(&stream->writeback_info[j], &stream->writeback_info[i], - sizeof(struct dc_writeback_info)); - j++; - } - } + /* keep this entry, compacting it down when earlier entries were removed */ + if (j != i) + memcpy(&stream->writeback_info[j], &stream->writeback_info[i], + sizeof(struct dc_writeback_info)); + j++; } stream->num_wb_info = j; From 3b1fdd64133268aeccf8be488f52ff24998bab52 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 18 Jun 2026 21:12:21 -0600 Subject: [PATCH 025/155] drm/amd/display: Add KUnit tests for ism Add KUnit coverage for the following ISM functions: - dm_ism_next_state(): state transitions for running, busy, hysteresis-wait, optimized-idle, and aborted states - dm_ism_get_sso_delay(): null stream, zero frames, 1080p60 3-frame, and 4k60 1-frame calculations - dm_ism_get_idle_allow_delay(): null stream, zero filter/entry/delay frames, short-idle filtering, wrap-around, old history cutoff, mixed durations, and entry count exceeding history size - amdgpu_dm_ism_init(): initial state setup - amdgpu_dm_ism_fini(): cleanup after init - dm_ism_set_last_idle_ts(): timestamp update - dm_ism_insert_record(): basic insert and wrap-around - dm_ism_trigger_event(): valid and invalid transitions Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 4 + .../amdgpu_dm/tests/amdgpu_dm_ism_test.c | 353 ++++++++++++++++++ 2 files changed, 357 insertions(+) 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 32391b56097e..a9575bf25fc2 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 @@ -502,6 +502,7 @@ void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, } while (next_event < DM_ISM_NUM_EVENTS); } +EXPORT_IF_KUNIT(amdgpu_dm_ism_commit_event); static void dm_ism_delayed_work_func(struct work_struct *work) @@ -568,6 +569,7 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) disable_delayed_work_sync(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_disable); /** * amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER @@ -603,6 +605,7 @@ void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm) DM_ISM_EVENT_EXIT_IDLE_REQUESTED); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_force_full_power); /** * amdgpu_dm_ism_enable - enable the ISM @@ -626,6 +629,7 @@ void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm) enable_delayed_work(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_enable); void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism, struct amdgpu_dm_ism_config *config) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c index 7dfb3b351d20..6394b967ac3a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -8,6 +8,9 @@ #include #include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_ism.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -848,6 +851,345 @@ static void dm_test_dispatch_next_event_no_action_state(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } +/* + * Helper: allocate an amdgpu_crtc (which embeds the ISM) wired up to a + * minimally-populated amdgpu_device so that the container_of()/drm_to_adev() + * lookups inside the ISM event machinery resolve correctly. + * + * The amdgpu_device is large, so it must be heap-allocated via kunit_kzalloc. + * acrtc->base.dev points at the embedded &adev->ddev, which is what + * drm_to_adev() expects (it is a container_of of ddev). + */ +static struct amdgpu_crtc *alloc_test_acrtc(struct kunit *test, + struct amdgpu_device **adev_out) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + mutex_init(&adev->dm.dc_lock); + + acrtc->base.dev = &adev->ddev; + + if (adev_out) + *adev_out = adev; + + return acrtc; +} + +/* + * Helper: register an already-allocated amdgpu_crtc into the DRM device's + * crtc_list so that drm_for_each_crtc() iterates it. Only the list linkage is + * required for the ISM enable/disable/force-full-power helpers. + */ +static void register_test_acrtc(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) +{ + INIT_LIST_HEAD(&adev->ddev.mode_config.crtc_list); + INIT_LIST_HEAD(&acrtc->base.head); + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); +} + +/* ===== Tests for amdgpu_dm_ism_commit_event ===== */ + +/** + * dm_test_ism_commit_event_no_state - commit_event returns early without a crtc state + * @test: KUnit test context + * + * When the CRTC has no atomic state (base.state == NULL) the function takes + * the NO_STATE early-return path and the FSM is left untouched. + */ +static void dm_test_ism_commit_event_no_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = NULL; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_cursor_transition - cursor begin/end drive FSM without DC work + * @test: KUnit test context + * + * BEGIN_CURSOR_UPDATE then END_CURSOR_UPDATE from FULL_POWER_RUNNING traverse + * the FULL_POWER_BUSY state. Neither transition reaches the idle-optimization + * commit path, so no DC hardware access occurs and the FSM returns to + * FULL_POWER_RUNNING. + */ +static void dm_test_ism_commit_event_cursor_transition(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_BUSY); + + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_END_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_invalid_event - invalid event leaves FSM unchanged + * @test: KUnit test context + * + * EXIT_IDLE_REQUESTED is not a valid event from FULL_POWER_RUNNING, so the + * FSM does not transition and no power-state dispatch occurs. + */ +static void dm_test_ism_commit_event_invalid_event(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_force_full_power ===== */ + +/** + * dm_test_ism_force_full_power - force-full-power sends EXIT_IDLE to every CRTC + * @test: KUnit test context + * + * From the initial FULL_POWER_RUNNING state EXIT_IDLE_REQUESTED is a no-op, so + * the FSM remains in FULL_POWER_RUNNING and no DC work is scheduled. + */ +static void dm_test_ism_force_full_power(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + register_test_acrtc(adev, acrtc); + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_force_full_power(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_disable / amdgpu_dm_ism_enable ===== */ + +/** + * dm_test_ism_disable_enable_cycle - disable then enable quiesces and re-arms work + * @test: KUnit test context + * + * Walks every CRTC's ISM, disabling its delayed work (synchronously) and then + * re-enabling it. With no work ever scheduled both calls complete without + * touching the FSM state. disable must be called without dc_lock held. + */ +static void dm_test_ism_disable_enable_cycle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + register_test_acrtc(adev, acrtc); + + amdgpu_dm_ism_disable(&adev->dm); + amdgpu_dm_ism_enable(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for dm_ism_dispatch_power_state (via commit_event) ===== */ + +/* + * Build a config + history that makes dm_ism_get_idle_allow_delay() return a + * non-zero hysteresis delay. A non-zero delay keeps the FSM parked in + * HYSTERESIS_WAITING (the dispatcher returns DM_ISM_NUM_EVENTS instead of an + * immediate follow-up event), preventing the cascade into the DC-dependent + * OPTIMIZED_IDLE / *_SSO states. + */ +static void setup_idle_delay_history(struct amdgpu_dm_ism *ism, + struct dc_stream_state *stream) +{ + uint64_t one_frame_ns; + + 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); + + 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; +} + +/** + * dm_test_ism_dispatch_hysteresis_schedule_and_cancel - cover the HYSTERESIS_WAITING dispatch arms + * @test: KUnit test context + * + * ENTER_IDLE_REQUESTED moves FULL_POWER_RUNNING -> HYSTERESIS_WAITING. The + * current-state arm of dm_ism_dispatch_power_state() then records the idle + * timestamp, computes a (non-zero) idle-allow delay and schedules the delayed + * worker. A subsequent BEGIN_CURSOR_UPDATE (-> HYSTERESIS_BUSY) exercises the + * previous-state arm that cancels that pending worker. Neither arm reaches the + * DC-dependent idle-optimization commit path. + */ +static void dm_test_ism_dispatch_hysteresis_schedule_and_cancel(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct dc_stream_state *stream; + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 0, + .sso_num_frames = 0, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + /* Enter HYSTERESIS_WAITING: schedules the idle-allow worker. */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + + /* Cursor update cancels the pending worker (prev-state arm). */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_BUSY); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_dispatch_optimized_idle_defers_sso - cover OPTIMIZED_IDLE dispatch without DC + * @test: KUnit test context + * + * With sso_num_frames < filter_num_frames the OPTIMIZED_IDLE current-state arm + * skips the idle-optimization commit and only schedules the SSO worker. Driving + * HYSTERESIS_WAITING -> OPTIMIZED_IDLE via TIMER_ELAPSED exercises that arm + * (get_sso_delay + the skip branch + mod_delayed_work) without any DC access. + */ +static void dm_test_ism_dispatch_optimized_idle_defers_sso(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct dc_stream_state *stream; + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 0, + .sso_num_frames = 2, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + + /* + * Timer fires: HYSTERESIS_WAITING -> OPTIMIZED_IDLE. sso_delay + * is non-zero and sso_num_frames < filter_num_frames, so the + * commit is deferred to the SSO worker and the FSM parks here. + */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_TIMER_ELAPSED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_OPTIMIZED_IDLE); + + /* Cancel the scheduled SSO worker while still holding dc_lock. */ + cancel_delayed_work(&acrtc->ism.sso_delayed_work); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + 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), @@ -910,6 +1252,17 @@ static struct kunit_case dm_ism_test_cases[] = { KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_with_sso_delay), KUNIT_CASE(dm_test_dispatch_next_event_timer_aborted), KUNIT_CASE(dm_test_dispatch_next_event_no_action_state), + /* amdgpu_dm_ism_commit_event */ + KUNIT_CASE(dm_test_ism_commit_event_no_state), + KUNIT_CASE(dm_test_ism_commit_event_cursor_transition), + KUNIT_CASE(dm_test_ism_commit_event_invalid_event), + /* amdgpu_dm_ism_force_full_power */ + KUNIT_CASE(dm_test_ism_force_full_power), + /* amdgpu_dm_ism_disable / amdgpu_dm_ism_enable */ + KUNIT_CASE(dm_test_ism_disable_enable_cycle), + /* dm_ism_dispatch_power_state (via commit_event) */ + KUNIT_CASE(dm_test_ism_dispatch_hysteresis_schedule_and_cancel), + KUNIT_CASE(dm_test_ism_dispatch_optimized_idle_defers_sso), {} }; From b6f466c509ad2f390b3fc91cd0de4783554f5f98 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Mon, 22 Jun 2026 16:29:25 -0400 Subject: [PATCH 026/155] drm/amd/display: add null pointer access check [why] need to add null pointer access check Reviewed-by: Gabe Teeger Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c b/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c index 4b36c01a9e7a..00e82d627d91 100644 --- a/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c +++ b/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c @@ -1189,8 +1189,9 @@ void dcn10_link_encoder_dp_set_phy_pattern( set_dp_phy_pattern_prbs7(enc10); break; case DP_TEST_PATTERN_80BIT_CUSTOM: - set_dp_phy_pattern_80bit_custom( - enc10, param->custom_pattern); + if (param) + set_dp_phy_pattern_80bit_custom( + enc10, param->custom_pattern); break; case DP_TEST_PATTERN_CP2520_1: set_dp_phy_pattern_hbr2_compliance_cp2520_2(enc10, 1); @@ -1202,8 +1203,9 @@ void dcn10_link_encoder_dp_set_phy_pattern( set_dp_phy_pattern_hbr2_compliance_cp2520_2(enc10, 3); break; case DP_TEST_PATTERN_VIDEO_MODE: { - set_dp_phy_pattern_passthrough_mode( - enc10, param->dp_panel_mode); + if (param) + set_dp_phy_pattern_passthrough_mode( + enc10, param->dp_panel_mode); break; } From fd651b9a3be9e7aba98ac2afacbf10b541d9ba00 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 24 Jun 2026 14:59:20 -0400 Subject: [PATCH 027/155] drm/amd/display: Add DCN42B VID_CRC_CONTROL and HBLANK_CONTROL registers [why] These registers are needed by our existing code. [how] Add the missing register defines to dcn42b_resource.c. Remove DCN42B variant of HP_DP_STREAM_ENC_REG_LIST_RI as it is now the same as the DCN42B one, when these registers are included. Reviewed-by: Aurabindo Pillai Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../dc/resource/dcn42b/dcn42b_resource.c | 15 ++++++- .../dc/resource/dcn42b/dcn42b_resource.h | 40 ------------------- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 172836dfd679..c555be6e541d 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -134,6 +134,19 @@ #define regHUBP3_HUBPREQ_DEBUG 0x088d #define regHUBP3_HUBPREQ_DEBUG_BASE_IDX 2 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_VID_CRC_CONTROL 0x3687 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC1_DP_SYM32_ENC_VID_CRC_CONTROL 0x375b +#define regDP_SYM32_ENC1_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_VID_CRC_CONTROL 0x382f +#define regDP_SYM32_ENC2_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_HBLANK_CONTROL 0x366b +#define regDP_SYM32_ENC0_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC1_DP_SYM32_ENC_HBLANK_CONTROL 0x373f +#define regDP_SYM32_ENC1_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_HBLANK_CONTROL 0x3813 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 + enum dcn401_clk_src_array_id { DCN401_CLK_SRC_PLL0, DCN401_CLK_SRC_PLL1, @@ -351,7 +364,7 @@ static const struct dcn10_link_enc_mask le_mask = { LINK_ENCODER_MASK_SH_LIST_DCN42B(_MASK)}; #define hpo_dp_stream_encoder_reg_init(id) \ - DCN42B_HPO_DP_STREAM_ENC_REG_LIST_RI(id) + DCN42_HPO_DP_STREAM_ENC_REG_LIST_RI(id) static struct dcn31_hpo_dp_stream_encoder_registers hpo_dp_stream_enc_regs[4]; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 1a6e0e7be0fa..484ba1455468 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -254,46 +254,6 @@ SRI_ARR(STREAM_MAPPER_CONTROL, DIG, id),\ SRI_ARR(DIG_FE_AUDIO_CNTL, DIG, id) -/* HPO DP stream encoder */ -/* Not in DCN42B: - * SRI_ARR(DP_SYM32_ENC_VID_CRC_CONTROL, DP_SYM32_ENC, id), - * SRI_ARR(DP_SYM32_ENC_HBLANK_CONTROL, DP_SYM32_ENC, id), - */ -#define DCN42B_HPO_DP_STREAM_ENC_REG_LIST_RI(id) \ - SR_ARR(DP_STREAM_MAPPER_CONTROL0, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL1, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL2, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL3, id), \ - SRI_ARR(DP_STREAM_ENC_CLOCK_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_INPUT_MUX_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_AUDIO_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_CLOCK_RAMP_ADJUSTER_FIFO_STATUS_CONTROL0, DP_STREAM_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_PIXEL_FORMAT, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_PIXEL_FORMAT_DOUBLE_BUFFER_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA0, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA1, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA2, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA3, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA4, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA5, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA6, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA7, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA8, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA_DOUBLE_BUFFER_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_FIFO_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_STREAM_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_VBID_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL0, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL2, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL3, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL5, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL11, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_METADATA_PACKET_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_AUDIO_CONTROL0, DP_SYM32_ENC, id) - /*HPO DP link encoder regs */ #define DCN42B_HPO_DP_LINK_ENC_REG_LIST_RI(id) \ SRI_ARR(DP_LINK_ENC_CLOCK_CONTROL, DP_LINK_ENC, id), \ From cee2f77c774f256676422483e4cad2c38e10d68b Mon Sep 17 00:00:00 2001 From: James Lin Date: Wed, 24 Jun 2026 15:36:03 +0800 Subject: [PATCH 028/155] drm/amd/display: Add MALL status readback support for DCN 4.0.1 [why] The IGT test igt@amdgpu/amd_mall@static-screen always fails on DCN 4.0.1 because debugfs reports "mall enabled: no" even when MALL is actually active. This is because the DCN 4.0.1 hubbub function table is missing the .get_mall_en callback, so capabilities_show() always sees mall_in_use as false. [how] Reuse hubbub32_get_mall_en() for DCN 4.0.1 since the DCHUBBUB_ARB_MALL_CNTL register layout is identical to DCN 3.2: - Register the DCHUBBUB_ARB_MALL_CNTL register offset in dcn401_resource.h - Add MALL_PREFETCH_COMPLETE and MALL_IN_USE mask/shift definitions in dcn401_hubbub.h - Wire up .get_mall_en = hubbub32_get_mall_en in hubbub4_01_funcs Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c | 3 ++- drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h | 4 +++- .../gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c index bf30a1bb61b7..e9ff20c02b1e 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c @@ -1250,7 +1250,8 @@ static const struct hubbub_funcs hubbub4_01_funcs = { .program_compbuf_segments = dcn401_program_compbuf_segments, .wait_for_det_update = dcn401_wait_for_det_update, .program_arbiter = dcn401_program_arbiter, - .hubbub_read_reg_state = hubbub3_read_reg_state + .hubbub_read_reg_state = hubbub3_read_reg_state, + .get_mall_en = hubbub32_get_mall_en }; void hubbub401_construct(struct dcn20_hubbub *hubbub2, diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h index f48715544429..346bead81504 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h @@ -133,7 +133,9 @@ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, ROB_OVERFLOW_STATUS, mask_sh),\ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, ROB_OVERFLOW_CLEAR, mask_sh),\ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, DCHUBBUB_HW_DEBUG, mask_sh),\ - HUBBUB_SF(DCHUBBUB_CTRL_STATUS, CSTATE_SWATH_CHK_GOOD_MODE, mask_sh) + HUBBUB_SF(DCHUBBUB_CTRL_STATUS, CSTATE_SWATH_CHK_GOOD_MODE, mask_sh),\ + HUBBUB_SF(DCHUBBUB_ARB_MALL_CNTL, MALL_PREFETCH_COMPLETE, mask_sh),\ + HUBBUB_SF(DCHUBBUB_ARB_MALL_CNTL, MALL_IN_USE, mask_sh) bool hubbub401_program_urgent_watermarks( struct hubbub *hubbub, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h index 47f82b818262..ce96fe1ddb7c 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h @@ -622,7 +622,8 @@ int dcn401_get_power_profile(const struct dc_state *context); SR(DCHUBBUB_MEM_PWR_MODE_CTRL), \ SR(DCHUBBUB_TIMEOUT_DETECTION_CTRL1), \ SR(DCHUBBUB_TIMEOUT_DETECTION_CTRL2), \ - SR(DCHUBBUB_CTRL_STATUS) + SR(DCHUBBUB_CTRL_STATUS), \ + SR(DCHUBBUB_ARB_MALL_CNTL) /* DCCG */ From 5a3fb8427ebc39e95597626da3991a4fd79deef8 Mon Sep 17 00:00:00 2001 From: Dominik Kaszewski Date: Fri, 19 Jun 2026 13:50:39 +0200 Subject: [PATCH 029/155] drm/amd/display: Fix indenting of dpms on [Why & How] Cleanup for future refactors. Reviewed-by: Wenjing Liu Signed-off-by: Dominik Kaszewski Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/link/link_dpms.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 479a8fbfdc5d..335ae952ef60 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -2610,13 +2610,14 @@ void link_set_dpms_on( */ if (stream->timing.flags.DSC) { if (dc_is_dp_signal(stream->signal) || - dc_is_virtual_signal(stream->signal)) + dc_is_virtual_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, true); } if (link->replay_settings.config.replay_supported && !dc_is_embedded_signal(link->connector_signal)) dp_setup_replay(link, stream); + // TODO: Split DPMS-on into 3 functions at this point status = enable_link(state, pipe_ctx); if (status != DC_OK) { @@ -2629,9 +2630,9 @@ void link_set_dpms_on( * show the stream anyway. But MST displays can't proceed * without link training. */ - if ((status != DC_FAIL_DP_LINK_TRAINING && - status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || - stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { + if ((status != DC_FAIL_DP_LINK_TRAINING && + status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { if (false == link->link_status.link_active) disable_link(link, &pipe_ctx->link_res, stream->signal); @@ -2639,6 +2640,7 @@ void link_set_dpms_on( return; } } + // TODO: Split DPMS-on into 3 functions at this point if (stream->timing.flags.DSC && dc_is_hdmi_frl_signal(stream->signal)) //TODO: bring HDMI FRL in line with DP @@ -2657,13 +2659,11 @@ void link_set_dpms_on( if (!(dc_is_virtual_signal(stream->signal) || dc_is_hdmi_frl_signal(stream->signal) || dp_is_128b_132b_signal(pipe_ctx))) { - - if (link_enc) - link_enc->funcs->setup( + if (link_enc) + link_enc->funcs->setup( link_enc, stream->signal); - - } + } dc->hwss.enable_stream(pipe_ctx); From 5b7e4ad090f2700bfad37f43e4d3553b71be3591 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Wed, 24 Jun 2026 18:54:43 -0400 Subject: [PATCH 030/155] drm/amd/display: ensure dtbclk clk_src selected before hdmistream_clk_en [why] correct a sequence issue by switching to dcn35's dccg sequence: to make sure select dtbclk src first before programming hdmistream_clk_en. Reviewed-by: Leo Chen Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c | 2 +- drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h | 5 ++++- drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c index 483cd9ab7eb7..617f504c8e6b 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c @@ -1654,7 +1654,7 @@ void dccg35_set_dpstreamclk_root_clock_gating(struct dccg *dccg, int dp_hpo_inst -static void dccg35_set_hdmistreamclk( +void dccg35_set_hdmistreamclk( struct dccg *dccg, enum streamclk_source src, uint32_t otg_inst) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h index 554700287c1a..58f4c1e998b3 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h @@ -269,5 +269,8 @@ void dccg35_disable_dscclk(struct dccg *dccg, int inst); void dccg35_enable_symclk_se(struct dccg *dccg, uint32_t stream_enc_inst, uint32_t link_enc_inst); void dccg35_disable_symclk_se(struct dccg *dccg, uint32_t stream_enc_inst, uint32_t link_enc_inst); - +void dccg35_set_hdmistreamclk( + struct dccg *dccg, + enum streamclk_source src, + uint32_t otg_inst); #endif //__DCN35_DCCG_H__ diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index 616a896f0782..3b03b152da22 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -313,7 +313,7 @@ static void dccg42_init(struct dccg *dccg) static const struct dccg_funcs dccg42_funcs = { .enable_hdmicharclk = dccg401_enable_hdmicharclk, .disable_hdmicharclk = dccg42_disable_hdmicharclk, - .set_hdmistreamclk = dccg401_set_hdmistreamclk, + .set_hdmistreamclk = dccg35_set_hdmistreamclk, .set_hdmistreamclk_root_clock_gating = dccg35_set_hdmistreamclk_root_clock_gating, .update_dpp_dto = dccg35_update_dpp_dto, .dpp_root_clock_control = dccg35_dpp_root_clock_control, From acf97c57d091f0df2315310f82ffd6ccff500594 Mon Sep 17 00:00:00 2001 From: James Lin Date: Fri, 26 Jun 2026 11:21:00 +0800 Subject: [PATCH 031/155] drm/amd/display: Re-evaluate cursor mode on plane position/size change [Why] dm_crtc_get_cursor_mode() only re-evaluates the required cursor mode (native vs overlay) when a top plane changes its scale, pixel format, enable state, or zpos/color pipeline. It does not re-evaluate when a plane changes only its destination rectangle (crtc_x, crtc_y, crtc_w, crtc_h) at a constant scale. A pure move/resize can create or remove a hole under the cursor, which changes whether the native cursor is valid. When a primary plane shrinks and no longer covers the cursor region, the cursor mode stays NATIVE and the cursor is not rendered over the uncovered area, so it becomes invisible there. This is caught by igt@amdgpu/amd_cursor_overlay@non-full, where the test CRC was a constant black value across all cursor positions instead of tracking the reference. [How] In the per-plane loop of dm_crtc_get_cursor_mode(), set consider_mode_change when any of crtc_x, crtc_y, crtc_w or crtc_h differs between the old and new plane state, so a plane move/resize forces re-evaluation of the cursor mode. The driver then correctly promotes the cursor to OVERLAY mode when the primary stops covering the cursor region. Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 67b825cbb88f..74bb5845b6be 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6649,6 +6649,20 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, break; } + /* + * A plane moving or resizing (without a scale change) changes how + * much of the CRTC it covers. This can create/remove holes under + * the cursor and thus flip the required cursor mode (native vs + * overlay), so the destination rect must be re-evaluated too. + */ + if (old_plane_state->crtc_x != plane_state->crtc_x || + old_plane_state->crtc_y != plane_state->crtc_y || + old_plane_state->crtc_w != plane_state->crtc_w || + old_plane_state->crtc_h != plane_state->crtc_h) { + consider_mode_change = true; + break; + } + if (dm_plane_color_pipeline_active(state, plane, true) != dm_plane_color_pipeline_active(state, plane, false)) { consider_mode_change = true; From 50dd2baefb4cfb0d1a83b14d6a94ea212eb8f134 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 032/155] drm/amd/display: Test EDID and ACPI/VBIOS readers [WHAT] Add KUnit coverage for the EDID parsing helpers edid_extract_panel_id, apply_edid_quirks and dm_helpers_parse_edid_caps, together with the ACPI/VBIOS EDID readers dm_helpers_probe_acpi_edid, dm_helpers_read_acpi_edid and dm_helpers_read_vbios_hardcoded_edid. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 27 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 19 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 727 ++++++++++++++++++ 3 files changed, 769 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 71e2627f9a9d..8f905ad8aaa1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -98,7 +98,7 @@ STATIC_IFN_KUNIT u32 edid_extract_panel_id(struct edid *edid) } EXPORT_IF_KUNIT(edid_extract_panel_id); -static void apply_edid_quirks(struct dc_link *link, struct edid *edid, +STATIC_IFN_KUNIT void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps) { struct amdgpu_dm_connector *aconnector = link->priv; @@ -141,6 +141,7 @@ static void apply_edid_quirks(struct dc_link *link, struct edid *edid, return; } } +EXPORT_IF_KUNIT(apply_edid_quirks); /** * dm_helpers_parse_edid_caps() - Parse edid caps @@ -237,6 +238,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( return result; } +EXPORT_IF_KUNIT(dm_helpers_parse_edid_caps); static void fill_dc_mst_payload_table_from_drm(struct dc_link *link, @@ -979,6 +981,20 @@ bool dm_helpers_dp_write_dsc_enable( return ret; } +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +uint dm_helpers_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(dm_helpers_get_dc_debug_mask); + +void dm_helpers_set_dc_debug_mask(uint debug_mask) +{ + amdgpu_dc_debug_mask = debug_mask; +} +EXPORT_IF_KUNIT(dm_helpers_set_dc_debug_mask); +#endif + bool dm_helpers_dp_write_hblank_reduction(struct dc_context *ctx, const struct dc_stream_state *stream) { // TODO @@ -1002,7 +1018,7 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link) return dp_sink_present; } -static int +STATIC_IFN_KUNIT int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) { struct drm_connector *connector = data; @@ -1040,8 +1056,9 @@ dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) return r; } +EXPORT_IF_KUNIT(dm_helpers_probe_acpi_edid); -static const struct drm_edid * +STATIC_IFN_KUNIT const struct drm_edid * dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; @@ -1062,8 +1079,9 @@ dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector) return drm_edid_read_custom(connector, dm_helpers_probe_acpi_edid, connector); } +EXPORT_IF_KUNIT(dm_helpers_read_acpi_edid); -static const struct drm_edid * +STATIC_IFN_KUNIT const struct drm_edid * dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_connector *aconnector) { struct dc_bios *bios = link->ctx->dc_bios; @@ -1101,6 +1119,7 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn return edid; } +EXPORT_IF_KUNIT(dm_helpers_read_vbios_hardcoded_edid); STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index 2ac9762895ec..e5cbcd425847 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -9,12 +9,31 @@ #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) #include +struct amdgpu_dm_connector; +struct dc_link; +struct dc_context; +struct dc_stream_state; +struct dc_edid_caps; +struct dc_dp_mst_stream_allocation_table; +struct drm_dp_aux; +struct drm_dp_mst_atomic_payload; +struct drm_dp_mst_topology_mgr; +struct drm_dp_mst_topology_state; + /* Exported for KUnit testing */ u32 edid_extract_panel_id(struct edid *edid); +void apply_edid_quirks(struct dc_link *link, struct edid *edid, + struct dc_edid_caps *edid_caps); uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +uint dm_helpers_get_dc_debug_mask(void); +void dm_helpers_set_dc_debug_mask(uint debug_mask); +int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len); +const struct drm_edid *dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector); +const struct drm_edid *dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, + struct amdgpu_dm_connector *aconnector); #endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ #endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 33014a2d2222..3766d8d211e4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -8,13 +8,18 @@ #include #include #include +#include #include "dc.h" +#include "core_types.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_mst_types.h" +#include "dc_bios_types.h" #include "dm_helpers.h" #include "ddc_service_types.h" +#include "dmub_cmd.h" #include "amdgpu_dm_helpers.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -61,6 +66,701 @@ static void dm_test_edid_extract_panel_id_zeros(struct kunit *test) KUNIT_EXPECT_EQ(test, edid_extract_panel_id(edid), 0U); } +/* Tests for apply_edid_quirks() */ + +/* + * Build an EDID whose extracted panel id equals @panel_id. Inverse of + * edid_extract_panel_id(): mfg_id holds the top 16 bits, prod_code the + * low 16 bits (prod_code[0] | prod_code[1] << 8). + */ +static struct edid *dm_test_edid_with_panel_id(struct kunit *test, u32 panel_id) +{ + struct edid *edid; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->mfg_id[0] = (panel_id >> 24) & 0xff; + edid->mfg_id[1] = (panel_id >> 16) & 0xff; + edid->prod_code[0] = panel_id & 0xff; + edid->prod_code[1] = (panel_id >> 8) & 0xff; + + return edid; +} + +/* + * Wire a connector-backed link so apply_edid_quirks() can resolve + * link->priv->base.dev for its drm_dbg_driver() calls. + */ +static struct dc_link *dm_test_quirk_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + link->priv = aconnector; + + return link; +} + +/** + * dm_test_apply_edid_quirks_dpcd_poweroff_delay - Test GBT 0x3215 delay quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_dpcd_poweroff_delay(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('G', 'B', 'T', 0x3215)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.wait_after_dpcd_poweroff_ms, 10000U); +} + +/** + * dm_test_apply_edid_quirks_disable_fams - Test SAM panel FAMS-disable quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_disable_fams(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('S', 'A', 'M', 0x0E5E)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_fams); +} + +/** + * dm_test_apply_edid_quirks_remove_sink_ext_caps - Test AUO 0x317 clear quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_remove_sink_ext_caps(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('A', 'U', 'O', 0xA7AB)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.remove_sink_ext_caps); +} + +/** + * dm_test_apply_edid_quirks_disable_colorimetry - Test SDC VSC-disable quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_disable_colorimetry(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('S', 'D', 'C', 0x4154)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_colorimetry); +} + +/** + * dm_test_apply_edid_quirks_skip_phy_ssc - Test DEL 0x4147 PHY SSC quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_skip_phy_ssc(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('D', 'E', 'L', 0x4147)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, link->wa_flags.skip_phy_ssc_reduction); +} + +/** + * dm_test_apply_edid_quirks_unknown_noop - Test unknown panel id is a no-op + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_unknown_noop(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('X', 'Y', 'Z', 0x0000)); + + apply_edid_quirks(link, edid, &edid_caps); + + /* default: branch leaves every quirk field untouched */ + KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.wait_after_dpcd_poweroff_ms, 0U); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.disable_fams); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.remove_sink_ext_caps); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.disable_colorimetry); + KUNIT_EXPECT_FALSE(test, link->wa_flags.skip_phy_ssc_reduction); +} + +/* Tests for dm_helpers_parse_edid_caps() */ + +/* + * Build a minimal valid base EDID block into @dc_edid. When @good_checksum is + * false the final checksum byte is corrupted so drm_edid_is_valid() fails. + * + * manufacturer_id = 0xAC10, product_id = 0x1234, serial = 0x12345678, + * week = 10, year (raw) = 30, digital input, no CEA extension. + */ +static void dm_test_fill_base_edid(struct dc_edid *dc_edid, bool good_checksum) +{ + static const u8 header[8] = { + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 + }; + u8 *raw = dc_edid->raw_edid; + int sum = 0; + int i; + + memset(raw, 0, EDID_LENGTH); + memcpy(raw, header, sizeof(header)); + + raw[8] = 0x10; raw[9] = 0xAC; /* mfg_id -> 0xAC10 */ + raw[10] = 0x34; raw[11] = 0x12; /* prod_code -> 0x1234 */ + raw[12] = 0x78; raw[13] = 0x56; /* serial -> 0x12345678 */ + raw[14] = 0x34; raw[15] = 0x12; + raw[16] = 10; /* mfg_week */ + raw[17] = 30; /* mfg_year (raw) */ + raw[18] = 1; /* version */ + raw[19] = 4; /* revision */ + raw[20] = DRM_EDID_INPUT_DIGITAL; /* digital input */ + raw[126] = 0; /* no extensions */ + + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += raw[i]; + raw[127] = (256 - (sum % 256)) % 256; + if (!good_checksum) + raw[127] ^= 0xFF; /* corrupt checksum */ + + dc_edid->length = EDID_LENGTH; +} + +/** + * dm_test_parse_edid_caps_null_edid - Test NULL edid returns EDID_BAD_INPUT + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_null_edid(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, NULL, &edid_caps), EDID_BAD_INPUT); +} + +/** + * dm_test_parse_edid_caps_null_caps - Test NULL edid_caps returns EDID_BAD_INPUT + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_null_caps(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, NULL), EDID_BAD_INPUT); +} + +/** + * dm_test_parse_edid_caps_valid - Test field extraction from a valid EDID + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_valid(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_base_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->manufacturer_id, 0xAC10); + KUNIT_EXPECT_EQ(test, edid_caps->product_id, 0x1234); + KUNIT_EXPECT_EQ(test, edid_caps->serial_number, 0x12345678U); + KUNIT_EXPECT_EQ(test, edid_caps->manufacture_week, 10); + KUNIT_EXPECT_EQ(test, edid_caps->manufacture_year, 30); + KUNIT_EXPECT_FALSE(test, edid_caps->analog); +} + +/** + * dm_test_parse_edid_caps_bad_checksum - Test bad checksum still parses fields + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_bad_checksum(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_base_edid(dc_edid, false); + + /* Invalid checksum -> EDID_BAD_CHECKSUM but fields are still parsed */ + KUNIT_EXPECT_EQ(test, + dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), + EDID_BAD_CHECKSUM); + KUNIT_EXPECT_EQ(test, edid_caps->manufacturer_id, 0xAC10); + KUNIT_EXPECT_EQ(test, edid_caps->product_id, 0x1234); +} + +/** + * dm_test_parse_edid_caps_hdmi_frl - Test HDMI/FRL branch via connector info + * @test: The KUnit test context + * + * Drives the edid_caps->edid_hdmi path by marking the connector as HDMI and + * providing a fake dc with FRL enabled, so populate_hdmi_info_from_connector() + * is exercised inside dm_helpers_parse_edid_caps(). + */ +static void dm_test_parse_edid_caps_hdmi_frl(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_connector *connector = &aconnector->base; + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + struct dc *dc; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->dc = dc; + dc->config.enable_frl = true; + + dm_test_fill_base_edid(dc_edid, true); + + /* Drive the HDMI/FRL branch */ + connector->display_info.is_hdmi = true; + connector->display_info.hdmi.scdc.supported = true; + connector->display_info.hdmi.max_lanes = 4; + connector->display_info.hdmi.max_frl_rate_per_lane = 12; + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_TRUE(test, edid_caps->edid_hdmi); + KUNIT_EXPECT_TRUE(test, edid_caps->scdc_present); + /* max_lanes 4 + max_frl_rate_per_lane 12 -> rate index 6 */ + KUNIT_EXPECT_EQ(test, edid_caps->max_frl_rate, 6); +} + +/** + * dm_test_parse_edid_caps_hdmi_frl_dsc - Test HDMI FRL DSC sub-branch + * @test: The KUnit test context + * + * Sets the connector's HDMI DSC capability so populate_hdmi_info_from_connector() + * reports frl_dsc_support, exercising the frl_dsc_support log path. + */ +static void dm_test_parse_edid_caps_hdmi_frl_dsc(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_connector *connector = &aconnector->base; + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + struct dc *dc; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->dc = dc; + dc->config.enable_frl = true; + + dm_test_fill_base_edid(dc_edid, true); + + connector->display_info.is_hdmi = true; + /* Drive the frl_dsc_support sub-branch */ + connector->display_info.hdmi.dsc_cap.v_1p2 = true; + connector->display_info.hdmi.dsc_cap.bpc_supported = 10; + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_TRUE(test, edid_caps->frl_dsc_support); + KUNIT_EXPECT_TRUE(test, edid_caps->frl_dsc_10bpc); +} + +/* + * Build a 2-block EDID: a valid base block plus a CTA-861 extension block + * carrying one LPCM Short Audio Descriptor and, when @with_speaker is set, a + * Speaker Allocation Data Block, so the audio/speaker parsing code is + * exercised. + */ +static void dm_test_fill_cea_edid(struct dc_edid *dc_edid, bool with_speaker) +{ + u8 *raw = dc_edid->raw_edid; + u8 *ext = raw + EDID_LENGTH; + u8 dtd_offset; + int sum = 0; + int i; + + /* Base block, flagged as having one extension */ + dm_test_fill_base_edid(dc_edid, true); + raw[126] = 1; + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += raw[i]; + raw[127] = (256 - (sum % 256)) % 256; + + /* Audio DB spans [4,7]; optional Speaker Alloc DB spans [8,11] */ + dtd_offset = with_speaker ? 12 : 8; + + /* CTA-861 extension block */ + memset(ext, 0, EDID_LENGTH); + ext[0] = 0x02; /* CTA extension tag */ + ext[1] = 0x03; /* revision 3 */ + ext[2] = dtd_offset; /* DTD offset; end of data blocks */ + ext[3] = 0x00; /* no native DTDs / feature flags */ + /* Audio Data Block: tag 1, length 3, one LPCM SAD */ + ext[4] = (1 << 5) | 3; + ext[5] = (1 << 3) | 1; /* format 1 (LPCM), 2 channels */ + ext[6] = 0x07; /* 32 / 44.1 / 48 kHz */ + ext[7] = 0x07; /* 16 / 20 / 24-bit */ + if (with_speaker) { + /* Speaker Allocation Data Block: tag 4, length 3 */ + ext[8] = (4 << 5) | 3; + ext[9] = 0x01; /* front left / front right */ + } + + sum = 0; + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += ext[i]; + ext[127] = (256 - (sum % 256)) % 256; + + dc_edid->length = 2 * EDID_LENGTH; +} + +/** + * dm_test_parse_edid_caps_cea_audio - Test CEA audio/speaker block parsing + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_cea_audio(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_cea_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->audio_mode_count, 1); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].format_code, 1); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].channel_count, 2); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].sample_rate, 0x07); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].sample_size, 0x07); + KUNIT_EXPECT_EQ(test, edid_caps->speaker_flags, 0x01); +} + +/** + * dm_test_parse_edid_caps_cea_no_speaker - Test default speaker flags path + * @test: The KUnit test context + * + * Audio data block present but no Speaker Allocation Data Block, so + * speaker_flags falls back to DEFAULT_SPEAKER_LOCATION. + */ +static void dm_test_parse_edid_caps_cea_no_speaker(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_cea_edid(dc_edid, false); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->audio_mode_count, 1); + KUNIT_EXPECT_EQ(test, edid_caps->speaker_flags, DEFAULT_SPEAKER_LOCATION); +} + +/* Tests for ACPI and VBIOS EDID readers */ + +/** + * dm_test_probe_acpi_edid_no_companion - Test ACPI probe without companion + * @test: The KUnit test context + */ +static void dm_test_probe_acpi_edid_no_companion(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + u8 buf[EDID_LENGTH] = {0}; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + + KUNIT_EXPECT_EQ(test, + dm_helpers_probe_acpi_edid(&aconnector->base, buf, 0, sizeof(buf)), + -ENODEV); +} + +/** + * dm_test_read_acpi_edid_debug_mask_disabled - Test debug mask early return + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_debug_mask_disabled(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + uint old_debug_mask; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + old_debug_mask = dm_helpers_get_dc_debug_mask(); + + dm_helpers_set_dc_debug_mask(old_debug_mask | DC_DISABLE_ACPI_EDID); + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); + dm_helpers_set_dc_debug_mask(old_debug_mask); +} + +/** + * dm_test_read_acpi_edid_non_panel_connector - Test non-panel connector skip + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_non_panel_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); +} + +/** + * dm_test_read_acpi_edid_force_off - Test forced-off connector skip + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_force_off(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + aconnector->base.force = DRM_FORCE_OFF; + + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); +} + +struct dm_test_vbios_edid { + struct dc_bios bios; + enum bp_result result; + const u8 *fake_edid; + u16 fake_edid_size; + u16 width_mm; + u16 height_mm; +}; + +static enum bp_result dm_test_get_embedded_panel_info(struct dc_bios *bios, + struct embedded_panel_info *info) +{ + struct dm_test_vbios_edid *fixture; + + fixture = container_of(bios, struct dm_test_vbios_edid, bios); + if (fixture->result != BP_RESULT_OK) + return fixture->result; + + info->fake_edid = fixture->fake_edid; + info->fake_edid_size = fixture->fake_edid_size; + info->panel_width_mm = fixture->width_mm; + info->panel_height_mm = fixture->height_mm; + + return BP_RESULT_OK; +} + +static const struct dc_vbios_funcs dm_test_vbios_edid_funcs = { + .get_embedded_panel_info = dm_test_get_embedded_panel_info, +}; + +static void dm_test_setup_vbios_link(struct kunit *test, + struct dc_link **link_out, + struct amdgpu_dm_connector **aconnector_out, + struct dm_test_vbios_edid **fixture_out) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + aconnector = dm_kunit_alloc_connector(test, adev, link); + + fixture->bios.funcs = &dm_test_vbios_edid_funcs; + fixture->result = BP_RESULT_OK; + ctx->dc_bios = &fixture->bios; + link->ctx = ctx; + link->priv = aconnector; + link->connector_signal = SIGNAL_TYPE_EDP; + + *link_out = link; + *aconnector_out = aconnector; + *fixture_out = fixture; +} + +/** + * dm_test_read_vbios_edid_non_embedded - Test non-embedded signal skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_non_embedded(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_missing_callback - Test missing callback skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_missing_callback(struct kunit *test) +{ + static const struct dc_vbios_funcs empty_funcs; + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->bios.funcs = &empty_funcs; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_callback_error - Test callback failure skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_callback_error(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->result = BP_RESULT_BADINPUT; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_missing_fake_edid - Test missing fake EDID skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_missing_fake_edid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->fake_edid = NULL; + fixture->fake_edid_size = 0; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_invalid_fake_edid - Test invalid fake EDID skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_invalid_fake_edid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_edid *dc_edid; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, false); + fixture->fake_edid = dc_edid->raw_edid; + fixture->fake_edid_size = EDID_LENGTH; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_valid - Test valid fake EDID updates display size + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_valid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + const struct drm_edid *edid; + struct dc_edid *dc_edid; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, true); + fixture->fake_edid = dc_edid->raw_edid; + fixture->fake_edid_size = EDID_LENGTH; + fixture->width_mm = 301; + fixture->height_mm = 201; + + edid = dm_helpers_read_vbios_hardcoded_edid(link, aconnector); + + KUNIT_ASSERT_NOT_NULL(test, edid); + KUNIT_EXPECT_EQ(test, aconnector->base.display_info.width_mm, 301U); + KUNIT_EXPECT_EQ(test, aconnector->base.display_info.height_mm, 201U); + drm_edid_free(edid); +} + /* Tests for dm_is_freesync_pcon_whitelist() */ /** @@ -581,6 +1281,33 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), KUNIT_CASE(dm_test_edid_extract_panel_id_zeros), + /* apply_edid_quirks */ + KUNIT_CASE(dm_test_apply_edid_quirks_dpcd_poweroff_delay), + KUNIT_CASE(dm_test_apply_edid_quirks_disable_fams), + KUNIT_CASE(dm_test_apply_edid_quirks_remove_sink_ext_caps), + KUNIT_CASE(dm_test_apply_edid_quirks_disable_colorimetry), + KUNIT_CASE(dm_test_apply_edid_quirks_skip_phy_ssc), + KUNIT_CASE(dm_test_apply_edid_quirks_unknown_noop), + /* dm_helpers_parse_edid_caps */ + KUNIT_CASE(dm_test_parse_edid_caps_null_edid), + KUNIT_CASE(dm_test_parse_edid_caps_null_caps), + KUNIT_CASE(dm_test_parse_edid_caps_valid), + KUNIT_CASE(dm_test_parse_edid_caps_bad_checksum), + KUNIT_CASE(dm_test_parse_edid_caps_hdmi_frl), + KUNIT_CASE(dm_test_parse_edid_caps_hdmi_frl_dsc), + KUNIT_CASE(dm_test_parse_edid_caps_cea_audio), + KUNIT_CASE(dm_test_parse_edid_caps_cea_no_speaker), + /* ACPI / VBIOS / local EDID readers */ + KUNIT_CASE(dm_test_probe_acpi_edid_no_companion), + KUNIT_CASE(dm_test_read_acpi_edid_debug_mask_disabled), + KUNIT_CASE(dm_test_read_acpi_edid_non_panel_connector), + KUNIT_CASE(dm_test_read_acpi_edid_force_off), + KUNIT_CASE(dm_test_read_vbios_edid_non_embedded), + KUNIT_CASE(dm_test_read_vbios_edid_missing_callback), + KUNIT_CASE(dm_test_read_vbios_edid_callback_error), + KUNIT_CASE(dm_test_read_vbios_edid_missing_fake_edid), + KUNIT_CASE(dm_test_read_vbios_edid_invalid_fake_edid), + KUNIT_CASE(dm_test_read_vbios_edid_valid), /* dm_is_freesync_pcon_whitelist */ KUNIT_CASE(dm_test_freesync_pcon_whitelist_all_known), KUNIT_CASE(dm_test_freesync_pcon_whitelist_not_in_list), From 7af4219735d90c6a935c74406fb33e0c4cac9ecc Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 033/155] drm/amd/display: Test DPCD AUX and Synaptics helpers [WHAT] Add KUnit coverage for DTN logging, DPCD read/write, fused IO and the Synaptics DSC workaround helpers execute_synaptics_rc_command, apply_synaptics_fifo_reset_wa, write_dsc_enable_synaptics_non_virtual_dpcd_mst and dm_helpers_dp_write_dsc_enable. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 11 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 13 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 485 ++++++++++++++++++ 3 files changed, 506 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 8f905ad8aaa1..169f83a3c901 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -719,8 +719,9 @@ bool dm_helpers_execute_fused_io( return amdgpu_dm_execute_fused_io(dev, link, commands, count, timeout_us); } +EXPORT_IF_KUNIT(dm_helpers_execute_fused_io); -static bool execute_synaptics_rc_command(struct drm_dp_aux *aux, +STATIC_IFN_KUNIT bool execute_synaptics_rc_command(struct drm_dp_aux *aux, bool is_write_cmd, unsigned char cmd, unsigned int length, @@ -792,8 +793,9 @@ static bool execute_synaptics_rc_command(struct drm_dp_aux *aux, DRM_ERROR("%s: write cmd ..., err = %d\n", __func__, ret); return false; } +EXPORT_IF_KUNIT(execute_synaptics_rc_command); -static void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) +STATIC_IFN_KUNIT void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) { unsigned char data[16] = {0}; @@ -857,11 +859,12 @@ static void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) drm_dbg_dp(aux->drm_dev, "Done\n"); } +EXPORT_IF_KUNIT(apply_synaptics_fifo_reset_wa); /* MST Dock */ static const uint8_t SYNAPTICS_DEVICE_ID[] = "SYNA"; -static uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( +STATIC_IFN_KUNIT uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( struct drm_dp_aux *aux, const struct dc_stream_state *stream, bool enable) @@ -897,6 +900,7 @@ static uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( return ret; } +EXPORT_IF_KUNIT(write_dsc_enable_synaptics_non_virtual_dpcd_mst); bool dm_helpers_dp_write_dsc_enable( struct dc_context *ctx, @@ -980,6 +984,7 @@ bool dm_helpers_dp_write_dsc_enable( return ret; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_dsc_enable); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) uint dm_helpers_get_dc_debug_mask(void) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index e5cbcd425847..343650b50707 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -28,12 +28,25 @@ uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +bool dm_helpers_dp_write_dsc_enable(struct dc_context *ctx, + const struct dc_stream_state *stream, + bool enable); uint dm_helpers_get_dc_debug_mask(void); void dm_helpers_set_dc_debug_mask(uint debug_mask); int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len); const struct drm_edid *dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector); const struct drm_edid *dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_connector *aconnector); +bool execute_synaptics_rc_command(struct drm_dp_aux *aux, + bool is_write_cmd, + unsigned char cmd, + unsigned int length, + unsigned int offset, + unsigned char *data); +void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux); +uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst(struct drm_dp_aux *aux, + const struct dc_stream_state *stream, + bool enable); #endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ #endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 3766d8d211e4..eed941a889e9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -1211,6 +1211,475 @@ static void dm_test_dp_write_dpcd_null_priv(struct kunit *test) dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); } +/* + * Stub AUX transfer that ACKs every transaction (zero-filling reads), so + * drm_dp_dpcd_read()/drm_dp_dpcd_write() report the full transfer size. + */ +static ssize_t dm_test_dpcd_ack_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) + memset(msg->buffer, 0, msg->size); + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +/* + * Wire a connector-backed link with a working AUX channel so the DPCD + * read/write helpers can complete a real transaction. + */ +static struct dc_link *dm_test_dpcd_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + + aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev; + aconnector->dm_dp_aux.aux.transfer = dm_test_dpcd_ack_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + + link->priv = aconnector; + + return link; +} + +/** + * dm_test_dp_read_dpcd_success - Test DPCD read returns true on ACKed transfer + * @test: The KUnit test context + */ +static void dm_test_dp_read_dpcd_success(struct kunit *test) +{ + struct dc_link *link = dm_test_dpcd_link(test); + uint8_t data = 0; + + KUNIT_EXPECT_TRUE(test, + dm_helpers_dp_read_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/** + * dm_test_dp_write_dpcd_success - Test DPCD write returns true on ACKed transfer + * @test: The KUnit test context + */ +static void dm_test_dp_write_dpcd_success(struct kunit *test) +{ + struct dc_link *link = dm_test_dpcd_link(test); + uint8_t data = 0; + + KUNIT_EXPECT_TRUE(test, + dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/* Tests for dm_helpers_execute_fused_io() */ + +/** + * dm_test_execute_fused_io_null_dmub_srv - Test fused IO fails without DMUB service + * @test: The KUnit test context + */ +static void dm_test_execute_fused_io_null_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + union dmub_rb_cmd *commands; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + mutex_init(&adev->dm.dpia_aux_lock); + spin_lock_init(&adev->dm.dmub_lock); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + commands = kunit_kzalloc(test, sizeof(*commands), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, commands); + + ctx->driver_context = adev; + link->ctx = ctx; + commands[0].fused_io.request.u.aux.ddc_line = 0; + + KUNIT_EXPECT_FALSE(test, dm_helpers_execute_fused_io(ctx, link, commands, 1, 1)); +} + +struct dm_test_synaptics_aux { + struct drm_dp_aux aux; + u32 fail_address; + u32 last_dpcd_write_address; + u8 rc_result; + u8 dpcd_read_value; + u8 dpcd_write_value; + u8 last_rc_data[16]; + u8 read_rc_data[16]; + u8 last_rc_command; + u8 rc_commands[32]; + u8 dsc_enable_values[8]; + u32 last_rc_offset; + u32 last_rc_length; + unsigned int rc_data_writes; + unsigned int rc_data_reads; + unsigned int rc_command_reads; + unsigned int rc_result_reads; + unsigned int rc_command_count; + unsigned int downspread_reads; + unsigned int downspread_writes; + unsigned int dsc_enable_writes; +}; + +static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct dm_test_synaptics_aux *fixture; + u8 request; + u8 *buffer; + size_t copy_size; + unsigned int index; + + fixture = container_of(aux, struct dm_test_synaptics_aux, aux); + request = msg->request & ~DP_AUX_I2C_MOT; + buffer = msg->buffer; + + if (fixture->fail_address == msg->address) + return -EIO; + + if (request == DP_AUX_NATIVE_WRITE) { + switch (msg->address) { + case DP_DOWNSPREAD_CTRL: + fixture->last_dpcd_write_address = msg->address; + if (msg->size) + fixture->dpcd_write_value = buffer[0]; + fixture->downspread_writes++; + break; + case SYNAPTICS_RC_DATA: + copy_size = min_t(size_t, msg->size, sizeof(fixture->last_rc_data)); + memset(fixture->last_rc_data, 0, sizeof(fixture->last_rc_data)); + memcpy(fixture->last_rc_data, buffer, copy_size); + fixture->rc_data_writes++; + break; + case SYNAPTICS_RC_OFFSET: + if (msg->size >= 4) + fixture->last_rc_offset = buffer[0] | buffer[1] << 8 | + buffer[2] << 16 | buffer[3] << 24; + break; + case SYNAPTICS_RC_LENGTH: + if (msg->size >= 2) + fixture->last_rc_length = buffer[0] | buffer[1] << 8; + break; + case SYNAPTICS_RC_COMMAND: + fixture->last_rc_command = buffer[0]; + if (fixture->rc_command_count < ARRAY_SIZE(fixture->rc_commands)) { + fixture->rc_commands[fixture->rc_command_count] = buffer[0] & 0x7f; + fixture->rc_command_count++; + } + break; + case DP_DSC_ENABLE: + if (fixture->dsc_enable_writes < ARRAY_SIZE(fixture->dsc_enable_values)) { + fixture->dsc_enable_values[fixture->dsc_enable_writes] = buffer[0]; + fixture->dsc_enable_writes++; + } + break; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + if (request == DP_AUX_NATIVE_READ) { + memset(buffer, 0, msg->size); + switch (msg->address) { + case DP_DOWNSPREAD_CTRL: + if (msg->size) + buffer[0] = fixture->dpcd_read_value; + fixture->downspread_reads++; + break; + case SYNAPTICS_RC_COMMAND: + if (msg->size) + buffer[0] = fixture->last_rc_command & 0x7f; + fixture->rc_command_reads++; + break; + case SYNAPTICS_RC_RESULT: + if (msg->size) + buffer[0] = fixture->rc_result; + fixture->rc_result_reads++; + break; + case SYNAPTICS_RC_DATA: + copy_size = min_t(size_t, msg->size, sizeof(fixture->read_rc_data)); + for (index = 0; index < copy_size; index++) + buffer[index] = fixture->read_rc_data[index]; + fixture->rc_data_reads++; + break; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux_with_dev(struct kunit *test, + struct drm_device *drm_dev) +{ + struct dm_test_synaptics_aux *fixture; + unsigned int index; + + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + + for (index = 0; index < ARRAY_SIZE(fixture->read_rc_data); index++) + fixture->read_rc_data[index] = 0x03; + + fixture->rc_result = 0; + fixture->aux.drm_dev = drm_dev; + fixture->aux.transfer = dm_test_synaptics_aux_transfer; + drm_dp_aux_init(&fixture->aux); + + return fixture; +} + +static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + return dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev); +} + +static void dm_test_expect_synaptics_commands(struct kunit *test, + struct dm_test_synaptics_aux *fixture, + const u8 *expected_commands, + unsigned int expected_count) +{ + unsigned int index; + + KUNIT_ASSERT_EQ(test, fixture->rc_command_count, expected_count); + + for (index = 0; index < expected_count; index++) + KUNIT_EXPECT_EQ(test, fixture->rc_commands[index], expected_commands[index]); +} + +static void dm_test_setup_synaptics_stream(struct dc_stream_state *stream, + struct dc_link *link) +{ + stream->link = link; + stream->signal = SIGNAL_TYPE_DISPLAY_PORT_MST; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; + memcpy(link->dpcd_caps.branch_dev_name, "SYNA", 4); +} + +/** + * dm_test_execute_synaptics_rc_command_write_success - Test RC write success + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_write_success(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data[5] = { 'P', 'R', 'I', 'U', 'S' }; + + fixture = dm_test_alloc_synaptics_aux(test); + + KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, true, + 0x01, sizeof(data), 0x123456, + data)); + KUNIT_EXPECT_EQ(test, memcmp(fixture->last_rc_data, data, sizeof(data)), 0); + KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0x123456U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_length, (u32)sizeof(data)); + KUNIT_EXPECT_EQ(test, fixture->last_rc_command, (u8)0x81); + KUNIT_EXPECT_EQ(test, fixture->rc_command_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, 1U); +} + +/** + * dm_test_execute_synaptics_rc_command_read_success - Test RC read success + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_read_success(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data[4] = { 0 }; + u8 expected[4] = { 0xa5, 0x5a, 0xc3, 0x3c }; + + fixture = dm_test_alloc_synaptics_aux(test); + memcpy(fixture->read_rc_data, expected, sizeof(expected)); + + KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, false, + 0x31, sizeof(data), 0x220998, + data)); + KUNIT_EXPECT_EQ(test, memcmp(data, expected, sizeof(expected)), 0); + KUNIT_EXPECT_EQ(test, fixture->rc_data_writes, 0U); + KUNIT_EXPECT_EQ(test, fixture->rc_data_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0x220998U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_length, (u32)sizeof(data)); +} + +/** + * dm_test_execute_synaptics_rc_command_write_fail - Test RC write failure + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_write_fail(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data = 0; + + fixture = dm_test_alloc_synaptics_aux(test); + fixture->fail_address = SYNAPTICS_RC_LENGTH; + + KUNIT_EXPECT_FALSE(test, execute_synaptics_rc_command(&fixture->aux, true, + 0x01, sizeof(data), 0, &data)); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 0U); +} + +/** + * dm_test_apply_synaptics_fifo_reset_wa_full - Test full FIFO reset sequence + * @test: The KUnit test context + */ +static void dm_test_apply_synaptics_fifo_reset_wa_full(struct kunit *test) +{ + static const u8 expected_commands[] = { + 0x01, 0x31, 0x21, 0x31, 0x21, 0x31, 0x21, + 0x31, 0x21, 0x31, 0x31, 0x21, 0x02, + }; + struct dm_test_synaptics_aux *fixture; + + fixture = dm_test_alloc_synaptics_aux(test); + + apply_synaptics_fifo_reset_wa(&fixture->aux); + + dm_test_expect_synaptics_commands(test, fixture, expected_commands, + ARRAY_SIZE(expected_commands)); + KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, (unsigned int)ARRAY_SIZE(expected_commands)); +} + +/** + * dm_test_apply_synaptics_fifo_reset_wa_first_fail - Test FIFO reset early exit + * @test: The KUnit test context + */ +static void dm_test_apply_synaptics_fifo_reset_wa_first_fail(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + + fixture = dm_test_alloc_synaptics_aux(test); + fixture->rc_result = 0xff; + + apply_synaptics_fifo_reset_wa(&fixture->aux); + + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 1U); + KUNIT_EXPECT_EQ(test, fixture->rc_commands[0], (u8)0x01); +} + +static void dm_test_write_dsc_enable_synaptics(struct kunit *test, + bool link_active, + bool enable, + bool synaptics_branch, + unsigned int expected_dsc_writes, + unsigned int expected_rc_commands) +{ + struct dm_test_synaptics_aux *fixture; + struct dc_stream_state *stream; + struct dc_link *link; + u8 ret; + + fixture = dm_test_alloc_synaptics_aux(test); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = dm_kunit_alloc_link(test); + + dm_test_setup_synaptics_stream(stream, link); + link->link_status.link_active = link_active; + if (!synaptics_branch) + memcpy(link->dpcd_caps.branch_dev_name, "ABCD", 4); + + ret = write_dsc_enable_synaptics_non_virtual_dpcd_mst(&fixture->aux, stream, enable); + + KUNIT_EXPECT_EQ(test, ret, expected_dsc_writes ? 1 : 0); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, expected_dsc_writes); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, expected_rc_commands); + if (expected_dsc_writes) + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], enable ? 1 : 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_inactive - Test enable plus FIFO reset + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_inactive(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, true, true, 1, 13); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_active - Test enable skips FIFO reset + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_active(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, true, true, true, 1, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_disable_inactive - Test inactive disable writes DPCD + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_disable_inactive(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, false, true, 1, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_disable_active - Test active disable skips DPCD + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_disable_active(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, true, false, true, 0, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_non_synaptics - Test non-Synaptics enable + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_non_synaptics(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, true, false, 1, 0); +} + +/** + * dm_test_dp_write_dsc_enable_routes_synaptics - Test public DSC helper workaround route + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_routes_synaptics(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct dc_link *link; + + fixture = dm_test_alloc_synaptics_aux(test); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = dm_kunit_alloc_link(test); + + dm_test_setup_synaptics_stream(stream, link); + stream->dm_stream_context = aconnector; + aconnector->dc_link = link; + aconnector->dsc_aux = &fixture->aux; + + KUNIT_EXPECT_TRUE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], (u8)1); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 13U); +} + /* Tests for dm_helpers_dp_mst_start_top_mgr() / dm_helpers_dp_mst_stop_top_mgr() */ /** @@ -1341,6 +1810,22 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* dm_helpers_dp_read_dpcd / dm_helpers_dp_write_dpcd */ KUNIT_CASE(dm_test_dp_read_dpcd_null_priv), KUNIT_CASE(dm_test_dp_write_dpcd_null_priv), + KUNIT_CASE(dm_test_dp_read_dpcd_success), + KUNIT_CASE(dm_test_dp_write_dpcd_success), + /* dm_helpers_execute_fused_io */ + KUNIT_CASE(dm_test_execute_fused_io_null_dmub_srv), + /* Synaptics RC/FIFO/DSC helpers */ + KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_success), + KUNIT_CASE(dm_test_execute_synaptics_rc_command_read_success), + KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_fail), + KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_full), + KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_first_fail), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_inactive), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_active), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_disable_inactive), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_disable_active), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_non_synaptics), + KUNIT_CASE(dm_test_dp_write_dsc_enable_routes_synaptics), /* dm_helpers_dp_mst_start_top_mgr / dm_helpers_dp_mst_stop_top_mgr */ KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv), KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv), From 0984a22c9ffc241bf8190d64bf3d26d6f098e73c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 034/155] drm/amd/display: Test MST start/stop and panel settings [WHAT] Add KUnit coverage for dm_helpers_dp_mst_start_top_mgr and dm_helpers_dp_mst_stop_top_mgr, dm_helpers_dp_write_hblank_reduction, get_dsc_max_slices, dm_helpers_init_panel_settings, dm_helpers_override_panel_settings and fill_dc_mst_payload_table_from_drm. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 8 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 295 ++++++++++++++++++ 3 files changed, 306 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 169f83a3c901..f7ba7eab69e2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -240,7 +240,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( } EXPORT_IF_KUNIT(dm_helpers_parse_edid_caps); -static void +STATIC_IFN_KUNIT void fill_dc_mst_payload_table_from_drm(struct dc_link *link, bool enable, struct drm_dp_mst_atomic_payload *target_payload, @@ -290,6 +290,7 @@ fill_dc_mst_payload_table_from_drm(struct dc_link *link, /* Overwrite the old table */ *table = new_table; } +EXPORT_IF_KUNIT(fill_dc_mst_payload_table_from_drm); void dm_helpers_dp_update_branch_info( struct dc_context *ctx, @@ -1149,7 +1150,7 @@ STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_pe } EXPORT_IF_KUNIT(get_max_frl_rate); -static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) +STATIC_IFN_KUNIT uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) { uint8_t dsc_max_slices; @@ -1172,6 +1173,7 @@ static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) return dsc_max_slices; } +EXPORT_IF_KUNIT(get_dsc_max_slices); void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hdmi, struct dc_edid_caps *edid_caps) { @@ -1354,6 +1356,7 @@ void dm_helpers_init_panel_settings( panel_config->dsc.disable_dsc_edp = false; panel_config->dsc.force_dsc_edp_policy = 0; } +EXPORT_IF_KUNIT(dm_helpers_init_panel_settings); void dm_helpers_override_panel_settings( struct dc_context *ctx, @@ -1371,6 +1374,7 @@ void dm_helpers_override_panel_settings( link->panel_config.psr.disallow_replay = true; } } +EXPORT_IF_KUNIT(dm_helpers_override_panel_settings); void *dm_helpers_allocate_gpu_mem( struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index 343650b50707..e256e1e12422 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -25,9 +25,14 @@ u32 edid_extract_panel_id(struct edid *edid); void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps); uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); +uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +void fill_dc_mst_payload_table_from_drm(struct dc_link *link, + bool enable, + struct drm_dp_mst_atomic_payload *target_payload, + struct dc_dp_mst_stream_allocation_table *table); bool dm_helpers_dp_write_dsc_enable(struct dc_context *ctx, const struct dc_stream_state *stream, bool enable); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index eed941a889e9..599de8240bee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -1746,6 +1746,282 @@ static void dm_test_dp_write_hblank_reduction_false(struct kunit *test) KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_hblank_reduction(NULL, NULL)); } +/* Tests for get_dsc_max_slices() */ + +/** + * dm_test_get_dsc_max_slices_1_340 - Test 1 slice at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_1_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(1, 340), 1); +} + +/** + * dm_test_get_dsc_max_slices_2_340 - Test 2 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_2_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(2, 340), 2); +} + +/** + * dm_test_get_dsc_max_slices_4_340 - Test 4 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_4_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(4, 340), 3); +} + +/** + * dm_test_get_dsc_max_slices_8_340 - Test 8 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_8_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(8, 340), 4); +} + +/** + * dm_test_get_dsc_max_slices_8_400 - Test 8 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_8_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(8, 400), 5); +} + +/** + * dm_test_get_dsc_max_slices_12_400 - Test 12 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_12_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(12, 400), 6); +} + +/** + * dm_test_get_dsc_max_slices_16_400 - Test 16 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_16_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(16, 400), 7); +} + +/** + * dm_test_get_dsc_max_slices_unknown - Test unknown combination returns 0 + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(3, 340), 0); + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(1, 400), 0); +} + +/* Tests for dm_helpers_init_panel_settings() */ + +/** + * dm_test_init_panel_settings_pps - Test panel power sequence settings init + * @test: The KUnit test context + */ +static void dm_test_init_panel_settings_pps(struct kunit *test) +{ + struct dc_panel_config panel_config = {0}; + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.panel_patch.extra_t3_ms = 100; + sink->edid_caps.panel_patch.extra_t7_ms = 200; + sink->edid_caps.panel_patch.extra_delay_backlight_off = 50; + sink->edid_caps.panel_patch.extra_t12_ms = 300; + + dm_helpers_init_panel_settings(NULL, &panel_config, sink); + + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t3_ms, 100U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t7_ms, 200U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_delay_backlight_off, 50U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_post_t7_ms, 0U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_pre_t11_ms, 0U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t12_ms, 300U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_post_OUI_ms, 0U); +} + +/** + * dm_test_init_panel_settings_dsc - Test DSC defaults in panel settings init + * @test: The KUnit test context + */ +static void dm_test_init_panel_settings_dsc(struct kunit *test) +{ + struct dc_panel_config panel_config; + struct dc_sink *sink; + + memset(&panel_config, 0xFF, sizeof(panel_config)); + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + dm_helpers_init_panel_settings(NULL, &panel_config, sink); + + KUNIT_EXPECT_FALSE(test, panel_config.dsc.disable_dsc_edp); + KUNIT_EXPECT_EQ(test, panel_config.dsc.force_dsc_edp_policy, 0U); +} + +/* Tests for dm_helpers_override_panel_settings() */ + +/** + * dm_test_override_panel_settings_debug_mask_disables_dsc - Test DSC mask + * @test: The KUnit test context + */ +static void dm_test_override_panel_settings_debug_mask_disables_dsc(struct kunit *test) +{ + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + uint old_debug_mask; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + link = dm_kunit_alloc_link(test); + ctx->dc = dc; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + old_debug_mask = dm_helpers_get_dc_debug_mask(); + dm_helpers_set_dc_debug_mask(old_debug_mask | DC_DISABLE_DSC); + dm_helpers_override_panel_settings(ctx, link); + dm_helpers_set_dc_debug_mask(old_debug_mask); + + KUNIT_EXPECT_TRUE(test, link->panel_config.dsc.disable_dsc_edp); +} + +/** + * dm_test_override_panel_settings_second_edp_disables_psr - Test eDP index 1 + * @test: The KUnit test context + */ +static void dm_test_override_panel_settings_second_edp_disables_psr(struct kunit *test) +{ + struct dc_context *ctx; + struct dc_link *first_link; + struct dc_link *second_link; + struct dc *dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + first_link = dm_kunit_alloc_link(test); + second_link = dm_kunit_alloc_link(test); + + ctx->dc = dc; + dc->link_count = 2; + dc->links[0] = first_link; + dc->links[1] = second_link; + first_link->connector_signal = SIGNAL_TYPE_EDP; + second_link->connector_signal = SIGNAL_TYPE_EDP; + + dm_helpers_override_panel_settings(ctx, second_link); + + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disable_psr); + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disallow_psrsu); + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disallow_replay); +} + +/* Tests for fill_dc_mst_payload_table_from_drm() */ + +/** + * dm_test_fill_mst_payload_table_enable - Test payload table fill on enable + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_enable(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Pre-existing allocation in the link table */ + link->mst_stream_alloc_table.stream_count = 1; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 4; + + /* New payload to add */ + payload.vcpi = 2; + payload.time_slots = 8; + + fill_dc_mst_payload_table_from_drm(link, true, &payload, &table); + + /* Should contain both the pre-existing and new allocation */ + KUNIT_EXPECT_EQ(test, table.stream_count, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 4); + KUNIT_EXPECT_EQ(test, table.stream_allocations[1].vcp_id, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[1].slot_count, 8); +} + +/** + * dm_test_fill_mst_payload_table_disable - Test payload table fill on disable + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_disable(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Two existing allocations in the link table */ + link->mst_stream_alloc_table.stream_count = 2; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 4; + link->mst_stream_alloc_table.stream_allocations[1].vcp_id = 2; + link->mst_stream_alloc_table.stream_allocations[1].slot_count = 8; + + /* Remove vcp_id 1 */ + payload.vcpi = 1; + payload.time_slots = 4; + + fill_dc_mst_payload_table_from_drm(link, false, &payload, &table); + + /* Only vcp_id 2 should remain */ + KUNIT_EXPECT_EQ(test, table.stream_count, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 8); +} + +/** + * dm_test_fill_mst_payload_table_empty - Test payload table fill when empty + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_empty(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Enable on an empty table */ + payload.vcpi = 5; + payload.time_slots = 12; + + fill_dc_mst_payload_table_from_drm(link, true, &payload, &table); + + KUNIT_EXPECT_EQ(test, table.stream_count, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 5); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 12); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -1832,6 +2108,25 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_mst_start_top_mgr_boot), /* dm_helpers_dp_write_hblank_reduction */ KUNIT_CASE(dm_test_dp_write_hblank_reduction_false), + /* get_dsc_max_slices */ + KUNIT_CASE(dm_test_get_dsc_max_slices_1_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_2_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_4_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_8_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_8_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_12_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_16_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_unknown), + /* dm_helpers_init_panel_settings */ + KUNIT_CASE(dm_test_init_panel_settings_pps), + KUNIT_CASE(dm_test_init_panel_settings_dsc), + /* dm_helpers_override_panel_settings */ + KUNIT_CASE(dm_test_override_panel_settings_debug_mask_disables_dsc), + KUNIT_CASE(dm_test_override_panel_settings_second_edp_disables_psr), + /* fill_dc_mst_payload_table_from_drm */ + KUNIT_CASE(dm_test_fill_mst_payload_table_enable), + KUNIT_CASE(dm_test_fill_mst_payload_table_disable), + KUNIT_CASE(dm_test_fill_mst_payload_table_empty), {} }; From f5e5e134dcf010d2ae45a579fe7470bfa357a2c2 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 035/155] drm/amd/display: Test I2C, stubs and MST early returns [WHAT] Add KUnit coverage for dm_helpers_submit_i2c, dm_helper_dmub_aux_transfer_sync, the empty stub helpers, the MST null-connector early returns, dm_helpers_dmub_outbox_interrupt_control, dm_helpers_mst_enable_stream_features and dm_helpers_enable_periodic_detection. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 18 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 541 ++++++++++++++++++ 2 files changed, 559 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index f7ba7eab69e2..ff23aee54c16 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -296,6 +296,7 @@ void dm_helpers_dp_update_branch_info( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_update_branch_info); static void dm_helpers_construct_old_payload( struct drm_dp_mst_topology_mgr *mgr, @@ -380,6 +381,7 @@ bool dm_helpers_dp_mst_write_payload_allocation_table( return true; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_write_payload_allocation_table); /* * poll pending down reply @@ -388,6 +390,7 @@ void dm_helpers_dp_mst_poll_pending_down_reply( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_mst_poll_pending_down_reply); /* * Clear payload allocation table before enable MST DP link. @@ -396,6 +399,7 @@ void dm_helpers_dp_mst_clear_payload_allocation_table( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_mst_clear_payload_allocation_table); /* * Polls for ACT (allocation change trigger) handled and sends @@ -426,6 +430,7 @@ enum act_return_status dm_helpers_dp_mst_poll_for_allocation_change_trigger( return ACT_SUCCESS; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_poll_for_allocation_change_trigger); void dm_helpers_dp_mst_send_payload_allocation( struct dc_context *ctx, @@ -460,6 +465,7 @@ void dm_helpers_dp_mst_send_payload_allocation( clr_flag, false); } } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_send_payload_allocation); void dm_helpers_dp_mst_update_mst_mgr_for_deallocation( struct dc_context *ctx, @@ -488,6 +494,7 @@ void dm_helpers_dp_mst_update_mst_mgr_for_deallocation( amdgpu_dm_set_mst_status(&aconnector->mst_status, set_flag, true); amdgpu_dm_set_mst_status(&aconnector->mst_status, clr_flag, false); } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_update_mst_mgr_for_deallocation); void dm_dtn_log_begin(struct dc_context *ctx, struct dc_log_buffer_ctx *log_ctx) @@ -707,6 +714,7 @@ bool dm_helpers_submit_i2c( return result; } +EXPORT_IF_KUNIT(dm_helpers_submit_i2c); bool dm_helpers_execute_fused_io( struct dc_context *ctx, @@ -1023,6 +1031,7 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link) mutex_unlock(&aconnector->dm_dp_aux.aux.hw_mutex); return dp_sink_present; } +EXPORT_IF_KUNIT(dm_helpers_is_dp_sink_present); STATIC_IFN_KUNIT int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) @@ -1312,6 +1321,7 @@ int dm_helper_dmub_aux_transfer_sync( return amdgpu_dm_process_dmub_aux_transfer_sync(ctx, link->link_index, payload, operation_result); } +EXPORT_IF_KUNIT(dm_helper_dmub_aux_transfer_sync); int dm_helpers_dmub_set_config_sync(struct dc_context *ctx, const struct dc_link *link, @@ -1326,18 +1336,21 @@ void dm_set_dcn_clocks(struct dc_context *ctx, struct dc_clocks *clks) { /* TODO: something */ } +EXPORT_IF_KUNIT(dm_set_dcn_clocks); void dm_helpers_dmu_timeout(struct dc_context *ctx) { // TODO: //amdgpu_device_gpu_recover(dc_context->driver-context, NULL); } +EXPORT_IF_KUNIT(dm_helpers_dmu_timeout); void dm_helpers_smu_timeout(struct dc_context *ctx, unsigned int msg_id, unsigned int param, unsigned int timeout_us) { // TODO: //amdgpu_device_gpu_recover(dc_context->driver-context, NULL); } +EXPORT_IF_KUNIT(dm_helpers_smu_timeout); void dm_helpers_init_panel_settings( struct dc_context *ctx, @@ -1410,6 +1423,7 @@ bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enabl enable ? "en" : "dis", ret); return ret; } +EXPORT_IF_KUNIT(dm_helpers_dmub_outbox_interrupt_control); void dm_helpers_mst_enable_stream_features(const struct dc_stream_state *stream) { @@ -1435,6 +1449,7 @@ void dm_helpers_mst_enable_stream_features(const struct dc_stream_state *stream) &new_downspread.raw, sizeof(new_downspread)); } +EXPORT_IF_KUNIT(dm_helpers_mst_enable_stream_features); bool dm_helpers_dp_handle_test_pattern_request( struct dc_context *ctx, @@ -1578,6 +1593,7 @@ void dm_set_phyd32clk(struct dc_context *ctx, int freq_khz) { // TODO } +EXPORT_IF_KUNIT(dm_set_phyd32clk); void dm_helpers_enable_periodic_detection(struct dc_context *ctx, bool enable) { @@ -1589,6 +1605,7 @@ void dm_helpers_enable_periodic_detection(struct dc_context *ctx, bool enable) schedule_work(&adev->dm.idle_workqueue->work); } } +EXPORT_IF_KUNIT(dm_helpers_enable_periodic_detection); void dm_helpers_dp_mst_update_branch_bandwidth( struct dc_context *ctx, @@ -1596,6 +1613,7 @@ void dm_helpers_dp_mst_update_branch_bandwidth( { // TODO } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_update_branch_bandwidth); STATIC_IFN_KUNIT const uint32_t dm_freesync_pcon_whitelist[] = { DP_BRANCH_DEVICE_ID_0060AD, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 599de8240bee..29feb8c3940b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -1421,6 +1421,14 @@ static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux, return msg->size; } +static struct dm_test_synaptics_aux *dm_test_current_aux_recorder; + +static ssize_t dm_test_current_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + return dm_test_synaptics_aux_transfer(&dm_test_current_aux_recorder->aux, msg); +} + static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux_with_dev(struct kunit *test, struct drm_device *drm_dev) { @@ -2022,6 +2030,507 @@ static void dm_test_fill_mst_payload_table_empty(struct kunit *test) KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 12); } +/* Tests for dm_helpers_submit_i2c() */ + +/** + * dm_test_submit_i2c_null_priv - Test i2c submit returns false without connector + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_null_priv(struct kunit *test) +{ + struct dc_link *link; + struct i2c_command cmd = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_submit_i2c(NULL, link, &cmd)); +} + +struct dm_test_i2c_adapter { + struct i2c_adapter base; + struct kunit *test; + struct i2c_payload *expected_payloads; + int expected_num; + int ret; + int calls; +}; + +static void dm_test_i2c_lock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + rt_mutex_lock(&adapter->bus_lock); +} + +static int dm_test_i2c_trylock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + return rt_mutex_trylock(&adapter->bus_lock); +} + +static void dm_test_i2c_unlock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + rt_mutex_unlock(&adapter->bus_lock); +} + +static const struct i2c_lock_operations dm_test_i2c_lock_ops = { + .lock_bus = dm_test_i2c_lock_bus, + .trylock_bus = dm_test_i2c_trylock_bus, + .unlock_bus = dm_test_i2c_unlock_bus, +}; + +static int dm_test_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct dm_test_i2c_adapter *fake; + int i; + + fake = container_of(adapter, struct dm_test_i2c_adapter, base); + fake->calls++; + + KUNIT_EXPECT_EQ(fake->test, num, fake->expected_num); + + for (i = 0; i < num; i++) { + KUNIT_EXPECT_EQ(fake->test, msgs[i].flags, + fake->expected_payloads[i].write ? 0 : I2C_M_RD); + KUNIT_EXPECT_EQ(fake->test, msgs[i].addr, + (u16)fake->expected_payloads[i].address); + KUNIT_EXPECT_EQ(fake->test, msgs[i].len, + (u16)fake->expected_payloads[i].length); + KUNIT_EXPECT_PTR_EQ(fake->test, msgs[i].buf, + fake->expected_payloads[i].data); + } + + return fake->ret; +} + +static const struct i2c_algorithm dm_test_i2c_algorithm = { + .master_xfer = dm_test_i2c_master_xfer, +}; + +static void dm_test_submit_i2c_transfer(struct kunit *test, + bool full_transfer) +{ + struct amdgpu_dm_connector *aconnector; + struct dm_test_i2c_adapter *fake; + struct dc_link *link; + u8 write_data[2] = { 0x12, 0x34 }; + u8 read_data[3] = { 0 }; + struct i2c_payload payloads[] = { + { true, 0x50, sizeof(write_data), write_data }, + { false, 0x51, sizeof(read_data), read_data }, + }; + struct i2c_command cmd = { + .payloads = payloads, + .number_of_payloads = ARRAY_SIZE(payloads), + }; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fake); + + fake->test = test; + fake->expected_payloads = payloads; + fake->expected_num = ARRAY_SIZE(payloads); + fake->ret = full_transfer ? ARRAY_SIZE(payloads) : 1; + fake->base.algo = &dm_test_i2c_algorithm; + fake->base.lock_ops = &dm_test_i2c_lock_ops; + rt_mutex_init(&fake->base.bus_lock); + rt_mutex_init(&fake->base.mux_lock); + + aconnector->i2c = (struct amdgpu_i2c_adapter *)fake; + link->priv = aconnector; + + KUNIT_EXPECT_EQ(test, dm_helpers_submit_i2c(NULL, link, &cmd), full_transfer); + KUNIT_EXPECT_EQ(test, fake->calls, 1); +} + +/** + * dm_test_submit_i2c_success - Test payloads are mapped to i2c_msg array + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_success(struct kunit *test) +{ + dm_test_submit_i2c_transfer(test, true); +} + +/** + * dm_test_submit_i2c_partial_transfer - Test short i2c transfer returns false + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_partial_transfer(struct kunit *test) +{ + dm_test_submit_i2c_transfer(test, false); +} + +/* Tests for dm_helper_dmub_aux_transfer_sync() */ + +/** + * dm_test_dmub_aux_transfer_sync_hpd_discon - Test aux transfer with HPD disconnected + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_transfer_sync_hpd_discon(struct kunit *test) +{ + struct dc_link *link; + struct dc_context *ctx; + struct aux_payload payload = {0}; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + link->hpd_status = false; + + ret = dm_helper_dmub_aux_transfer_sync(ctx, link, &payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, (int)result, (int)AUX_RET_ERROR_HPD_DISCON); +} + +/* Tests for empty stub functions (must not crash) */ + +/** + * dm_test_dp_update_branch_info_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_dp_update_branch_info_no_crash(struct kunit *test) +{ + dm_helpers_dp_update_branch_info(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_poll_pending_down_reply_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_poll_pending_down_reply_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_poll_pending_down_reply(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_clear_payload_alloc_table_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_clear_payload_alloc_table_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_clear_payload_allocation_table(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_set_dcn_clocks_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_set_dcn_clocks_no_crash(struct kunit *test) +{ + dm_set_dcn_clocks(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_dmu_timeout_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_dmu_timeout_no_crash(struct kunit *test) +{ + dm_helpers_dmu_timeout(NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_smu_timeout_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_smu_timeout_no_crash(struct kunit *test) +{ + dm_helpers_smu_timeout(NULL, 0, 0, 0); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_set_phyd32clk_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_set_phyd32clk_no_crash(struct kunit *test) +{ + dm_set_phyd32clk(NULL, 0); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_update_branch_bandwidth_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_update_branch_bandwidth_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_update_branch_bandwidth(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/* Tests for MST functions null-connector early returns */ + +/** + * dm_test_mst_write_payload_alloc_table_null_ctx - Test null connector returns false + * @test: The KUnit test context + */ +static void dm_test_mst_write_payload_alloc_table_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* dm_stream_context is NULL → aconnector is NULL → return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_mst_write_payload_allocation_table(NULL, stream, NULL, true)); +} + +/** + * dm_test_mst_poll_for_act_null_ctx - Test null connector returns ACT_FAILED + * @test: The KUnit test context + */ +static void dm_test_mst_poll_for_act_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/** + * dm_test_mst_send_payload_alloc_null_ctx - Test null connector does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_send_payload_alloc_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* Should early-return without crash */ + dm_helpers_dp_mst_send_payload_allocation(NULL, stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_update_mgr_dealloc_null_ctx - Test null connector does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_update_mgr_dealloc_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + dm_helpers_dp_mst_update_mst_mgr_for_deallocation(NULL, stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_is_dp_sink_present_null_priv - Test null connector returns true + * @test: The KUnit test context + */ +static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* NULL priv → DRM_ERROR + return true */ + KUNIT_EXPECT_TRUE(test, dm_helpers_is_dp_sink_present(link)); +} + +/* Tests for dm_helpers_dmub_outbox_interrupt_control() */ + +/** + * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc + * @test: The KUnit test context + * + * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * helper returns false without touching real interrupt hardware. + */ +static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) +{ + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + /* ctx->dc is NULL → dc_interrupt_set returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); + KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); +} + +/* Tests for dm_helpers_mst_enable_stream_features() */ + +/** + * dm_test_mst_enable_stream_features_aux_disabled - Test early return when aux disabled + * @test: The KUnit test context + */ +static void dm_test_mst_enable_stream_features_aux_disabled(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + link->aux_access_disabled = true; + + /* aux_access_disabled → early return without DPCD access */ + dm_helpers_mst_enable_stream_features(stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_enable_stream_features_writes_downspread - Test MSA ignore write + * @test: The KUnit test context + */ +static void dm_test_mst_enable_stream_features_writes_downspread(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + fixture = dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + link = dm_kunit_alloc_link(test); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + dm_test_current_aux_recorder = fixture; + aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev; + aconnector->dm_dp_aux.aux.transfer = dm_test_current_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + link->priv = aconnector; + stream->link = link; + stream->ignore_msa_timing_param = true; + + dm_helpers_mst_enable_stream_features(stream); + + KUNIT_EXPECT_EQ(test, fixture->downspread_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->downspread_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->last_dpcd_write_address, + DP_DOWNSPREAD_CTRL); + KUNIT_EXPECT_EQ(test, fixture->dpcd_write_value, (u8)BIT(7)); +} + +/* Tests for dm_helpers_enable_periodic_detection() */ + +struct dm_test_idle_work { + struct idle_workqueue base; + bool ran; +}; + +static void dm_test_idle_work_func(struct work_struct *work) +{ + struct dm_test_idle_work *idle_work; + + idle_work = container_of(work, struct dm_test_idle_work, base.work); + idle_work->ran = true; +} + +/** + * dm_test_enable_periodic_detection_no_workqueue - Test no-op without idle workqueue + * @test: The KUnit test context + */ +static void dm_test_enable_periodic_detection_no_workqueue(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + + /* adev->dm.idle_workqueue is NULL → no-op, no crash */ + dm_helpers_enable_periodic_detection(ctx, true); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_enable_periodic_detection_updates_enable - Test idle work enable flag + * @test: The KUnit test context + * + * Keeps idle_workqueue->running set so the helper only updates the enable flag + * and does not queue the idle worker. + */ +static void dm_test_enable_periodic_detection_updates_enable(struct kunit *test) +{ + struct idle_workqueue *idle_work; + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, idle_work); + + ctx->driver_context = adev; + adev->dm.idle_workqueue = idle_work; + idle_work->running = true; + + dm_helpers_enable_periodic_detection(ctx, true); + KUNIT_EXPECT_TRUE(test, idle_work->enable); + + dm_helpers_enable_periodic_detection(ctx, false); + KUNIT_EXPECT_FALSE(test, idle_work->enable); +} + +/** + * dm_test_enable_periodic_detection_schedules_work - Test headless schedule path + * @test: The KUnit test context + */ +static void dm_test_enable_periodic_detection_schedules_work(struct kunit *test) +{ + struct dm_test_idle_work *idle_work; + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, idle_work); + + ctx->driver_context = adev; + adev->dm.ddev = &adev->ddev; + adev->dm.idle_workqueue = &idle_work->base; + INIT_WORK(&idle_work->base.work, dm_test_idle_work_func); + + dm_helpers_enable_periodic_detection(ctx, true); + flush_work(&idle_work->base.work); + + KUNIT_EXPECT_TRUE(test, idle_work->base.enable); + KUNIT_EXPECT_TRUE(test, idle_work->ran); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -2127,6 +2636,38 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_fill_mst_payload_table_enable), KUNIT_CASE(dm_test_fill_mst_payload_table_disable), KUNIT_CASE(dm_test_fill_mst_payload_table_empty), + /* dm_helpers_submit_i2c */ + KUNIT_CASE(dm_test_submit_i2c_null_priv), + KUNIT_CASE(dm_test_submit_i2c_success), + KUNIT_CASE(dm_test_submit_i2c_partial_transfer), + /* dm_helper_dmub_aux_transfer_sync */ + KUNIT_CASE(dm_test_dmub_aux_transfer_sync_hpd_discon), + /* Empty stub functions */ + KUNIT_CASE(dm_test_dp_update_branch_info_no_crash), + KUNIT_CASE(dm_test_mst_poll_pending_down_reply_no_crash), + KUNIT_CASE(dm_test_mst_clear_payload_alloc_table_no_crash), + KUNIT_CASE(dm_test_set_dcn_clocks_no_crash), + KUNIT_CASE(dm_test_dmu_timeout_no_crash), + KUNIT_CASE(dm_test_smu_timeout_no_crash), + KUNIT_CASE(dm_test_set_phyd32clk_no_crash), + KUNIT_CASE(dm_test_mst_update_branch_bandwidth_no_crash), + /* MST null-connector early returns */ + KUNIT_CASE(dm_test_mst_write_payload_alloc_table_null_ctx), + KUNIT_CASE(dm_test_mst_poll_for_act_null_ctx), + /* dm_helpers_dp_mst_poll_for_allocation_change_trigger success/fail */ + KUNIT_CASE(dm_test_mst_send_payload_alloc_null_ctx), + KUNIT_CASE(dm_test_mst_update_mgr_dealloc_null_ctx), + /* dm_helpers_is_dp_sink_present */ + KUNIT_CASE(dm_test_is_dp_sink_present_null_priv), + /* dm_helpers_dmub_outbox_interrupt_control */ + KUNIT_CASE(dm_test_dmub_outbox_interrupt_control_null_dc), + /* dm_helpers_mst_enable_stream_features */ + KUNIT_CASE(dm_test_mst_enable_stream_features_aux_disabled), + KUNIT_CASE(dm_test_mst_enable_stream_features_writes_downspread), + /* dm_helpers_enable_periodic_detection */ + KUNIT_CASE(dm_test_enable_periodic_detection_no_workqueue), + KUNIT_CASE(dm_test_enable_periodic_detection_updates_enable), + KUNIT_CASE(dm_test_enable_periodic_detection_schedules_work), {} }; From a30c4e979bba8da348a29edd77f103b89f0d717b Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 036/155] drm/amd/display: Test MCCS FreeSync VCP helpers [WHAT] Add KUnit coverage for dm_helpers_read_mccs_caps and dm_helpers_mccs_vcp_set, including the DP/HDMI/legacy-PCON selection, the i2c VCP request and set packets and the retry-failure paths. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 2 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 419 ++++++++++++++++++ 2 files changed, 421 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index ff23aee54c16..a1612b1ae666 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1800,6 +1800,7 @@ void dm_helpers_read_mccs_caps(struct dc_context *ctx, struct dc_link *link, } } } +EXPORT_IF_KUNIT(dm_helpers_read_mccs_caps); static int mccs_operation_vcp_set(unsigned int vcp_code, struct dc_link *link, uint16_t value) { @@ -1874,4 +1875,5 @@ void dm_helpers_mccs_vcp_set(struct dc_context *ctx, struct dc_link *link, drm_dbg_driver(dev, "%s: Failed to set VCP code %d", __func__, sink->edid_caps.freesync_vcp_code); } +EXPORT_IF_KUNIT(dm_helpers_mccs_vcp_set); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 29feb8c3940b..bde697092032 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -2110,6 +2110,83 @@ static const struct i2c_algorithm dm_test_i2c_algorithm = { .master_xfer = dm_test_i2c_master_xfer, }; +struct dm_test_mccs_i2c_adapter { + struct i2c_adapter base; + u8 write_data[16]; + u8 read_reply[11]; + int write_ret; + int read_ret; + unsigned int write_len; + unsigned int writes; + unsigned int reads; +}; + +static int dm_test_mccs_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct dm_test_mccs_i2c_adapter *fake; + struct i2c_msg *msg = msgs; + size_t copy_len; + + fake = container_of(adapter, struct dm_test_mccs_i2c_adapter, base); + + if (num != 1) + return 0; + + if (msg->flags & I2C_M_RD) { + fake->reads++; + if (fake->read_ret != 1) + return fake->read_ret; + + copy_len = min_t(size_t, msg->len, sizeof(fake->read_reply)); + memcpy(msg->buf, fake->read_reply, copy_len); + return 1; + } + + fake->writes++; + if (fake->write_ret != 1) + return fake->write_ret; + + copy_len = min_t(size_t, msg->len, sizeof(fake->write_data)); + memcpy(fake->write_data, msg->buf, copy_len); + fake->write_len = copy_len; + + return 1; +} + +static const struct i2c_algorithm dm_test_mccs_i2c_algorithm = { + .master_xfer = dm_test_mccs_i2c_master_xfer, +}; + +static struct dm_test_mccs_i2c_adapter *dm_test_alloc_mccs_i2c(struct kunit *test) +{ + struct dm_test_mccs_i2c_adapter *fake; + + fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fake); + + fake->base.algo = &dm_test_mccs_i2c_algorithm; + fake->base.lock_ops = &dm_test_i2c_lock_ops; + fake->write_ret = 1; + fake->read_ret = 1; + rt_mutex_init(&fake->base.bus_lock); + rt_mutex_init(&fake->base.mux_lock); + + return fake; +} + +static u8 dm_test_mccs_checksum(const u8 *data, unsigned int len) +{ + u8 checksum = 0x6e; + unsigned int i; + + for (i = 0; i < len; i++) + checksum ^= data[i]; + + return checksum; +} + static void dm_test_submit_i2c_transfer(struct kunit *test, bool full_transfer) { @@ -2531,6 +2608,334 @@ static void dm_test_enable_periodic_detection_schedules_work(struct kunit *test) KUNIT_EXPECT_TRUE(test, idle_work->ran); } +/* Tests for dm_helpers_read_mccs_caps() */ + +/** + * dm_test_read_mccs_caps_null_ctx - Test early return with NULL context + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_null_ctx(struct kunit *test) +{ + /* NULL ctx → early return, no crash */ + dm_helpers_read_mccs_caps(NULL, NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_read_mccs_caps_null_link - Test early return with NULL link + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_null_link(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* link is NULL → drm_dbg_driver + return */ + dm_helpers_read_mccs_caps(ctx, NULL, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_read_mccs_caps_no_vcp_code - Test no-vcp-code path clears freesync support + * @test: The KUnit test context + * + * With freesync_vcp_code == 0 the i2c/MCCS path is skipped entirely and the + * function only clears sink->mccs_caps.freesync_supported. + */ +static void dm_test_read_mccs_caps_no_vcp_code(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.freesync_vcp_code = 0; + sink->mccs_caps.freesync_supported = true; /* should be cleared */ + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_FALSE(test, sink->mccs_caps.freesync_supported); +} + +/* + * Allocate and wire the adev/ctx/link/sink/connector/i2c objects shared by the + * MCCS read/set tests so each test only configures the fields it exercises. + */ +struct dm_test_mccs_fixture { + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + struct amdgpu_dm_connector *aconnector; + struct dm_test_mccs_i2c_adapter *fake; +}; + +static struct dm_test_mccs_fixture dm_test_alloc_mccs_fixture(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture; + + fixture.adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, fixture.adev); + fixture.ctx = kunit_kzalloc(test, sizeof(*fixture.ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.ctx); + fixture.link = kunit_kzalloc(test, sizeof(*fixture.link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.link); + fixture.sink = kunit_kzalloc(test, sizeof(*fixture.sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.sink); + fixture.aconnector = kunit_kzalloc(test, sizeof(*fixture.aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.aconnector); + fixture.fake = dm_test_alloc_mccs_i2c(test); + + fixture.ctx->driver_context = fixture.adev; + fixture.aconnector->i2c = (struct amdgpu_i2c_adapter *)fixture.fake; + fixture.link->priv = fixture.aconnector; + + return fixture; +} + +/** + * dm_test_read_mccs_caps_i2c_vcp_request - Test MCCS VCP request packet + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_i2c_vcp_request(struct kunit *test) +{ + static const u8 expected_prefix[] = { 0x51, 0x82, 0x01, 0xe3 }; + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); + KUNIT_EXPECT_EQ(test, fake->write_len, (unsigned int)sizeof(expected_prefix) + 1); + KUNIT_EXPECT_EQ(test, memcmp(fake->write_data, expected_prefix, + sizeof(expected_prefix)), 0); + KUNIT_EXPECT_EQ(test, fake->write_data[4], + dm_test_mccs_checksum(expected_prefix, sizeof(expected_prefix))); +} + +/** + * dm_test_read_mccs_caps_hdmi_vcp_request - Test local HDMI MCCS path + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_hdmi_vcp_request(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); +} + +/** + * dm_test_read_mccs_caps_legacy_pcon_vcp_request - Test legacy PCON path + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_legacy_pcon_vcp_request(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); +} + +/** + * dm_test_read_mccs_caps_i2c_failure - Test VCP request retry failure + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_i2c_failure(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + fixture.aconnector->base.dev = &fixture.adev->ddev; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->write_ret = 0; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_FALSE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 5U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); +} + +/* Tests for dm_helpers_mccs_vcp_set() */ + +/** + * dm_test_mccs_vcp_set_null_ctx - Test early return with NULL context + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_null_ctx(struct kunit *test) +{ + /* NULL ctx → early return, no crash */ + dm_helpers_mccs_vcp_set(NULL, NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_not_supported - Test early return when freesync unsupported + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_not_supported(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->mccs_caps.freesync_supported = false; + + /* freesync not supported → early return without i2c */ + dm_helpers_mccs_vcp_set(ctx, link, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_null_link - Test early return with NULL link + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_null_link(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + ctx->driver_context = adev; + dm_helpers_mccs_vcp_set(ctx, NULL, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_i2c_packet - Test MCCS VCP set packet + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_i2c_packet(struct kunit *test) +{ + static const u8 expected_prefix[] = { + 0x51, 0x84, 0x03, 0xe3, 0x01, 0x01, + }; + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + sink->mccs_caps.freesync_supported = true; + sink->edid_caps.freesync_vcp_code = 0xe3; + + dm_helpers_mccs_vcp_set(ctx, link, sink); + + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); + KUNIT_EXPECT_EQ(test, fake->write_len, (unsigned int)sizeof(expected_prefix) + 1); + KUNIT_EXPECT_EQ(test, memcmp(fake->write_data, expected_prefix, + sizeof(expected_prefix)), 0); + KUNIT_EXPECT_EQ(test, fake->write_data[6], + dm_test_mccs_checksum(expected_prefix, sizeof(expected_prefix))); +} + +/** + * dm_test_mccs_vcp_set_i2c_failure - Test VCP set retry failure path + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_i2c_failure(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + sink->mccs_caps.freesync_supported = true; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->write_ret = 0; + + dm_helpers_mccs_vcp_set(ctx, link, sink); + + KUNIT_EXPECT_EQ(test, fake->writes, 5U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -2668,6 +3073,20 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_enable_periodic_detection_no_workqueue), KUNIT_CASE(dm_test_enable_periodic_detection_updates_enable), KUNIT_CASE(dm_test_enable_periodic_detection_schedules_work), + /* dm_helpers_read_mccs_caps */ + KUNIT_CASE(dm_test_read_mccs_caps_null_ctx), + KUNIT_CASE(dm_test_read_mccs_caps_null_link), + KUNIT_CASE(dm_test_read_mccs_caps_no_vcp_code), + KUNIT_CASE(dm_test_read_mccs_caps_i2c_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_hdmi_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_legacy_pcon_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_i2c_failure), + /* dm_helpers_mccs_vcp_set */ + KUNIT_CASE(dm_test_mccs_vcp_set_null_ctx), + KUNIT_CASE(dm_test_mccs_vcp_set_not_supported), + KUNIT_CASE(dm_test_mccs_vcp_set_null_link), + KUNIT_CASE(dm_test_mccs_vcp_set_i2c_packet), + KUNIT_CASE(dm_test_mccs_vcp_set_i2c_failure), {} }; From 319f7e13423ae3f486b9aea82f9ad2d6af0ee608 Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Mon, 6 Jul 2026 15:15:07 -0400 Subject: [PATCH 037/155] drm/amdkfd: fix 32-bit overflow in CWSR total size calculation total_cwsr_size was computed in 32-bit before being used as a BO/SVM allocation size. With large ctx_save_restore_area_size and debug_memory_size multiplied by the XCC count, the product can wrap, yielding an undersized CWSR save area that firmware later overruns. Promote total_cwsr_size to u64 and use check_add_overflow()/ check_mul_overflow() in both kfd_queue_acquire_buffers() and kfd_queue_release_buffers(). Signed-off-by: Yongqiang Sun Reviewed-by: Philip Yang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 28354a4e5dd5..98a5512b701b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -23,6 +23,7 @@ */ #include +#include #include "kfd_priv.h" #include "kfd_topology.h" #include "kfd_svm.h" @@ -235,7 +236,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope struct kfd_topology_device *topo_dev; u64 expected_queue_size; struct amdgpu_vm *vm; - u32 total_cwsr_size; + u64 total_cwsr_size; int err; topo_dev = kfd_topology_device_by_id(pdd->dev->id); @@ -308,8 +309,14 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope goto out_err_unreserve; } - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) { + err = -EINVAL; + goto out_err_unreserve; + } total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); err = kfd_queue_buffer_get(vm, (void *)properties->ctx_save_restore_area_address, @@ -344,7 +351,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_properties *properties) { struct kfd_topology_device *topo_dev; - u32 total_cwsr_size; + u64 total_cwsr_size; kfd_queue_buffer_put(&properties->wptr_bo); kfd_queue_buffer_put(&properties->rptr_bo); @@ -355,8 +362,12 @@ int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_prope topo_dev = kfd_topology_device_by_id(pdd->dev->id); if (!topo_dev) return -EINVAL; - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) + return -EINVAL; total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); kfd_queue_buffer_svm_put(pdd, properties->ctx_save_restore_area_address, total_cwsr_size); From 290b370657fe7373cb0f301355536bfc9900bcba Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 15:27:51 +0800 Subject: [PATCH 038/155] drm/amd/display: move connector state dereference after NULL check amdgpu_dm_connector_atomic_check() checks whether the old or new connector state returned by the atomic helpers is NULL before using those pointers. However, new_con_state is already dereferenced while initializing crtc, before the NULL check is reached. If drm_atomic_get_new_connector_state() returns NULL, the function can dereference the NULL pointer before the WARN_ON() check can handle it. Declare crtc first and initialize it only after the NULL check has succeeded. Fixes: 1e5e8d672fec ("drm/amd/display: Avoid a NULL pointer dereference") Signed-off-by: Guangshuo Li Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260708072751.724400-1-lgs201920130244@gmail.com (ML: adjust for movement to amdgpu_dm_connector.c) Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index f77af83ade39..dfce7f79dfe0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -2369,7 +2369,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, drm_atomic_get_new_connector_state(state, conn); struct drm_connector_state *old_con_state = drm_atomic_get_old_connector_state(state, conn); - struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); int ret; @@ -2385,6 +2385,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, return ret; } + crtc = new_con_state->crtc; if (!crtc) return 0; From b60d1d91444bc9c8e3b99d815be9647e129d017f Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 9 Jul 2026 14:22:51 +0800 Subject: [PATCH 039/155] drm/amdgpu: use IP version check in sysfs creation conditional logic avoid sysfs node creation faults when performing NPS mode switching Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index fd2c0cc1b958..19067df24600 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -3777,8 +3777,11 @@ int amdgpu_ras_block_late_init(struct amdgpu_device *adev, } if (amdgpu_uniras_enabled(adev) || (ras_obj->hw_ops && - (ras_obj->hw_ops->query_ras_error_count || - ras_obj->hw_ops->query_ras_error_status))) { + (ras_obj->hw_ops->query_ras_error_count || + ras_obj->hw_ops->query_ras_error_status)) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6)) { r = amdgpu_ras_sysfs_create(adev, ras_block); if (r) goto interrupt; From a8880289fd67e85d155dbe6d625e3f6fcd94ff51 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Tue, 7 Jul 2026 21:30:43 +0800 Subject: [PATCH 040/155] drm/amdgpu: drop debug_enable_ras_aca debug mask flag drop debug_enable_ras_aca debug mask flag Signed-off-by: Ce Sun Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 - drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 6 ------ drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 3 +-- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 8339ab6f5d32..f5d65bd0ac25 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -926,7 +926,6 @@ struct amdgpu_device { bool debug_largebar; bool debug_disable_soft_recovery; bool debug_use_vram_fw_buf; - bool debug_enable_ras_aca; bool debug_exp_resets; bool debug_disable_gpu_ring_reset; bool debug_vm_userptr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ad631ad31899..5362705143bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -139,7 +139,6 @@ enum AMDGPU_DEBUG_MASK { AMDGPU_DEBUG_LARGEBAR = BIT(1), AMDGPU_DEBUG_DISABLE_GPU_SOFT_RECOVERY = BIT(2), AMDGPU_DEBUG_USE_VRAM_FW_BUF = BIT(3), - AMDGPU_DEBUG_ENABLE_RAS_ACA = BIT(4), AMDGPU_DEBUG_ENABLE_EXP_RESETS = BIT(5), AMDGPU_DEBUG_DISABLE_GPU_RING_RESET = BIT(6), AMDGPU_DEBUG_SMU_POOL = BIT(7), @@ -2264,11 +2263,6 @@ static void amdgpu_init_debug_options(struct amdgpu_device *adev) adev->debug_use_vram_fw_buf = true; } - if (amdgpu_debug_mask & AMDGPU_DEBUG_ENABLE_RAS_ACA) { - pr_info("debug: enable RAS ACA\n"); - adev->debug_enable_ras_aca = true; - } - if (amdgpu_debug_mask & AMDGPU_DEBUG_ENABLE_EXP_RESETS) { pr_info("debug: enable experimental reset features\n"); adev->debug_exp_resets = true; diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c index b62bbb5ea292..3b5da8515a20 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c @@ -332,8 +332,7 @@ static int amdgpu_ras_mgr_sw_init(struct amdgpu_ip_block *ip_block) if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || - amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6) || - adev->debug_enable_ras_aca) + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6)) con->uniras_enabled = true; else return 0; From bf65973e5f5e1d6f7398c43bb8bc78ec6c1f3116 Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Fri, 26 Jun 2026 20:20:15 +0800 Subject: [PATCH 041/155] drm/amdgpu/ras: only check bad page for address-based UMC injection UMC error injection on MI300 series is dispatched by the RAS TA using the injection method; only the "coherent" methods are address based, the single-shot/persistent/ac-parity ones ignore the address. The debugfs control path validated the injection address against the bad page list for every UMC injection. On uniras (SMU v13+) devices the address is now validated by the ras_mgr inject handler, so the legacy debugfs bad page check only runs on the legacy RAS path; other ASICs keep injecting by address. In the ras_mgr handler an injection is treated as non address-based only when userspace passes the U64_MAX sentinel address and the method is a non-address method. In that case the address is cleared to 0 and the bad page / range validation is skipped; otherwise the injection address is validated as before. Signed-off-by: Stanley.Yang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 10 ++- .../gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c | 81 +++++++++++++++---- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 19067df24600..de121539788d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -596,8 +596,14 @@ static ssize_t amdgpu_ras_debugfs_ctrl_write(struct file *f, ret = amdgpu_ras_feature_enable(adev, &data.head, 1); break; case 2: - /* umc ce/ue error injection for a bad page is not allowed */ - if (data.head.block == AMDGPU_RAS_BLOCK__UMC) + /* + * UMC ce/ue error injection for a bad page is not allowed. For + * uniras (SMU v13+) devices the injection address is validated by + * the ras_mgr inject handler, so only run the legacy bad page + * check for the legacy RAS path. + */ + if (data.head.block == AMDGPU_RAS_BLOCK__UMC && + !amdgpu_uniras_enabled(adev)) ret = amdgpu_ras_check_bad_page(adev, data.inject.address); if (ret == -EINVAL) { dev_warn(adev->dev, "RAS WARN: input address 0x%llx is invalid.", diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c index bfbfdffbfbe6..c2285fde8b3c 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c @@ -82,6 +82,43 @@ static uint64_t local_addr_to_xgmi_global_addr(struct ras_core_context *ras_core return (addr + xgmi->physical_node_id * xgmi->node_segment_size); } +/* + * UMC error injection is dispatched by the RAS TA using the injection method + * carried in struct ras_cmd_inject_error_req. Only the "coherent" methods + * program an explicit injection address and are therefore address-based; the + * single-shot, persistent and ac-parity methods ignore the address. + * + * Keep these values in sync with the RAS TA. + */ +enum umc_inject_method { + UMC_METHOD_COHERENT = 0, + UMC_METHOD_SINGLE_SHOT = 1, + UMC_METHOD_PERSISTENT = 2, + UMC_METHOD_PERSISTENT_DISABLE = 3, + UMC_METHOD_COHERENT_NO_DETECTION = 4, + UMC_METHOD_COHERENT_WR = 5, + UMC_METHOD_SINGLE_SHOT_WR = 6, + UMC_METHOD_PERSISTENT_WR = 7, + UMC_METHOD_SINGLE_SHOT_CLEAN = 8, +}; + +/* + * Return true when @method does not program an explicit injection address. + * Only the coherent methods are address-based; every other method ignores the + * address, so userspace signals them by setting the address to U64_MAX. + */ +static bool amdgpu_ras_mgr_is_non_address_injection(u64 method) +{ + switch (method) { + case UMC_METHOD_COHERENT: + case UMC_METHOD_COHERENT_NO_DETECTION: + case UMC_METHOD_COHERENT_WR: + return false; + default: + return true; + } +} + static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, struct ras_cmd_ctx *cmd, void *data) { @@ -91,25 +128,35 @@ static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, int ret = RAS_CMD__ERROR_GENERIC; if (req->block_id == RAS_BLOCK_ID__UMC) { - if (amdgpu_ras_mgr_check_retired_addr(adev, req->address)) { - RAS_DEV_WARN(ras_core->dev, - "RAS WARN: inject: 0x%llx has already been marked as bad!\n", - req->address); - return RAS_CMD__ERROR_ACCESS_DENIED; - } - - if ((req->address >= adev->gmc.mc_vram_size && - adev->gmc.mc_vram_size) || - (req->address >= RAS_UMC_INJECT_ADDR_LIMIT)) { - RAS_DEV_WARN(adev, "RAS WARN: input address 0x%llx is invalid.", + /* + * Only address-based UMC injections carry an explicit injection + * address that has to be validated. A non address-based method + * ignores the address, and userspace flags such an injection by + * setting the address to U64_MAX. When both the sentinel and the + * method agree, clear the address so the RAS TA ignores it and + * skip the validation; otherwise validate the injection address. + */ + if (req->address == U64_MAX && amdgpu_ras_mgr_is_non_address_injection(req->method)) { + req->address = 0x0; + } else { + if (amdgpu_ras_mgr_check_retired_addr(adev, req->address)) { + RAS_DEV_WARN(ras_core->dev, + "RAS WARN: inject: 0x%llx has already been marked as bad!\n", req->address); - return RAS_CMD__ERROR_INVALID_INPUT_DATA; - } + return RAS_CMD__ERROR_ACCESS_DENIED; + } - /* Calculate XGMI relative offset */ - if (adev->gmc.xgmi.num_physical_nodes > 1 && - req->block_id != RAS_BLOCK_ID__GFX) { - req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); + if ((req->address >= adev->gmc.mc_vram_size && + adev->gmc.mc_vram_size) || + (req->address >= RAS_UMC_INJECT_ADDR_LIMIT)) { + RAS_DEV_WARN(adev, "RAS WARN: input address 0x%llx is invalid.", + req->address); + return RAS_CMD__ERROR_INVALID_INPUT_DATA; + } + + /* Calculate XGMI relative offset */ + if (adev->gmc.xgmi.num_physical_nodes > 1) + req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); } } From 4a82dba89fee39781e50236d46413e460c01c2a3 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Fri, 10 Jul 2026 16:55:53 +0530 Subject: [PATCH 042/155] drm/amdkfd: Do not fail process debugfs setup on debugfs errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit debugfs is intended for debugging only, and failures to create debugfs entries should not affect normal operation. Remove the check for debugfs_create_dir() in kfd_debugfs_add_process(). If debugfs entries cannot be created, continue without them instead of reporting an unnecessary error. Fixes: 325de6bf3328 ("drm/amdkfd: expose pasid of secondary contexts by debugfs") Reported-by: Dan Carpenter Cc: Zhu Lingshan Cc: Felix Kuehling Signed-off-by: Srinivasan Shanmugam Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c b/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c index 464836f2a53f..33e905d59dd9 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c @@ -211,10 +211,6 @@ int kfd_debugfs_add_process(struct kfd_process *p) entry->proc_dentry = debugfs_create_dir(name, primary_entry->proc_dentry); } - if (IS_ERR_OR_NULL(entry->proc_dentry)) { - ret = entry->proc_dentry ? PTR_ERR(entry->proc_dentry) : -ENOMEM; - goto err_free_entry; - } list_add(&entry->list, &procs); kfd_debugfs_create_pasid_files(p, entry->proc_dentry); From da805bfb3dfeb3fd30b905eb2ad98700994192e6 Mon Sep 17 00:00:00 2001 From: Andre Luiz Batista Bueno Date: Wed, 8 Jul 2026 13:28:50 -0300 Subject: [PATCH 043/155] drm/amdgpu: deduplicate JPEG v5.0 interrupt routine Both jpeg_v5_0_1.c and jpeg_v5_0_2.c implement identical interrupt processing routines. To avoid code duplication, make the implementation in jpeg_v5_0_1.c non-static and call it directly from jpeg_v5_0_2.c. Signed-off-by: Andre Luiz Batista Bueno Co-developed-by: Enzo Furegatti Spinella Signed-off-by: Enzo Furegatti Spinella Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 2 +- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h | 8 +++ drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c | 63 ++---------------------- 3 files changed, 12 insertions(+), 61 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index 8846cb3ed12b..db3fbe4eabb9 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -772,7 +772,7 @@ static int jpeg_v5_0_1_set_ras_interrupt_state(struct amdgpu_device *adev, -static int jpeg_v5_0_1_process_interrupt(struct amdgpu_device *adev, +int jpeg_v5_0_1_process_interrupt(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry) { diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h index a7e58d5fb246..67346faecb47 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h @@ -108,4 +108,12 @@ enum amdgpu_jpeg_v5_0_1_sub_block { AMDGPU_JPEG_V5_0_1_MAX_SUB_BLOCK, }; +struct amdgpu_irq_src; +struct amdgpu_iv_entry; +struct amdgpu_device; + +int jpeg_v5_0_1_process_interrupt(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry); + #endif /* __JPEG_V5_0_1_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c index ff02f72352a8..ea54a2b6eddb 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c @@ -34,6 +34,8 @@ #include "vcn/vcn_5_0_0_sh_mask.h" #include "ivsrcid/vcn/irqsrcs_vcn_5_0.h" +#include "jpeg_v5_0_1.h" + static void jpeg_v5_0_2_set_dec_ring_funcs(struct amdgpu_device *adev); static void jpeg_v5_0_2_set_irq_funcs(struct amdgpu_device *adev); static int jpeg_v5_0_2_set_powergating_state(struct amdgpu_ip_block *ip_block, @@ -583,65 +585,6 @@ static int jpeg_v5_0_2_set_interrupt_state(struct amdgpu_device *adev, return 0; } -static int jpeg_v5_0_2_process_interrupt(struct amdgpu_device *adev, - struct amdgpu_irq_src *source, - struct amdgpu_iv_entry *entry) -{ - u32 i, inst; - - i = node_id_to_phys_map[entry->node_id]; - DRM_DEV_DEBUG(adev->dev, "IH: JPEG TRAP\n"); - - for (inst = 0; inst < adev->jpeg.num_jpeg_inst; ++inst) - if (adev->jpeg.inst[inst].aid_id == i) - break; - - if (inst >= adev->jpeg.num_jpeg_inst) { - dev_WARN_ONCE(adev->dev, 1, - "Interrupt received for unknown JPEG instance %d", - entry->node_id); - return 0; - } - - switch (entry->src_id) { - case VCN_5_0__SRCID__JPEG_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[0]); - break; - case VCN_5_0__SRCID__JPEG1_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[1]); - break; - case VCN_5_0__SRCID__JPEG2_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[2]); - break; - case VCN_5_0__SRCID__JPEG3_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[3]); - break; - case VCN_5_0__SRCID__JPEG4_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[4]); - break; - case VCN_5_0__SRCID__JPEG5_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[5]); - break; - case VCN_5_0__SRCID__JPEG6_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[6]); - break; - case VCN_5_0__SRCID__JPEG7_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[7]); - break; - case VCN_5_0__SRCID__JPEG8_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[8]); - break; - case VCN_5_0__SRCID__JPEG9_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[9]); - break; - default: - DRM_DEV_ERROR(adev->dev, "Unhandled interrupt: %d %d\n", - entry->src_id, entry->src_data[0]); - break; - } - - return 0; -} static void jpeg_v5_0_2_core_stall_reset(struct amdgpu_ring *ring) { @@ -747,7 +690,7 @@ static void jpeg_v5_0_2_set_dec_ring_funcs(struct amdgpu_device *adev) static const struct amdgpu_irq_src_funcs jpeg_v5_0_2_irq_funcs = { .set = jpeg_v5_0_2_set_interrupt_state, - .process = jpeg_v5_0_2_process_interrupt, + .process = jpeg_v5_0_1_process_interrupt, }; static void jpeg_v5_0_2_set_irq_funcs(struct amdgpu_device *adev) From e4ae30a12aa95942814957d8bc1ce7366a7107d7 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 29 Jun 2026 02:04:13 +1000 Subject: [PATCH 044/155] drm/amd/display: Fix DM I2C teardown race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DM I2C adapters can remain visible to userspace while DM teardown is already in progress. A concurrent i2c-dev transfer may then enter amdgpu_dm_i2c_xfer() after the backing DM state has been torn down, leading to a NULL pointer dereference. Create a devres group around the DM I2C adapter lifetime and release it at the start of dm_hw_fini(), before HPD, IRQ, and DM state are torn down. This removes the I2C adapters first and waits for in-flight users to drain before the structures used by amdgpu_dm_i2c_xfer() disappear. This fixes a teardown ordering race seen during device removal: BUG: kernel NULL pointer dereference RIP: amdgpu_dm_i2c_xfer+0x122/0x1c0 [amdgpu] Call Trace: __i2c_transfer i2c_transfer i2cdev_ioctl_rdwr Fixes: 5b3eca05cfb0 ("drm/amd/display: Use devm_i2c_add_adapter to simplify i2c cleanup logic") Signed-off-by: Geoffrey McRae Acked-by: Alex Deucher Reviewed-by: Leo Li Cc: Alex Deucher Cc: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 14 +++++++++++++- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 74bb5845b6be..fb19a0781e59 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1496,17 +1496,26 @@ static int dm_hw_init(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int r; + adev->dm.i2c_devres_group = devres_open_group(adev->dev, NULL, GFP_KERNEL); + if (!adev->dm.i2c_devres_group) + return -ENOMEM; + /* Create DAL display manager */ r = amdgpu_dm_init(adev); if (r) - return r; + goto err_release_i2c; amdgpu_dm_hpd_init(adev); r = dm_oem_i2c_hw_init(adev); if (r) drm_info(adev_to_drm(adev), "Failed to add OEM i2c bus\n"); + devres_close_group(adev->dev, adev->dm.i2c_devres_group); return 0; + +err_release_i2c: + devres_release_group(adev->dev, adev->dm.i2c_devres_group); + return r; } /** @@ -1521,6 +1530,9 @@ static int dm_hw_fini(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; + if (adev->dm.i2c_devres_group) + devres_release_group(adev->dev, adev->dm.i2c_devres_group); + amdgpu_dm_hpd_fini(adev); amdgpu_dm_irq_fini(adev); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 91affbdb2d6c..a04c7f26672b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -688,6 +688,13 @@ struct amdgpu_display_manager { */ void *bb_from_dmub; + /** + * @i2c_devres_group: + * + * Devres group for DM i2c adapter lifetime management. + */ + void *i2c_devres_group; + /** * @oem_i2c: * From e78b0a367f8690b682029d90e75308dc84ed51de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 29 May 2026 11:09:08 +0200 Subject: [PATCH 045/155] drm/amd/display: Fix preferred link rate for NUTMEG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When there is a preferred link rate setting, it needs to be applied to both the current and initial link rate. This was regressed by a "coding style" fix, which caused the current link rate to not respect the preferred value. This commit restores the functionality of NUTMEG, the DP bridge encoder found on old APUs such as Kaveri. Fixes: a62346043a89 ("drm/amd/display: Fix coding style issue") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5465 Cc: Chuanyu Tseng Reviewed-by: Fangzhi Zuo Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/link/protocols/link_dp_capability.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index d2329714408a..5d3b5266005d 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -756,8 +756,10 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) return false; - if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) + if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) { initial_link_setting.link_rate = link->preferred_link_setting.link_rate; + current_link_setting.link_rate = link->preferred_link_setting.link_rate; + } /* search for the minimum link setting that: * 1. is supported according to the link training result From 871ceb853841bcaa4e6cec3723b16c4887a760be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 29 May 2026 11:09:09 +0200 Subject: [PATCH 046/155] drm/amd/display: Add dp_skip_rbr flag for NUTMEG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No functional changes. Just clean up a conceptual mismatch. Based on feedback on the NUTMEG code in DC, the preferred_link_setting is meant to force the DP link to a specific setting, meaning both the link rate and lane count should be locked to an exact value. What NUTMEG needs is a lower bound on the link rate, which is not the same concept. Implement this as a HW workaround flag instead. Suggested-by: Wenjing Liu Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 ++ drivers/gpu/drm/amd/display/dc/link/link_detection.c | 2 +- .../drm/amd/display/dc/link/protocols/link_dp_capability.c | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index c78fdfc0ef4b..a10ee711668e 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1936,6 +1936,8 @@ struct dc_scratch_space { bool dp_skip_DID2; bool dp_skip_reset_segment; bool dp_skip_fs_144hz; + /* Some DP bridges don't work with RBR and must use HBR. */ + bool dp_skip_rbr; bool dp_mot_reset_segment; /* Some USB4 docks do not handle turning off MST DSC once it has been enabled. */ bool dpia_mst_dsc_always_on; diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 2abe9d3a68d5..4f18b668340c 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -646,7 +646,7 @@ static bool detect_dp(struct dc_link *link, link->dpcd_caps.sink_count.bits.SINK_COUNT = 1; /* NUTMEG requires that we use HBR, doesn't work with RBR. */ if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_00001A) - link->preferred_link_setting.link_rate = LINK_RATE_HIGH; + link->wa_flags.dp_skip_rbr = true; } return true; diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index 5d3b5266005d..24e3bbec15ff 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -756,9 +756,9 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) return false; - if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) { - initial_link_setting.link_rate = link->preferred_link_setting.link_rate; - current_link_setting.link_rate = link->preferred_link_setting.link_rate; + if (link->wa_flags.dp_skip_rbr) { + initial_link_setting.link_rate = LINK_RATE_HIGH; + current_link_setting.link_rate = LINK_RATE_HIGH; } /* search for the minimum link setting that: From 5b92b6bbeb5a5cde9a860dc3eb5916446445dd20 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Fri, 10 Jul 2026 20:06:33 +0530 Subject: [PATCH 047/155] drm/amd/display: Run FP-disabled MST stub test only without DC FP dm_mst_test_fp_guarded_public_stubs() passes NULL arguments to dm_dp_mst_is_port_support_mode() to test the stub implementation when CONFIG_DRM_AMD_DC_FP is disabled. However, this test is always registered. When CONFIG_DRM_AMD_DC_FP is enabled, the real implementation is used instead of the stub. The real implementation expects valid pointers, so passing NULL can lead to a NULL pointer dereference. Register this test only when CONFIG_DRM_AMD_DC_FP is disabled. Reported-by: Dan Carpenter Cc: Bhawanpreet Lakha Cc: Alex Hung Cc: George Zhang Cc: Roman Li Cc: Tom Chung Cc: Aurabindo Pillai Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Hung Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index 3a663ee0ca2b..eaffff61129e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -1015,6 +1015,7 @@ static void dm_mst_test_detect_unregistered(struct kunit *test) (int)connector_status_disconnected); } +#if !defined(CONFIG_DRM_AMD_DC_FP) /** * dm_mst_test_fp_guarded_public_stubs - Test FP-off public fallbacks * @test: KUnit test context @@ -1027,6 +1028,7 @@ static void dm_mst_test_fp_guarded_public_stubs(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_dp_mst_is_port_support_mode(NULL, NULL), (enum dc_status)DC_OK); } +#endif static struct kunit_case dm_mst_types_test_cases[] = { /* needs_dsc_aux_workaround tests */ @@ -1077,7 +1079,9 @@ static struct kunit_case dm_mst_types_test_cases[] = { /* dm_dp_mst_detect tests */ KUNIT_CASE(dm_mst_test_detect_unregistered), /* CONFIG_DRM_AMD_DC_FP disabled public paths */ +#if !defined(CONFIG_DRM_AMD_DC_FP) KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs), +#endif {} }; From b40bd803afc89e0fc1c69911f713b867720b5a14 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 048/155] drm/amd/display: Test MST payload and DSC enable helpers [WHAT] Add KUnit coverage for dm_helpers_construct_old_payload, the DP MST payload allocation, poll-for-ACT and deallocation success paths, the full dm_helpers_dp_write_dsc_enable matrix and dm_helpers_dp_handle_test_pattern_request. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 4 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 4 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 806 ++++++++++++++++++ 3 files changed, 813 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index a1612b1ae666..86f979262f28 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -298,7 +298,7 @@ void dm_helpers_dp_update_branch_info( {} EXPORT_IF_KUNIT(dm_helpers_dp_update_branch_info); -static void dm_helpers_construct_old_payload( +STATIC_IFN_KUNIT void dm_helpers_construct_old_payload( struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, struct drm_dp_mst_atomic_payload *new_payload, @@ -329,6 +329,7 @@ static void dm_helpers_construct_old_payload( old_payload->time_slots = allocated_time_slots; old_payload->pbn = allocated_time_slots * pbn_per_slot; } +EXPORT_IF_KUNIT(dm_helpers_construct_old_payload); /* * Writes payload allocation table in immediate downstream device. @@ -1588,6 +1589,7 @@ bool dm_helpers_dp_handle_test_pattern_request( return false; } +EXPORT_IF_KUNIT(dm_helpers_dp_handle_test_pattern_request); void dm_set_phyd32clk(struct dc_context *ctx, int freq_khz) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index e256e1e12422..0eab820757c1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -33,6 +33,10 @@ void fill_dc_mst_payload_table_from_drm(struct dc_link *link, bool enable, struct drm_dp_mst_atomic_payload *target_payload, struct dc_dp_mst_stream_allocation_table *table); +void dm_helpers_construct_old_payload(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_state *mst_state, + struct drm_dp_mst_atomic_payload *new_payload, + struct drm_dp_mst_atomic_payload *old_payload); bool dm_helpers_dp_write_dsc_enable(struct dc_context *ctx, const struct dc_stream_state *stream, bool enable); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index bde697092032..058e1ad15dfe 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -2936,6 +2936,784 @@ static void dm_test_mccs_vcp_set_i2c_failure(struct kunit *test) KUNIT_EXPECT_EQ(test, fake->reads, 0U); } +/* Tests for dm_helpers_construct_old_payload() */ + +/** + * dm_test_construct_old_payload_empty_list - Test PBN/time-slot calc, empty list + * @test: The KUnit test context + * + * With no other payloads, next_payload_vc_start stays at mgr->next_start_slot, + * so allocated time_slots = next_start_slot - vc_start_slot. + */ +static void dm_test_construct_old_payload_empty_list(struct kunit *test) +{ + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *new_payload; + struct drm_dp_mst_atomic_payload *old_payload; + + mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mgr); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + new_payload = kunit_kzalloc(test, sizeof(*new_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, new_payload); + old_payload = kunit_kzalloc(test, sizeof(*old_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_payload); + + INIT_LIST_HEAD(&mst_state->payloads); + mgr->next_start_slot = 10; + mst_state->pbn_div.full = 5 << 12; /* dfixed_trunc → 5 PBN/slot */ + new_payload->vc_start_slot = 3; + + dm_helpers_construct_old_payload(mgr, mst_state, new_payload, + old_payload); + + /* 10 - 3 = 7 slots, 7 * 5 = 35 PBN */ + KUNIT_EXPECT_EQ(test, old_payload->time_slots, 7); + KUNIT_EXPECT_EQ(test, old_payload->pbn, 35); +} + +/** + * dm_test_construct_old_payload_intervening - Test calc with an intervening payload + * @test: The KUnit test context + * + * A payload whose vc_start_slot falls between the new payload and the manager's + * next_start_slot narrows the allocated time-slot window. + */ +static void dm_test_construct_old_payload_intervening(struct kunit *test) +{ + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *new_payload; + struct drm_dp_mst_atomic_payload *other_payload; + struct drm_dp_mst_atomic_payload *old_payload; + + mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mgr); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + new_payload = kunit_kzalloc(test, sizeof(*new_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, new_payload); + other_payload = kunit_kzalloc(test, sizeof(*other_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other_payload); + old_payload = kunit_kzalloc(test, sizeof(*old_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_payload); + + INIT_LIST_HEAD(&mst_state->payloads); + mgr->next_start_slot = 10; + mst_state->pbn_div.full = 5 << 12; + new_payload->vc_start_slot = 3; + + /* other payload at slot 6 (between 3 and 10) narrows window to 6 */ + other_payload->vc_start_slot = 6; + list_add_tail(&other_payload->next, &mst_state->payloads); + + dm_helpers_construct_old_payload(mgr, mst_state, new_payload, + old_payload); + + /* 6 - 3 = 3 slots, 3 * 5 = 15 PBN */ + KUNIT_EXPECT_EQ(test, old_payload->time_slots, 3); + KUNIT_EXPECT_EQ(test, old_payload->pbn, 15); +} + +/* Tests for dm_helpers_dp_mst_write_payload_allocation_table() success path */ + +/** + * dm_test_write_payload_alloc_table_success - Exercise the MST success path + * @test: The KUnit test context + * @enable: true for the add-payload path, false for the remove-payload path + * + * Builds a minimal MST topology-state fixture so the helper traverses past the + * early NULL checks and runs the real DRM MST payload helpers + * (drm_dp_add_payload_part1 / drm_dp_remove_payload_part1). With + * mgr->mst_primary == NULL the topology walk fails gracefully, so no remote + * DPCD/AUX traffic is generated, and the helper still returns true. + */ +static void dm_test_write_payload_alloc_table_success(struct kunit *test, + bool enable) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *payload; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + struct dc_link *link; + struct dc_dp_mst_stream_allocation_table table = { 0 }; + bool ret; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, payload); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* aconnector acts as its own MST root for this fixture */ + aconnector->mst_root = aconnector; + aconnector->mst_output_port = port; + + mgr = &aconnector->mst_mgr; + mutex_init(&mgr->lock); + mgr->dev = &adev->ddev; + mgr->mst_primary = NULL; /* topology walk fails gracefully */ + mgr->base.state = &mst_state->base; + + INIT_LIST_HEAD(&mst_state->payloads); + mst_state->pbn_div.full = 5 << 12; /* dfixed_trunc → 5 PBN/slot */ + + /* payload found by drm_atomic_get_mst_payload_state via matching port */ + payload->port = port; + payload->vcpi = 1; + payload->vc_start_slot = 1; + payload->time_slots = 2; + list_add_tail(&payload->next, &mst_state->payloads); + + /* pre-existing HW allocation so the disable path finds a VCPI to clear */ + link->mst_stream_alloc_table.stream_count = 1; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 2; + + stream->dm_stream_context = aconnector; + stream->link = link; + + ret = dm_helpers_dp_mst_write_payload_allocation_table(NULL, stream, + &table, enable); + + KUNIT_EXPECT_TRUE(test, ret); + + if (enable) + /* add path keeps the old entry and appends the new payload */ + KUNIT_EXPECT_EQ(test, table.stream_count, 2); + else + /* remove path clears the only entry */ + KUNIT_EXPECT_EQ(test, table.stream_count, 0); +} + +/** + * dm_test_write_payload_alloc_table_enable - Test add-payload success path + * @test: The KUnit test context + */ +static void dm_test_write_payload_alloc_table_enable(struct kunit *test) +{ + dm_test_write_payload_alloc_table_success(test, true); +} + +/** + * dm_test_write_payload_alloc_table_disable - Test remove-payload success path + * @test: The KUnit test context + */ +static void dm_test_write_payload_alloc_table_disable(struct kunit *test) +{ + dm_test_write_payload_alloc_table_success(test, false); +} + +/* Tests for dm_helpers_dp_mst_poll_for_allocation_change_trigger() */ + +/* + * Stub AUX transfer that ACKs a DPCD read of the payload-table update status + * with the ACT-handled bit set, so drm_dp_check_act_status() returns 0. + */ +static ssize_t dm_test_act_aux_transfer_handled(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) { + memset(msg->buffer, 0, msg->size); + if (msg->size > 0) + ((u8 *)msg->buffer)[0] = DP_PAYLOAD_ACT_HANDLED; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +/* + * Stub AUX transfer that fails every transaction, so the ACT status read + * returns an error and drm_dp_check_act_status() returns non-zero. + */ +static ssize_t dm_test_act_aux_transfer_fail(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + return -EIO; +} + +/** + * dm_test_mst_start_top_mgr_set_mst_fail - Test MST start failure path + * @test: The KUnit test context + * + * With a connector-backed link and a failing AUX channel, the non-boot + * path calls drm_dp_mst_topology_mgr_set_mst(true), which fails to read the + * DPCD caps and returns a negative error, so the helper returns false. + */ +static void dm_test_mst_start_top_mgr_set_mst_fail(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + link = dm_kunit_alloc_link(test); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_fail; + drm_dp_aux_init(aux); + + mutex_init(&aconnector->mst_mgr.lock); + aconnector->mst_mgr.dev = &adev->ddev; + aconnector->mst_mgr.aux = aux; + link->priv = aconnector; + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, false)); +} + +/** + * dm_test_mst_stop_top_mgr_active - Test MST stop on an active topology manager + * @test: The KUnit test context + * + * With mst_state set, the helper calls drm_dp_mst_topology_mgr_set_mst(false) + * to disable MST and clears the link lane count. The helper always returns + * false. + */ +static void dm_test_mst_stop_top_mgr_active(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + link = dm_kunit_alloc_link(test); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_handled; + drm_dp_aux_init(aux); + + mutex_init(&aconnector->mst_mgr.lock); + aconnector->mst_mgr.dev = &adev->ddev; + aconnector->mst_mgr.aux = aux; + aconnector->mst_mgr.mst_state = true; + link->cur_link_settings.lane_count = 4; + link->priv = aconnector; + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_stop_top_mgr(NULL, link)); + KUNIT_EXPECT_EQ(test, link->cur_link_settings.lane_count, 0); +} + +/** + * dm_test_poll_for_act_no_mst_state - Test ACT poll bails when MST not started + * @test: The KUnit test context + * + * With mst_root set but mst_mgr->mst_state false, the helper returns + * ACT_FAILED before touching the AUX channel. + */ +static void dm_test_poll_for_act_no_mst_state(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + aconnector->mst_root = aconnector; + aconnector->mst_mgr.mst_state = false; + stream->dm_stream_context = aconnector; + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/** + * dm_test_poll_for_act_success - Test ACT poll success path + * @test: The KUnit test context + * + * With MST started and the AUX channel reporting ACT handled, + * drm_dp_check_act_status() returns 0 and the helper returns ACT_SUCCESS. + */ +static void dm_test_poll_for_act_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_stream_state *stream; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_handled; + drm_dp_aux_init(aux); + + aconnector->mst_root = aconnector; + aconnector->mst_mgr.mst_state = true; + aconnector->mst_mgr.aux = aux; + stream->dm_stream_context = aconnector; + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_SUCCESS); +} + +/** + * dm_test_poll_for_act_status_failed - Test ACT poll failure path + * @test: The KUnit test context + * + * With MST started but the AUX channel failing, drm_dp_check_act_status() + * returns non-zero and the helper returns ACT_FAILED. + */ +static void dm_test_poll_for_act_status_failed(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_stream_state *stream; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_fail; + drm_dp_aux_init(aux); + + aconnector->mst_root = aconnector; + aconnector->mst_mgr.mst_state = true; + aconnector->mst_mgr.aux = aux; + stream->dm_stream_context = aconnector; + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/* Tests for dm_helpers_dp_mst_send_payload_allocation() */ + +/** + * dm_test_mst_send_payload_alloc_part2_fail - Exercise the failure branch + * @test: The KUnit test context + * + * Builds a minimal MST topology-state fixture so the helper runs past the early + * NULL checks and calls drm_dp_add_payload_part2(). The payload's allocation + * status is left at its default (not DRM_DP_MST_PAYLOAD_ALLOCATION_DFP), so + * drm_dp_add_payload_part2() returns -EIO without any remote DPCD/AUX traffic. + * The non-zero return drives the failure branch, which clears the + * MST_ALLOCATE_NEW_PAYLOAD status bit. + */ +static void dm_test_mst_send_payload_alloc_part2_fail(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *payload; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, payload); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* aconnector acts as its own MST root for this fixture */ + aconnector->mst_root = aconnector; + aconnector->mst_output_port = port; + /* drm_dp_add_payload_part2() logs port->connector->name on failure */ + port->connector = &aconnector->base; + + mgr = &aconnector->mst_mgr; + mgr->dev = &adev->ddev; + mgr->base.state = &mst_state->base; + + INIT_LIST_HEAD(&mst_state->payloads); + + /* payload found by drm_atomic_get_mst_payload_state via matching port */ + payload->port = port; + list_add_tail(&payload->next, &mst_state->payloads); + + /* pre-set the bit so we can observe it being cleared on failure */ + aconnector->mst_status = MST_ALLOCATE_NEW_PAYLOAD; + + stream->dm_stream_context = aconnector; + dm_helpers_dp_mst_send_payload_allocation(NULL, stream); + + KUNIT_EXPECT_EQ(test, + aconnector->mst_status & MST_ALLOCATE_NEW_PAYLOAD, 0); +} + +/* Tests for dm_helpers_dp_mst_update_mst_mgr_for_deallocation() */ + +/** + * dm_test_mst_update_mgr_dealloc_success - Exercise the deallocation path + * @test: The KUnit test context + * + * Builds a minimal MST topology-state fixture so the helper runs past the early + * NULL checks through dm_helpers_construct_old_payload() and + * drm_dp_remove_payload_part2(), both of which are pure list/slot math with no + * remote DPCD/AUX traffic. Afterwards MST_CLEAR_ALLOCATED_PAYLOAD must be set, + * MST_ALLOCATE_NEW_PAYLOAD cleared, and the payload's slot released. + */ +static void dm_test_mst_update_mgr_dealloc_success(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *payload; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, payload); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* aconnector acts as its own MST root for this fixture */ + aconnector->mst_root = aconnector; + aconnector->mst_output_port = port; + + mgr = &aconnector->mst_mgr; + mgr->base.state = &mst_state->base; + mgr->next_start_slot = 10; + mgr->payload_count = 1; + + INIT_LIST_HEAD(&mst_state->payloads); + mst_state->pbn_div.full = 5 << 12; /* dfixed_trunc → 5 PBN/slot */ + + /* payload found by drm_atomic_get_mst_payload_state via matching port */ + payload->port = port; + payload->vc_start_slot = 3; + payload->time_slots = 7; + list_add_tail(&payload->next, &mst_state->payloads); + + /* pre-set the bit so we can observe it being cleared */ + aconnector->mst_status = MST_ALLOCATE_NEW_PAYLOAD; + + stream->dm_stream_context = aconnector; + dm_helpers_dp_mst_update_mst_mgr_for_deallocation(NULL, stream); + + KUNIT_EXPECT_EQ(test, + aconnector->mst_status & MST_CLEAR_ALLOCATED_PAYLOAD, + (int)MST_CLEAR_ALLOCATED_PAYLOAD); + KUNIT_EXPECT_EQ(test, + aconnector->mst_status & MST_ALLOCATE_NEW_PAYLOAD, 0); + /* drm_dp_remove_payload_part2() releases the payload's slot */ + KUNIT_EXPECT_EQ(test, payload->vc_start_slot, -1); +} + +/* Tests for dm_helpers_dp_write_dsc_enable() */ + +/** + * dm_test_dp_write_dsc_enable_mst_no_aux - Test MST early return without dsc_aux + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_no_aux(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->dm_stream_context = aconnector; + stream->signal = SIGNAL_TYPE_DISPLAY_PORT_MST; + aconnector->dsc_aux = NULL; + + /* MST signal with NULL dsc_aux → return false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); +} + +/** + * dm_test_dp_write_dsc_enable_non_dp - Test non-DP signal returns false + * @test: The KUnit test context + * + * For an HDMI signal neither the MST nor the DP/eDP block runs, so ret stays 0. + */ +static void dm_test_dp_write_dsc_enable_non_dp(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->dm_stream_context = aconnector; + stream->signal = SIGNAL_TYPE_HDMI_TYPE_A; + + /* Non-DP/MST signal → no DPCD write, ret stays 0 (false) */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); +} + +struct dm_test_dsc_aux_pair { + struct dm_test_synaptics_aux *main; + struct dm_test_synaptics_aux *passthrough; +}; + +static struct amdgpu_dm_connector *dm_test_alloc_dsc_connector(struct kunit *test, + struct dc_link *link) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->dc_link = link; + + return aconnector; +} + +static struct dc_stream_state *dm_test_alloc_dsc_stream(struct kunit *test, + struct dc_link *link, + enum signal_type signal) +{ + struct dc_stream_state *stream; + struct dc_sink *sink; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + stream->link = link; + stream->signal = signal; + stream->sink = sink; + sink->link = link; + + return stream; +} + +static struct dm_test_dsc_aux_pair dm_test_dp_write_dsc_enable_mst(struct kunit *test, + bool enable, + bool passthrough) +{ + struct dm_test_dsc_aux_pair aux_pair; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + struct dc_link *link; + bool ret; + + link = dm_kunit_alloc_link(test); + aconnector = dm_test_alloc_dsc_connector(test, link); + stream = dm_test_alloc_dsc_stream(test, link, SIGNAL_TYPE_DISPLAY_PORT_MST); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + aux_pair.main = dm_test_alloc_synaptics_aux_with_dev(test, NULL); + aux_pair.passthrough = dm_test_alloc_synaptics_aux_with_dev(test, NULL); + + stream->dm_stream_context = aconnector; + aconnector->dsc_aux = &aux_pair.main->aux; + aconnector->mst_output_port = port; + if (passthrough) + port->passthrough_aux = &aux_pair.passthrough->aux; + + ret = dm_helpers_dp_write_dsc_enable(NULL, stream, enable); + + KUNIT_EXPECT_TRUE(test, ret); + KUNIT_EXPECT_EQ(test, aux_pair.main->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, aux_pair.main->dsc_enable_values[0], enable ? 1 : 0); + KUNIT_EXPECT_EQ(test, aux_pair.passthrough->dsc_enable_writes, + passthrough ? 1U : 0U); + if (passthrough) + KUNIT_EXPECT_EQ(test, aux_pair.passthrough->dsc_enable_values[0], enable ? 2 : 0); + + return aux_pair; +} + +/** + * dm_test_dp_write_dsc_enable_mst_enable_decode_only - Test MST enable decoding write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_enable_decode_only(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, true, false); +} + +/** + * dm_test_dp_write_dsc_enable_mst_enable_passthrough - Test MST enable passthrough write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_enable_passthrough(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, true, true); +} + +/** + * dm_test_dp_write_dsc_enable_mst_disable_decode_only - Test MST disable decoding write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_disable_decode_only(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, false, false); +} + +/** + * dm_test_dp_write_dsc_enable_mst_disable_passthrough - Test MST disable passthrough write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_disable_passthrough(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, false, true); +} + +static void dm_test_dp_write_dsc_enable_sst(struct kunit *test, + enum display_dongle_type dongle_type, + bool enable, + u8 expected_value) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct dc_link *link; + bool ret; + + link = dm_kunit_alloc_link(test); + aconnector = dm_test_alloc_dsc_connector(test, link); + stream = dm_test_alloc_dsc_stream(test, link, SIGNAL_TYPE_DISPLAY_PORT); + fixture = dm_test_alloc_synaptics_aux_with_dev(test, NULL); + + stream->dm_stream_context = aconnector; + link->priv = aconnector; + link->dpcd_caps.dongle_type = dongle_type; + dm_test_current_aux_recorder = fixture; + aconnector->dm_dp_aux.aux.transfer = dm_test_current_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + + ret = dm_helpers_dp_write_dsc_enable(NULL, stream, enable); + + KUNIT_EXPECT_TRUE(test, ret); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], expected_value); +} + +/** + * dm_test_dp_write_dsc_enable_sst_rx_enable - Test SST RX enable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_sst_rx_enable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_NONE, true, 1); +} + +/** + * dm_test_dp_write_dsc_enable_sst_rx_disable - Test SST RX disable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_sst_rx_disable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_NONE, false, 0); +} + +/** + * dm_test_dp_write_dsc_enable_pcon_enable - Test DP-HDMI PCON enable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_pcon_enable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_DP_HDMI_CONVERTER, true, 1); +} + +/** + * dm_test_dp_write_dsc_enable_pcon_disable - Test DP-HDMI PCON disable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_pcon_disable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_DP_HDMI_CONVERTER, false, 0); +} + +/* Tests for dm_helpers_dp_handle_test_pattern_request() */ + +/** + * dm_test_dp_handle_test_pattern_no_pipe - Test no matching pipe returns false + * @test: The KUnit test context + */ +static void dm_test_dp_handle_test_pattern_no_pipe(struct kunit *test) +{ + union link_test_pattern test_pattern = {0}; + union test_misc test_params = {0}; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_state *state; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, link); + + ctx->dc = dc; + dc->current_state = state; + link->dc = dc; + link->priv = aconnector; + + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_handle_test_pattern_request(ctx, link, + test_pattern, + test_params)); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -3020,6 +3798,8 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv), KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv), KUNIT_CASE(dm_test_mst_start_top_mgr_boot), + KUNIT_CASE(dm_test_mst_start_top_mgr_set_mst_fail), + KUNIT_CASE(dm_test_mst_stop_top_mgr_active), /* dm_helpers_dp_write_hblank_reduction */ KUNIT_CASE(dm_test_dp_write_hblank_reduction_false), /* get_dsc_max_slices */ @@ -3056,12 +3836,22 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_smu_timeout_no_crash), KUNIT_CASE(dm_test_set_phyd32clk_no_crash), KUNIT_CASE(dm_test_mst_update_branch_bandwidth_no_crash), + /* dm_helpers_dp_mst_write_payload_allocation_table success path */ + KUNIT_CASE(dm_test_write_payload_alloc_table_enable), + KUNIT_CASE(dm_test_write_payload_alloc_table_disable), /* MST null-connector early returns */ KUNIT_CASE(dm_test_mst_write_payload_alloc_table_null_ctx), KUNIT_CASE(dm_test_mst_poll_for_act_null_ctx), /* dm_helpers_dp_mst_poll_for_allocation_change_trigger success/fail */ + KUNIT_CASE(dm_test_poll_for_act_no_mst_state), + KUNIT_CASE(dm_test_poll_for_act_success), + KUNIT_CASE(dm_test_poll_for_act_status_failed), KUNIT_CASE(dm_test_mst_send_payload_alloc_null_ctx), KUNIT_CASE(dm_test_mst_update_mgr_dealloc_null_ctx), + /* dm_helpers_dp_mst_send_payload_allocation failure path */ + KUNIT_CASE(dm_test_mst_send_payload_alloc_part2_fail), + /* dm_helpers_dp_mst_update_mst_mgr_for_deallocation success path */ + KUNIT_CASE(dm_test_mst_update_mgr_dealloc_success), /* dm_helpers_is_dp_sink_present */ KUNIT_CASE(dm_test_is_dp_sink_present_null_priv), /* dm_helpers_dmub_outbox_interrupt_control */ @@ -3087,6 +3877,22 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_mccs_vcp_set_null_link), KUNIT_CASE(dm_test_mccs_vcp_set_i2c_packet), KUNIT_CASE(dm_test_mccs_vcp_set_i2c_failure), + /* dm_helpers_construct_old_payload */ + KUNIT_CASE(dm_test_construct_old_payload_empty_list), + KUNIT_CASE(dm_test_construct_old_payload_intervening), + /* dm_helpers_dp_write_dsc_enable */ + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_no_aux), + KUNIT_CASE(dm_test_dp_write_dsc_enable_non_dp), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_enable_decode_only), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_enable_passthrough), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_disable_decode_only), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_disable_passthrough), + KUNIT_CASE(dm_test_dp_write_dsc_enable_sst_rx_enable), + KUNIT_CASE(dm_test_dp_write_dsc_enable_sst_rx_disable), + KUNIT_CASE(dm_test_dp_write_dsc_enable_pcon_enable), + KUNIT_CASE(dm_test_dp_write_dsc_enable_pcon_disable), + /* dm_helpers_dp_handle_test_pattern_request */ + KUNIT_CASE(dm_test_dp_handle_test_pattern_no_pipe), {} }; From 80e4717b4677bcc8a1eb19b1f763d13e1e227ee6 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jun 2026 23:50:10 -0600 Subject: [PATCH 049/155] drm/amd/display: Test irq funcs/suspend/workqueue [WHAT] Expand the amdgpu_dm_irq KUnit suite with the first batch of tests covering IRQ source-function setup, suspend/resume of the IRQ clients, and the HPD RX offload workqueue lifecycle. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 7 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 353 ++++++++++++++++++ 2 files changed, 360 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 551901c7598a..67e267a20f78 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -537,6 +537,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_disable(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_suspend); void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) { @@ -558,6 +559,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_early); void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) { @@ -586,6 +588,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_enable(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_late); /* * amdgpu_dm_irq_schedule_work - schedule all work items registered for the @@ -900,6 +903,8 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) adev->hpd_irq.num_types = adev->mode_info.num_hpd; adev->hpd_irq.funcs = &dm_hpd_irq_funcs; } +EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs); + void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { dc_interrupt_set(adev->dm.dc, @@ -1187,6 +1192,7 @@ struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(stru kfree(hpd_rx_offload_wq); return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_create_workqueue); void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) { @@ -1197,6 +1203,7 @@ void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) flush_workqueue(dm->hpd_rx_offload_wq[i].wq); } } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_work_suspend); STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index a73a6dd146d6..7ea6f93ca998 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -7,14 +7,23 @@ #include #include +#include +#include #include "dc.h" +#include "inc/core_types.h" +#include "irq/irq_service.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" #include "amdgpu_dm_kunit_test_helpers.h" +#include "dc_dmub_srv.h" +#include "ivsrcid/ivsrcid_vislands30.h" +#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" +#include "link_service.h" #include "dmub/dmub_srv.h" +#include "dal_asic_id.h" static void dm_test_irq_handler(void *arg) { @@ -31,6 +40,22 @@ static void dm_test_crtc_list_del(void *data) list_del_init(&acrtc->base.head); } +struct dm_test_hpd_rx_wq_ctx { + struct hpd_rx_irq_offload_work_queue *wq; + int count; +}; + +static void dm_test_destroy_hpd_rx_wq(void *data) +{ + struct dm_test_hpd_rx_wq_ctx *ctx = data; + int i; + + for (i = 0; i < ctx->count; i++) + if (ctx->wq[i].wq) + destroy_workqueue(ctx->wq[i].wq); + kfree(ctx->wq); +} + /* Tests for amdgpu_dm_hpd_to_dal_irq_source() */ /** @@ -842,6 +867,318 @@ static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test) KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0)); } +/* Tests for amdgpu_dm_set_irq_funcs() */ + +/** + * dm_test_set_irq_funcs - Test irq src funcs and counts are populated + * @test: The KUnit test context + */ +static void dm_test_set_irq_funcs(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 6; + adev->mode_info.num_hpd = 4; + + amdgpu_dm_set_irq_funcs(adev); + + KUNIT_EXPECT_EQ(test, adev->crtc_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->vline0_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->vupdate_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->pageflip_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->dmub_outbox_irq.num_types, 1); + KUNIT_EXPECT_EQ(test, adev->dmub_trace_irq.num_types, 1); + KUNIT_EXPECT_EQ(test, adev->hpd_irq.num_types, 4); + + KUNIT_EXPECT_TRUE(test, adev->crtc_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->vline0_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->dmub_outbox_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->vupdate_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->dmub_trace_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->pageflip_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->hpd_irq.funcs != NULL); +} + +/* Tests for amdgpu_dm_irq_suspend()/resume_early()/resume_late() */ + +/** + * dm_test_irq_suspend_empty - Test suspend walks empty handler tables safely + * @test: The KUnit test context + */ +static void dm_test_irq_suspend_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* + * With no registered handlers the HW dc_interrupt_set() calls are + * skipped, so suspend must complete without touching the (absent) DC. + */ + amdgpu_dm_irq_suspend(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_low_tab[src])); + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_high_tab[src])); + } +} + +/** + * dm_test_irq_resume_early_empty - Test early resume walks empty tables safely + * @test: The KUnit test context + */ +static void dm_test_irq_resume_early_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_resume_early(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_high_tab[src])); +} + +/** + * dm_test_irq_resume_late_empty - Test late resume walks empty tables safely + * @test: The KUnit test context + */ +static void dm_test_irq_resume_late_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_resume_late(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_low_tab[src])); +} + +/** + * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set path + * @test: The KUnit test context + * + * Registers a low-context HPD handler so the handler list is non-empty, + * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with + * no DC) and flush_work() on the registered handler. + */ +static void dm_test_irq_suspend_registered(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + void *handler; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_suspend(adev); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); +} + +/** + * dm_test_irq_suspend_disables_polling - Test suspend disables KMS polling + * @test: The KUnit test context + * + * With KMS polling active, suspend must take the poll-disable branch. + * drm_kms_helper_poll_disable() only cancels the poll work; it leaves the + * poll_enabled flag set (cleared later by drm_kms_helper_poll_fini()). + */ +static void dm_test_irq_suspend_disables_polling(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* Enable KMS polling so suspend takes the poll-disable branch. */ + drm_kms_helper_poll_init(&adev->ddev); + KUNIT_ASSERT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + amdgpu_dm_irq_suspend(adev); + + KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + drm_kms_helper_poll_fini(&adev->ddev); +} + +/** + * dm_test_irq_resume_early_registered - Test early resume reaches dc_interrupt_set + * @test: The KUnit test context + * + * Registers a low-context HPD RX handler so early resume calls + * dc_interrupt_set() for the short-pulse interrupt source. + */ +static void dm_test_irq_resume_early_registered(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + void *handler; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1RX; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_resume_early(adev); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1RX, + dm_test_irq_handler); +} + +/** + * dm_test_irq_resume_late_registered - Test late resume reaches dc_interrupt_set + * @test: The KUnit test context + * + * Registers a low-context HPD handler so late resume calls dc_interrupt_set() + * for the HPD interrupt source. + */ +static void dm_test_irq_resume_late_registered(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + void *handler; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_resume_late(adev); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); +} + +/** + * dm_test_irq_resume_late_enables_polling - Test late resume re-enables polling + * @test: The KUnit test context + * + * With KMS polling active, late resume must take the poll-enable branch and + * leave polling enabled. + */ +static void dm_test_irq_resume_late_enables_polling(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* Enable KMS polling so resume_late takes the poll-enable branch. */ + drm_kms_helper_poll_init(&adev->ddev); + KUNIT_ASSERT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + amdgpu_dm_irq_resume_late(adev); + + KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + drm_kms_helper_poll_fini(&adev->ddev); +} + +/* Tests for amdgpu_dm_hpd_rx_irq_create_workqueue() */ + +/** + * dm_test_hpd_rx_irq_create_workqueue - Test workqueue array creation + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_irq_create_workqueue(struct kunit *test) +{ + struct dm_test_hpd_rx_wq_ctx *ctx; + struct amdgpu_device *adev; + struct dc *dc; + int i; + + adev = dm_kunit_alloc_adev(test); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + dc->caps.max_links = 4; + adev->dm.dc = dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); + KUNIT_ASSERT_NOT_NULL(test, ctx->wq); + ctx->count = dc->caps.max_links; + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_hpd_rx_wq, ctx), 0); + + for (i = 0; i < dc->caps.max_links; i++) + KUNIT_EXPECT_TRUE(test, ctx->wq[i].wq != NULL); +} + +/* Tests for amdgpu_dm_hpd_rx_irq_work_suspend() */ + +/** + * dm_test_hpd_rx_irq_work_suspend_null - Test suspend with no work queue + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_irq_work_suspend_null(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + /* A NULL hpd_rx_offload_wq must be a safe no-op (DC untouched). */ + amdgpu_dm_hpd_rx_irq_work_suspend(dm); +} + +/** + * dm_test_hpd_rx_irq_work_suspend_flushes - Test suspend flushes queues + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_irq_work_suspend_flushes(struct kunit *test) +{ + struct dm_test_hpd_rx_wq_ctx *ctx; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + dc->caps.max_links = 2; + adev->dm.dc = dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); + KUNIT_ASSERT_NOT_NULL(test, ctx->wq); + ctx->count = dc->caps.max_links; + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_hpd_rx_wq, ctx), 0); + + adev->dm.hpd_rx_offload_wq = ctx->wq; + + amdgpu_dm_hpd_rx_irq_work_suspend(&adev->dm); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -894,6 +1231,22 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_match), KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_null), KUNIT_CASE(dm_test_get_crtc_by_otg_inst_empty_list), + /* amdgpu_dm_set_irq_funcs */ + KUNIT_CASE(dm_test_set_irq_funcs), + /* amdgpu_dm_irq_suspend/resume_early/resume_late */ + KUNIT_CASE(dm_test_irq_suspend_empty), + KUNIT_CASE(dm_test_irq_resume_early_empty), + KUNIT_CASE(dm_test_irq_resume_late_empty), + KUNIT_CASE(dm_test_irq_suspend_registered), + KUNIT_CASE(dm_test_irq_suspend_disables_polling), + KUNIT_CASE(dm_test_irq_resume_early_registered), + KUNIT_CASE(dm_test_irq_resume_late_registered), + KUNIT_CASE(dm_test_irq_resume_late_enables_polling), + /* amdgpu_dm_hpd_rx_irq_create_workqueue */ + KUNIT_CASE(dm_test_hpd_rx_irq_create_workqueue), + /* amdgpu_dm_hpd_rx_irq_work_suspend */ + KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_null), + KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_flushes), {} }; From a9011c014c44542bedcaca79566c07c8041070d3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jun 2026 23:50:51 -0600 Subject: [PATCH 050/155] drm/amd/display: Test irq state setters and work [WHAT] Add KUnit tests for the per-source IRQ state callbacks (HPD, pflip, CRTC, vline0, vupdate, dmub outbox/trace), the immediate and scheduled IRQ work paths, and amdgpu_dm_outbox_init. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 28 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 35 ++ .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 582 ++++++++++++++++++ 3 files changed, 636 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 67e267a20f78..4ae0f91b347d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -594,7 +594,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_late); * amdgpu_dm_irq_schedule_work - schedule all work items registered for the * "irq_source". */ -static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { struct list_head *handler_list = &adev->dm.irq_handler_list_low_tab[irq_source]; @@ -643,12 +643,13 @@ static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, irq_source); } } +EXPORT_IF_KUNIT(amdgpu_dm_irq_schedule_work); /* * amdgpu_dm_irq_immediate_work * Callback high irq work immediately, don't send to work queue */ -static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { struct amdgpu_dm_irq_handler_data *handler_data; @@ -667,6 +668,7 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); /** * amdgpu_dm_irq_handler - Generic DM IRQ handler @@ -719,7 +721,7 @@ STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int } EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source); -static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) @@ -730,6 +732,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, src, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); static inline int dm_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, @@ -765,7 +768,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, return 0; } -static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -778,8 +781,9 @@ static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, IRQ_TYPE_PFLIP, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_pflip_irq_state); -static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -792,8 +796,9 @@ static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VBLANK, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_crtc_irq_state); -static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -806,8 +811,9 @@ static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VLINE0, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_vline0_irq_state); -static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -818,8 +824,9 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, irq_source, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); -static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -832,8 +839,9 @@ static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VUPDATE, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_vupdate_irq_state); -static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) @@ -844,6 +852,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, irq_source, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = { .set = amdgpu_dm_set_crtc_irq_state, @@ -911,6 +920,7 @@ void amdgpu_dm_outbox_init(struct amdgpu_device *adev) DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } +EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); /** * amdgpu_dm_hpd_init - hpd setup callback. diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index bccb5d354a9f..15e024947970 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -123,9 +123,44 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct amdgpu_irq_src; +enum amdgpu_interrupt_state; + enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2); const char *dmub_notification_type_str(enum dmub_notification_type e); +int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source); +void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 7ea6f93ca998..7537d30cd983 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -33,6 +33,104 @@ static void dm_test_irq_handler_alt(void *arg) { } +static void dm_test_irq_handler_count(void *arg) +{ + int *count = arg; + + if (count) + (*count)++; +} + +static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) +{ + struct dc_context *ctx; + struct dc *dc; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc->ctx = ctx; + ctx->dc = dc; + + return dc; +} + +static enum dc_irq_source dm_test_to_dal_irq_source_dcn10( + struct irq_service *irq_service, + uint32_t src_id, + uint32_t ext_id) +{ + switch (src_id) { + case DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP: + return DC_IRQ_SOURCE_VBLANK1; + case DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT: + return DC_IRQ_SOURCE_VUPDATE1; + case DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT: + return DC_IRQ_SOURCE_PFLIP1; + case DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT: + return DC_IRQ_SOURCE_DMCUB_OUTBOX; + default: + return DC_IRQ_SOURCE_INVALID; + } +} + +static const struct irq_service_funcs dm_test_irq_service_funcs_dcn10 = { + .to_dal_irq_source = dm_test_to_dal_irq_source_dcn10 +}; + +static bool dm_test_irq_src_set(struct irq_service *irq_service, + const struct irq_source_info *info, bool enable) +{ + return true; +} + +static bool dm_test_irq_src_ack(struct irq_service *irq_service, + const struct irq_source_info *info) +{ + return true; +} + +/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +static struct irq_source_info_funcs dm_test_irq_src_funcs = { + .set = dm_test_irq_src_set, + .ack = dm_test_irq_src_ack, +}; + +static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, + const struct irq_service_funcs *funcs) +{ + struct irq_source_info *info; + struct resource_pool *res_pool; + struct irq_service *irqs; + struct dc *dc; + int i; + + dc = dm_test_alloc_dc_with_ctx(test); + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); + + /* + * Populate the per-source info table so dc_interrupt_set()/_ack() + * succeed without touching hardware registers. + */ + info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); + for (i = 0; i < DAL_IRQ_SOURCES_NUMBER; i++) + info[i].funcs = &dm_test_irq_src_funcs; + + irqs->funcs = funcs; + irqs->info = info; + res_pool->irqs = irqs; + dc->res_pool = res_pool; + + return dc; +} + static void dm_test_crtc_list_del(void *data) { struct amdgpu_crtc *acrtc = data; @@ -1179,6 +1277,462 @@ static void dm_test_hpd_rx_irq_work_suspend_flushes(struct kunit *test) amdgpu_dm_hpd_rx_irq_work_suspend(&adev->dm); } +/* Tests for CRTC-based irq state callbacks (no-CRTC early return) */ + +/** + * dm_test_set_crtc_irq_state_no_crtc - Test crtc irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* mode_info.crtcs[0] is NULL -> returns 0 without dereferencing DC. */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_pflip_irq_state_no_crtc - Test pflip irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_pflip_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_pflip_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/** + * dm_test_set_vline0_irq_state_no_crtc - Test vline0 irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_vline0_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vline0_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_vupdate_irq_state_no_crtc - Test vupdate irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_vupdate_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vupdate_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/* Tests for CRTC-based irq state callbacks (dm_irq_state happy path) */ + +/** + * dm_test_set_crtc_irq_state_otg_disabled - Test crtc irq state with disabled OTG + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_otg_disabled(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* otg_inst == -1 short-circuits before computing the irq source. */ + acrtc->otg_inst = -1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_crtc_irq_state_enable - Test crtc irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* + * otg_inst >= 0 computes the irq source and reaches the NULL-safe + * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + */ + acrtc->otg_inst = 3; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_pflip_irq_state_disable - Test pflip irq state reaches DC (disable) + * @test: The KUnit test context + */ +static void dm_test_set_pflip_irq_state_disable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* The disable state exercises the st == false path. */ + acrtc->otg_inst = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_pflip_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/** + * dm_test_set_vline0_irq_state_enable - Test vline0 irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_vline0_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->otg_inst = 0; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vline0_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_vupdate_irq_state_enable - Test vupdate irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->otg_inst = 2; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vupdate_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_crtc_irq_state_allows_idle - Test the idle-optimization branch + * @test: The KUnit test context + * + * With a non-NULL DC that advertises IPS support and currently allows idle + * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before + * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let dc_interrupt_set() succeed. + */ +static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + struct dal_logger *logger; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + + /* DC_LOG_* dereferences ctx->logger->dev, so wire a real drm device. */ + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + logger->dev = &adev->ddev; + dc->ctx->logger = logger; + + dc->caps.ips_support = true; + dc->idle_optimizations_allowed = true; + /* Keep dc_allow_idle_optimizations() a safe early return. */ + dc->debug.disable_idle_power_optimizations = true; + adev->dm.dc = dc; + + acrtc->otg_inst = 0; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/* Tests for amdgpu_dm_irq_immediate_work() */ + +/** + * dm_test_irq_immediate_work_empty - Test immediate work on empty high table + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* No registered high-context handlers: must be a safe no-op. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_irq_immediate_work_invokes_handler - Test immediate work calls handler + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_invokes_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* High-context handlers are invoked synchronously, in-place. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD1); + KUNIT_EXPECT_EQ(test, count, 1); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, dm_test_irq_handler_count); +} + +/** + * dm_test_irq_immediate_work_invokes_all - Test immediate work calls all handlers + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_invokes_all(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD2; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Both registered high-context handlers must run. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD2); + KUNIT_EXPECT_EQ(test, count, 2); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, dm_test_irq_handler_count); + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, dm_test_irq_handler_count); +} + +/* Tests for amdgpu_dm_irq_schedule_work() */ + +/** + * dm_test_irq_schedule_work_empty - Test schedule work on empty low table + * @test: The KUnit test context + */ +static void dm_test_irq_schedule_work_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* Empty handler list: schedule_work returns immediately. */ + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_irq_schedule_work_queues_handler - Test schedule work runs handler + * @test: The KUnit test context + */ +static void dm_test_irq_schedule_work_queues_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + + /* + * Low-context work runs asynchronously on system_highpri_wq. + * amdgpu_dm_irq_fini() flushes each pending work item before freeing + * the handlers, so the handler is guaranteed to have run afterwards. + */ + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, count, 1); +} + +/** + * dm_test_irq_schedule_work_requeue_fallback - Test the re-queue fallback path + * @test: The KUnit test context + * + * The first schedule queues the handler's work item. Issuing a second + * schedule before the work has run makes queue_work() fail for the + * still-pending item, forcing amdgpu_dm_irq_schedule_work() into the fallback + * that allocates and queues a fresh handler copy. Both work items run when + * amdgpu_dm_irq_fini() flushes the queue, so the handler fires twice. + */ +static void dm_test_irq_schedule_work_requeue_fallback(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, count, 2); +} + +/* Tests for amdgpu_dm_set_hpd_irq_state() */ + +/** + * dm_test_set_hpd_irq_state_null_dc - Test HPD irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* dc_interrupt_set() is a no-op when dc is NULL, so both states + * return 0 without dereferencing the (absent) DC. + */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_set_dmub_outbox_irq_state() */ + +/** + * dm_test_set_dmub_outbox_irq_state_null_dc - Test outbox irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_dmub_outbox_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_outbox_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_outbox_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_set_dmub_trace_irq_state() */ + +/** + * dm_test_set_dmub_trace_irq_state_null_dc - Test trace irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_dmub_trace_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_trace_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_trace_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_outbox_init() */ + +/** + * dm_test_outbox_init_null_dc - Test outbox init is a safe no-op with no DC + * @test: The KUnit test context + */ +static void dm_test_outbox_init_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + amdgpu_dm_outbox_init(adev); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -1247,6 +1801,34 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_rx_irq_work_suspend */ KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_null), KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_flushes), + /* CRTC-based irq state callbacks (no-CRTC early return) */ + KUNIT_CASE(dm_test_set_crtc_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_pflip_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_vline0_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_vupdate_irq_state_no_crtc), + /* CRTC-based irq state callbacks (dm_irq_state happy path) */ + KUNIT_CASE(dm_test_set_crtc_irq_state_otg_disabled), + KUNIT_CASE(dm_test_set_crtc_irq_state_enable), + KUNIT_CASE(dm_test_set_pflip_irq_state_disable), + KUNIT_CASE(dm_test_set_vline0_irq_state_enable), + KUNIT_CASE(dm_test_set_vupdate_irq_state_enable), + KUNIT_CASE(dm_test_set_crtc_irq_state_allows_idle), + /* amdgpu_dm_irq_immediate_work */ + KUNIT_CASE(dm_test_irq_immediate_work_empty), + KUNIT_CASE(dm_test_irq_immediate_work_invokes_handler), + KUNIT_CASE(dm_test_irq_immediate_work_invokes_all), + /* amdgpu_dm_irq_schedule_work */ + KUNIT_CASE(dm_test_irq_schedule_work_empty), + KUNIT_CASE(dm_test_irq_schedule_work_queues_handler), + KUNIT_CASE(dm_test_irq_schedule_work_requeue_fallback), + /* amdgpu_dm_set_hpd_irq_state */ + KUNIT_CASE(dm_test_set_hpd_irq_state_null_dc), + /* amdgpu_dm_set_dmub_outbox_irq_state */ + KUNIT_CASE(dm_test_set_dmub_outbox_irq_state_null_dc), + /* amdgpu_dm_set_dmub_trace_irq_state */ + KUNIT_CASE(dm_test_set_dmub_trace_irq_state_null_dc), + /* amdgpu_dm_outbox_init */ + KUNIT_CASE(dm_test_outbox_init_null_dc), {} }; From 747e1b0dc7b262d2dfff7261783330f32594b20f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 25 Jun 2026 13:07:14 -0600 Subject: [PATCH 051/155] drm/amd/display: Test HPD init/fini Add KUnit coverage for amdgpu_dm_hpd_init() and amdgpu_dm_hpd_fini(): empty connector list, the per-connector HW fallback path, the analog polling enable/disable path, and the base-driver irq-ref path. Export both functions for KUnit and add shared drm_connector test helpers. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 2 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 187 +++++++++++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 4ae0f91b347d..0e2a040c4023 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1003,6 +1003,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) if (use_polling) drm_kms_helper_poll_init(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_init); /** * amdgpu_dm_hpd_fini - hpd tear down callback. @@ -1056,6 +1057,7 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_fini(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_fini); /* ========== HPD handling ========== */ static void force_connector_state( diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 7537d30cd983..8c58523ef1bb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -154,6 +154,19 @@ static void dm_test_destroy_hpd_rx_wq(void *data) kfree(ctx->wq); } +static const struct drm_connector_funcs dm_test_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, +}; + +static void dm_test_connector_cleanup(void *data) +{ + drm_connector_cleanup(data); +} + /* Tests for amdgpu_dm_hpd_to_dal_irq_source() */ /** @@ -917,11 +930,9 @@ static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test) acrtc_b->otg_inst = 3; list_add_tail(&acrtc_a->base.head, &drm->mode_config.crtc_list); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, - acrtc_a), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, acrtc_a), 0); list_add_tail(&acrtc_b->base.head, &drm->mode_config.crtc_list); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, - acrtc_b), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, acrtc_b), 0); KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 3), acrtc_b); } @@ -946,8 +957,7 @@ static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) acrtc->otg_inst = 2; list_add_tail(&acrtc->base.head, &drm->mode_config.crtc_list); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, - acrtc), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, acrtc), 0); KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 5)); } @@ -1733,6 +1743,165 @@ static void dm_test_outbox_init_null_dc(struct kunit *test) amdgpu_dm_outbox_init(adev); } +/* Tests for amdgpu_dm_hpd_init()/amdgpu_dm_hpd_fini() */ + +/** + * dm_test_hpd_init_empty_connectors - Test HPD init with no connectors + * @test: The KUnit test context + */ +static void dm_test_hpd_init_empty_connectors(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + /* + * With an empty connector list the per-connector loop is skipped and + * the initial clear loop relies on dc_interrupt_set() being a no-op + * for a NULL dc, so init must complete without touching the DC. + */ + amdgpu_dm_hpd_init(adev); +} + +/** + * dm_test_hpd_fini_empty_connectors - Test HPD fini with no connectors + * @test: The KUnit test context + */ +static void dm_test_hpd_fini_empty_connectors(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + /* Empty connector list and disabled polling: fini is a safe no-op. */ + amdgpu_dm_hpd_fini(adev); +} + +/** + * dm_test_hpd_init_fini_with_connectors - Test HPD init/fini walk connectors + * @test: The KUnit test context + */ +static void dm_test_hpd_init_fini_with_connectors(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct drm_connector *wbconn; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + + /* + * num_hpd = 0 forces irq_type >= num_hpd so the loop takes the HW + * fallback (dc_interrupt_set()) instead of amdgpu_irq_get(); with a + * NULL dc that fallback is a safe no-op. + */ + adev->mode_info.num_hpd = 0; + + /* A writeback connector must be skipped by the per-connector loop. */ + wbconn = kunit_kzalloc(test, sizeof(*wbconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wbconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wbconn, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, wbconn), 0); + + /* A DisplayPort connector with a dc_link exercises the loop body. */ + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + aconn->dc_link = link; + + /* DC is absent (NULL), so the HW writes are NULL-safe no-ops. */ + amdgpu_dm_hpd_init(adev); + amdgpu_dm_hpd_fini(adev); +} + +/** + * dm_test_hpd_init_fini_analog_connector - Test HPD init/fini analog polling path + * @test: The KUnit test context + */ +static void dm_test_hpd_init_fini_analog_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + adev->mode_info.num_hpd = 0; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_VGA), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + /* An analog connector id makes the loop request polling. */ + link->link_id.id = CONNECTOR_ID_VGA; + aconn->dc_link = link; + + /* use_polling becomes true, so init must enable KMS polling. */ + amdgpu_dm_hpd_init(adev); + KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + /* fini must tear the polling back down. */ + amdgpu_dm_hpd_fini(adev); + KUNIT_EXPECT_FALSE(test, adev->ddev.mode_config.poll_enabled); +} + +/** + * dm_test_hpd_init_fini_irq_ref - Test HPD init/fini base-driver irq ref path + * @test: The KUnit test context + */ +static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + + /* + * num_hpd >= 1 makes irq_type (0) < num_hpd, so the loop takes the + * amdgpu_irq_get()/amdgpu_irq_put() branch instead of the + * dc_interrupt_set() fallback. The mock device has irq.installed == + * false, so both calls fail early with -ENOENT (logging an error) + * without touching the base-driver irq state. + */ + adev->mode_info.num_hpd = 1; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID; + aconn->dc_link = link; + + amdgpu_dm_hpd_init(adev); + amdgpu_dm_hpd_fini(adev); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -1829,6 +1998,12 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_set_dmub_trace_irq_state_null_dc), /* amdgpu_dm_outbox_init */ KUNIT_CASE(dm_test_outbox_init_null_dc), + /* amdgpu_dm_hpd_init/amdgpu_dm_hpd_fini */ + KUNIT_CASE(dm_test_hpd_init_empty_connectors), + KUNIT_CASE(dm_test_hpd_fini_empty_connectors), + KUNIT_CASE(dm_test_hpd_init_fini_with_connectors), + KUNIT_CASE(dm_test_hpd_init_fini_analog_connector), + KUNIT_CASE(dm_test_hpd_init_fini_irq_ref), {} }; From 4c28526e9a1089ff86db5a2680a4fa3897ae477c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 25 Jun 2026 13:07:40 -0600 Subject: [PATCH 052/155] drm/amd/display: Test HPD interrupt handling Add KUnit coverage for the HPD interrupt-handling helpers: the HPD-RX offload worker, the HDMI HPD debounce worker, handle_hpd_irq_helper(), handle_hpd_irq(), schedule_hpd_rx_offload_work() and handle_hpd_rx_irq(). Expose these statics for KUnit and add the stub link-service callbacks, sink helpers and fixtures the tests rely on. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 16 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 8 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 930 ++++++++++++++++++ 3 files changed, 949 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 0e2a040c4023..adac23be2972 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1075,7 +1075,7 @@ static void force_connector_state( mutex_unlock(&aconnector->hpd_lock); } -static void dm_handle_hpd_rx_offload_work(struct work_struct *work) +STATIC_IFN_KUNIT void dm_handle_hpd_rx_offload_work(struct work_struct *work) { struct hpd_rx_irq_offload_work *offload_work; struct amdgpu_dm_connector *aconnector; @@ -1168,6 +1168,7 @@ static void dm_handle_hpd_rx_offload_work(struct work_struct *work) kfree(offload_work); } +EXPORT_IF_KUNIT(dm_handle_hpd_rx_offload_work); struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) { @@ -1309,8 +1310,9 @@ void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work) dc_allow_idle_optimizations(dc, true); } } +EXPORT_IF_KUNIT(amdgpu_dm_hdmi_hpd_debounce_work); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, enum dc_detect_reason reason) { struct drm_connector *connector = &aconnector->base; @@ -1408,16 +1410,18 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, } } } +EXPORT_IF_KUNIT(handle_hpd_irq_helper); -static void handle_hpd_irq(void *param) +STATIC_IFN_KUNIT void handle_hpd_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); } +EXPORT_IF_KUNIT(handle_hpd_irq); -static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, +STATIC_IFN_KUNIT void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, union hpd_irq_data hpd_irq_data) { struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work); @@ -1435,8 +1439,9 @@ static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_ queue_work(offload_wq->wq, &offload_work->work); drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); } +EXPORT_IF_KUNIT(schedule_hpd_rx_offload_work); -static void handle_hpd_rx_irq(void *param) +STATIC_IFN_KUNIT void handle_hpd_rx_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; struct drm_connector *connector = &aconnector->base; @@ -1569,6 +1574,7 @@ static void handle_hpd_rx_irq(void *param) mutex_unlock(&aconnector->hpd_lock); } +EXPORT_IF_KUNIT(handle_hpd_rx_irq); /** * dmub_hpd_callback - DMUB HPD interrupt processing callback. diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 15e024947970..e01b0856f91f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -161,6 +161,14 @@ void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, enum dc_irq_source irq_source); void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, enum dc_irq_source irq_source); +void dm_handle_hpd_rx_offload_work(struct work_struct *work); +void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason); +void handle_hpd_irq(void *param); +void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, + struct hpd_rx_irq_offload_work_queue *offload_wq, + union hpd_irq_data hpd_irq_data); +void handle_hpd_rx_irq(void *param); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 8c58523ef1bb..fa161d0ecfd9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -24,6 +24,7 @@ #include "link_service.h" #include "dmub/dmub_srv.h" #include "dal_asic_id.h" +#include "../../amdgpu/amdgpu_reset.h" static void dm_test_irq_handler(void *arg) { @@ -41,6 +42,148 @@ static void dm_test_irq_handler_count(void *arg) (*count)++; } +static bool dm_test_detect_connection_none(struct dc_link *link, + enum dc_connection_type *type) +{ + *type = dc_connection_none; + + return true; +} + +static bool dm_test_detect_link_false(struct dc_link *link, + enum dc_detect_reason reason) +{ + return false; +} + +static bool dm_test_detect_connection_single(struct dc_link *link, + enum dc_connection_type *type) +{ + *type = dc_connection_single; + + return true; +} + +/* Recording stubs for the dm_handle_hpd_rx_offload_work() DP-IRQ branches. */ +static int dm_test_automated_test_count; +static int dm_test_handle_link_loss_count; + +static void dm_test_dp_handle_automated_test(struct dc_link *link) +{ + dm_test_automated_test_count++; +} + +static void dm_test_dp_handle_link_loss(struct dc_link *link) +{ + dm_test_handle_link_loss_count++; +} + +static bool dm_test_dp_parse_link_loss_true(struct dc_link *link, + union hpd_irq_data *hpd_irq_dpcd_data) +{ + return true; +} + +static bool dm_test_dp_should_allow_hpd_rx_irq_true(const struct dc_link *link) +{ + return true; +} + +static enum dc_status dm_test_dp_read_hpd_rx_irq_data_ok(struct dc_link *link, + union hpd_irq_data *irq_data) +{ + return DC_OK; +} + +/* + * Allocate a refcounted dc_sink for tests without pulling in the DC-core + * dc_sink_create()/dc_sink_release() symbols. Production code under test still + * uses dc_sink_retain()/dc_sink_release() (resolved inside the amdgpu module). + * Use kzalloc (not kunit_kzalloc) so the final kref_put frees it exactly once. + */ +static struct dc_sink *dm_test_sink_create(struct dc_link *link) +{ + struct dc_sink *sink = kzalloc(sizeof(*sink), GFP_KERNEL); + + if (!sink) + return NULL; + + sink->link = link; + sink->ctx = link->ctx; + kref_init(&sink->refcount); + + return sink; +} + +static void dm_test_sink_free(struct kref *kref) +{ + struct dc_sink *sink = container_of(kref, struct dc_sink, refcount); + + kfree(sink->dc_container_id); + kfree(sink); +} + +static void dm_test_sink_release(struct dc_sink *sink) +{ + kref_put(&sink->refcount, dm_test_sink_free); +} + +static bool dm_test_handle_hpd_rx_no_work(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = false; + + return false; +} + +static bool dm_test_handle_hpd_rx_automated(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = true; + hpd_irq_data->bytes.device_service_irq.bits.AUTOMATED_TEST = 1; + + return false; +} + +static bool dm_test_handle_hpd_rx_msg_rdy(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = true; + hpd_irq_data->bytes.device_service_irq.bits.UP_REQ_MSG_RDY = 1; + + return false; +} + +static bool dm_test_handle_hpd_rx_link_loss(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = true; + *has_left_work = true; + + return false; +} + +static bool dm_test_allow_hpd_rx_irq_true(const struct dc_link *link) +{ + return true; +} + + static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) { struct dc_context *ctx; @@ -1902,6 +2045,773 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) amdgpu_dm_hpd_fini(adev); } +/* Tests for dm_handle_hpd_rx_offload_work() */ + +/** + * dm_test_hpd_rx_offload_work_no_connector - Test missing connector early exit + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_offload_work_no_connector(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); +} + +/** + * dm_test_hpd_rx_offload_work_no_connection - Test no connection early exit + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_offload_work_no_connection(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->link_srv = link_srv; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->dc = dc; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); +} + +/** + * dm_test_hpd_rx_offload_work_automated_test - Test AUTOMATED_TEST branch + * @test: The KUnit test context + * + * With a present connection and the AUTOMATED_TEST service-IRQ bit set, the + * worker runs dc_link_dp_handle_automated_test() (stubbed via link_srv) and + * writes the test response with core_link_write_dpcd(). timing_changed is left + * false so force_connector_state() (which needs a registered DRM device) is + * skipped, and aux_access_disabled makes the DPCD write a safe no-op. + */ +static void dm_test_hpd_rx_offload_work_automated_test(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + dm_test_automated_test_count = 0; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + adev->reset_domain = kunit_kzalloc(test, sizeof(*adev->reset_domain), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->reset_domain); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + aconn->timing_changed = false; + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_single; + link_srv->dp_handle_automated_test = dm_test_dp_handle_automated_test; + dc->link_srv = link_srv; + dc->ctx = ctx; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->aux_access_disabled = true; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST = 1; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); + + KUNIT_EXPECT_EQ(test, dm_test_automated_test_count, 1); +} + +/** + * dm_test_hpd_rx_offload_work_link_loss - Test link-loss branch + * @test: The KUnit test context + * + * With a present non-eDP connection and no service-IRQ bits set, the worker + * takes the else-if link-loss path: dc_link_check_link_loss_status() and + * dc_link_dp_allow_hpd_rx_irq() gate entry, then dc_link_dp_read_hpd_rx_irq_data() + * returns DC_OK and a second link-loss check triggers dc_link_dp_handle_link_loss(). + * All four are stubbed via link_srv. + */ +static void dm_test_hpd_rx_offload_work_link_loss(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + dm_test_handle_link_loss_count = 0; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + adev->reset_domain = kunit_kzalloc(test, sizeof(*adev->reset_domain), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->reset_domain); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_single; + link_srv->dp_parse_link_loss_status = dm_test_dp_parse_link_loss_true; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_dp_should_allow_hpd_rx_irq_true; + link_srv->dp_read_hpd_rx_irq_data = dm_test_dp_read_hpd_rx_irq_data_ok; + link_srv->dp_handle_link_loss = dm_test_dp_handle_link_loss; + dc->link_srv = link_srv; + dc->ctx = ctx; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); + + KUNIT_EXPECT_EQ(test, dm_test_handle_link_loss_count, 1); +} + +/* Tests for amdgpu_dm_hdmi_hpd_debounce_work() */ + +/** + * dm_test_hdmi_hpd_debounce_detect_false - Test debounce false detect path + * @test: The KUnit test context + */ +static void dm_test_hdmi_hpd_debounce_detect_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + amdgpu_dm_hdmi_hpd_debounce_work(&aconn->hdmi_hpd_debounce_work.work); + KUNIT_EXPECT_NULL(test, aconn->hdmi_prev_sink); +} + +/** + * dm_test_hdmi_hpd_debounce_reallow_idle - Test debounce idle/sink-release tail + * @test: The KUnit test context + * + * With detection stubbed to return false, the if (ret) block is skipped, but + * the function tail is still exercised: IPS support plus an idle-allowed dmub + * makes reallow_idle true so dc_allow_idle_optimizations() runs on both entry + * and exit (disable_idle_power_optimizations keeps those safe early returns), + * and a cached hdmi_prev_sink is released and cleared. + */ +static void dm_test_hdmi_hpd_debounce_reallow_idle(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct dc_dmub_srv *dmub_srv; + struct amdgpu_device *adev; + struct dal_logger *logger; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* DC_LOG_* dereferences ctx->logger->dev, so wire a real drm device. */ + logger->dev = &adev->ddev; + dc->ctx->logger = logger; + + /* Make reallow_idle true and keep dc_allow_idle*() a safe early return. */ + dmub_srv->idle_allowed = true; + dc->ctx->dmub_srv = dmub_srv; + dc->caps.ips_support = true; + dc->debug.disable_idle_power_optimizations = true; + + /* A cached previous sink must be released and cleared. */ + aconn->hdmi_prev_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink); + + amdgpu_dm_hdmi_hpd_debounce_work(&aconn->hdmi_hpd_debounce_work.work); + KUNIT_EXPECT_NULL(test, aconn->hdmi_prev_sink); +} + +/* Tests for handle_hpd_irq()/handle_hpd_irq_helper() */ + +/** + * dm_test_handle_hpd_irq_disabled - Test HPD helper returns when disabled + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_irq_disabled(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + adev->dm.disable_hpd_irq = true; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + dc->ctx = ctx; + ctx->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + aconn->fake_enable = true; + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + KUNIT_EXPECT_TRUE(test, aconn->fake_enable); + + handle_hpd_irq(aconn); + KUNIT_EXPECT_TRUE(test, aconn->fake_enable); +} + +/** + * dm_test_handle_hpd_irq_helper_debounce_schedule - Test HDMI debounce branch + * @test: The KUnit test context + * + * An HDMI link reporting a disconnect (connection type none) with a non-zero + * debounce delay and a cached local_sink must take the debounce branch: it + * caches local_sink in hdmi_prev_sink and schedules the delayed debounce work + * instead of detecting immediately. + */ +static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* HDMI signal + debounce delay + cached sink -> debounce branch. */ + aconn->hdmi_hpd_debounce_delay_ms = 100; + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + link->local_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link->local_sink); + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + /* local_sink is cached for later comparison by the debounce work. */ + KUNIT_EXPECT_PTR_EQ(test, aconn->hdmi_prev_sink, link->local_sink); + + cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work); + dm_test_sink_release(aconn->hdmi_prev_sink); + dm_test_sink_release(link->local_sink); +} + +/** + * dm_test_handle_hpd_irq_helper_debounce_release_prev - Test stale prev_sink + * @test: The KUnit test context + * + * When the debounce branch is taken and a stale hdmi_prev_sink is already + * cached from a previous HPD, it must be released before caching the current + * local_sink. This exercises the dc_sink_release() of the previous sink. + */ +static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* HDMI signal + debounce delay + cached sink -> debounce branch. */ + aconn->hdmi_hpd_debounce_delay_ms = 100; + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + link->local_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link->local_sink); + + /* Stale sink from a previous HPD must be released by the helper. */ + aconn->hdmi_prev_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink); + KUNIT_ASSERT_PTR_NE(test, aconn->hdmi_prev_sink, link->local_sink); + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + /* Stale sink replaced by the current local_sink. */ + KUNIT_EXPECT_PTR_EQ(test, aconn->hdmi_prev_sink, link->local_sink); + + cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work); + dm_test_sink_release(aconn->hdmi_prev_sink); + dm_test_sink_release(link->local_sink); +} + +/** + * dm_test_handle_hpd_irq_helper_detect_false - Test immediate detect branch + * @test: The KUnit test context + * + * With no force, no debounce, and detection stubbed to report no connection, + * the helper takes the else branch: dc_exit_ips_for_hw_access() is a safe + * no-op (no IPS support) and dc_link_detect() returns false, so the connected + * if (ret) block is skipped. fake_enable must still be cleared. + */ +static void dm_test_handle_hpd_irq_helper_detect_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + aconn->fake_enable = true; + + /* No debounce delay and no force -> immediate-detect else branch. */ + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + KUNIT_EXPECT_FALSE(test, aconn->fake_enable); +} + +/* Tests for handle_hpd_rx_irq()/schedule_hpd_rx_offload_work() */ + +/** + * dm_test_handle_hpd_rx_irq_disabled - Test HPDRX handler returns when disabled + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_rx_irq_disabled(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + adev->dm.disable_hpd_irq = true; + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + adev->dm.hpd_rx_offload_wq = offload_wq; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + aconn->dc_link = link; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_schedule_hpd_rx_offload_work - Test offload work is queued + * @test: The KUnit test context + */ +static void dm_test_schedule_hpd_rx_offload_work(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_device *adev; + union hpd_irq_data data = { 0 }; + + adev = dm_kunit_alloc_adev(test); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + offload_wq->wq = create_singlethread_workqueue("dm_irq_test_hpd_rx"); + KUNIT_ASSERT_NOT_NULL(test, offload_wq->wq); + + schedule_hpd_rx_offload_work(adev, offload_wq, data); + flush_workqueue(offload_wq->wq); + destroy_workqueue(offload_wq->wq); +} + +static void dm_test_destroy_offload_wq(void *data) +{ + struct workqueue_struct *wq = data; + + flush_workqueue(wq); + destroy_workqueue(wq); +} + +/* + * Build an aconnector wired for handle_hpd_rx_irq(): HPD enabled, an MST-root + * connector on an MST-branch link so the post-detect block and drm_dp_cec_irq + * are skipped, and a flushed offload work queue. Caller sets the link_srv + * stubs (dp_handle_hpd_rx_irq and, if needed, dp_should_allow_hpd_rx_irq). + */ +static struct amdgpu_dm_connector *dm_test_setup_hpd_rx_irq(struct kunit *test, + struct link_service **link_srv_out) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + offload_wq->wq = create_singlethread_workqueue("dm_irq_test_hpd_rx"); + KUNIT_ASSERT_NOT_NULL(test, offload_wq->wq); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_offload_wq, + offload_wq->wq), 0); + adev->dm.hpd_rx_offload_wq = offload_wq; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + aconn->mst_mgr.mst_state = true; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + link->type = dc_connection_mst_branch; + aconn->dc_link = link; + + *link_srv_out = link_srv; + + return aconn; +} + +/** + * dm_test_handle_hpd_rx_irq_no_left_work - Test HPDRX early out (no left work) + * @test: The KUnit test context + * + * When dc_link_handle_hpd_rx_irq() reports no left-over work, the handler + * jumps to out and returns without scheduling any offload work. + */ +static void dm_test_handle_hpd_rx_irq_no_left_work(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_handle_hpd_rx_irq_automated_test - Test HPDRX automated-test path + * @test: The KUnit test context + * + * The AUTOMATED_TEST device-service bit must schedule offload work and jump to + * out before the allow-hpd-rx-irq checks. + */ +static void dm_test_handle_hpd_rx_irq_automated_test(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_automated; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_handle_hpd_rx_irq_msg_rdy - Test HPDRX MST message-ready path + * @test: The KUnit test context + * + * With HPD RX IRQs allowed and an UP_REQ_MSG_RDY bit set, the handler must take + * the MST message-ready branch, mark is_handling_mst_msg_rdy_event and schedule + * offload work. + */ +static void dm_test_handle_hpd_rx_irq_msg_rdy(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_msg_rdy; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_allow_hpd_rx_irq_true; + + handle_hpd_rx_irq(aconn); + + KUNIT_EXPECT_TRUE(test, drm_to_adev(aconn->base.dev) + ->dm.hpd_rx_offload_wq->is_handling_mst_msg_rdy_event); +} + +/** + * dm_test_handle_hpd_rx_irq_link_loss - Test HPDRX link-loss path + * @test: The KUnit test context + * + * With HPD RX IRQs allowed and a reported link loss (no message-ready bits), + * the handler must take the link-loss branch, mark is_handling_link_loss and + * schedule offload work. + */ +static void dm_test_handle_hpd_rx_irq_link_loss(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_link_loss; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_allow_hpd_rx_irq_true; + + handle_hpd_rx_irq(aconn); + + KUNIT_EXPECT_TRUE(test, drm_to_adev(aconn->base.dev) + ->dm.hpd_rx_offload_wq->is_handling_link_loss); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -2004,6 +2914,26 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_hpd_init_fini_with_connectors), KUNIT_CASE(dm_test_hpd_init_fini_analog_connector), KUNIT_CASE(dm_test_hpd_init_fini_irq_ref), + /* dm_handle_hpd_rx_offload_work */ + KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connector), + KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connection), + KUNIT_CASE(dm_test_hpd_rx_offload_work_automated_test), + KUNIT_CASE(dm_test_hpd_rx_offload_work_link_loss), + /* amdgpu_dm_hdmi_hpd_debounce_work */ + KUNIT_CASE(dm_test_hdmi_hpd_debounce_detect_false), + KUNIT_CASE(dm_test_hdmi_hpd_debounce_reallow_idle), + /* handle_hpd_irq/handle_hpd_irq_helper */ + KUNIT_CASE(dm_test_handle_hpd_irq_disabled), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_schedule), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_release_prev), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_detect_false), + /* handle_hpd_rx_irq/schedule_hpd_rx_offload_work */ + KUNIT_CASE(dm_test_handle_hpd_rx_irq_disabled), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_no_left_work), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_automated_test), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_msg_rdy), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_link_loss), + KUNIT_CASE(dm_test_schedule_hpd_rx_offload_work), {} }; From a76f7d5a14bb7c96ee9680cbba7d711e166b5672 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jun 2026 23:54:13 -0600 Subject: [PATCH 053/155] drm/amd/display: Test dmub HPD callbacks and handlers [WHAT] Add KUnit tests for the DMUB HPD notification callbacks (dmub_hpd_callback, dmub_hpd_sense_callback) and for amdgpu_dm_register_hpd_handlers(). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 7 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 4 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 418 ++++++++++++++++++ 3 files changed, 427 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index adac23be2972..74fe12faeb5b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1584,7 +1584,7 @@ EXPORT_IF_KUNIT(handle_hpd_rx_irq); * Dmub Hpd interrupt processing callback. Gets displayindex through the * ink index and calls helper to do the processing. */ -static void dmub_hpd_callback(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void dmub_hpd_callback(struct amdgpu_device *adev, struct dmub_notification *notify) { struct amdgpu_dm_connector *aconnector; @@ -1650,6 +1650,7 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, } } } +EXPORT_IF_KUNIT(dmub_hpd_callback); /** * dmub_hpd_sense_callback - DMUB HPD sense processing callback. @@ -1659,11 +1660,12 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, * HPD sense changes can occur during low power states and need to be * notified from firmware to driver. */ -static void dmub_hpd_sense_callback(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void dmub_hpd_sense_callback(struct amdgpu_device *adev, struct dmub_notification *notify) { drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); } +EXPORT_IF_KUNIT(dmub_hpd_sense_callback); int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) { @@ -1741,6 +1743,7 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) } return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_register_hpd_handlers); /* ========== IRQ handlers ========== */ struct amdgpu_crtc * diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index e01b0856f91f..7200d3754905 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -169,6 +169,10 @@ void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, union hpd_irq_data hpd_irq_data); void handle_hpd_rx_irq(void *param); +void dmub_hpd_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +void dmub_hpd_sense_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index fa161d0ecfd9..eb74883cb6d9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -2812,6 +2812,411 @@ static void dm_test_handle_hpd_rx_irq_link_loss(struct kunit *test) ->dm.hpd_rx_offload_wq->is_handling_link_loss); } +/* Tests for dmub_hpd_callback()/dmub_hpd_sense_callback() */ + +/** + * dm_test_dmub_hpd_callback_null_inputs - Test DMUB callback null inputs + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_null_inputs(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + dmub_hpd_callback(NULL, NULL); + dmub_hpd_callback(adev, NULL); +} + +/** + * dm_test_dmub_hpd_callback_invalid_index - Test DMUB callback index check + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_invalid_index(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + notify.link_index = 1; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_empty_connectors - Test DMUB callback without match + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_empty_connectors(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + notify.type = DMUB_NOTIFICATION_HPD_SENSE_NOTIFY; + + dmub_hpd_callback(adev, ¬ify); + dmub_hpd_sense_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_unknown_type_match - Test DMUB callback matched + * connector, unknown type + * @test: The KUnit test context + * + * A writeback connector must be skipped (continue) and a non-writeback + * connector whose dc_link matches the notification link must be selected. + * An unrecognized notification type takes the "unknown" warn branch, and the + * post-loop dispatch is a no-op (neither HPD nor HPD_IRQ), so no real DC/MST + * handler runs. + */ +static void dm_test_dmub_hpd_callback_unknown_type_match(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct drm_connector *wbconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + dc->links[0] = link; + + /* A writeback connector must be skipped by the loop. */ + wbconn = kunit_kzalloc(test, sizeof(*wbconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wbconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wbconn, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_connector_cleanup, wbconn), 0); + + /* A DisplayPort connector whose dc_link matches drives the match branch. */ + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD_SENSE_NOTIFY; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_hpd_type - Test DMUB callback HPD type dispatch + * @test: The KUnit test context + * + * A matched connector with notification type DMUB_NOTIFICATION_HPD logs the + * HPD callback and dispatches to handle_hpd_irq_helper(). Detection is stubbed + * to report no connection and link detect to fail, so the connected branches + * are skipped and no real DC/DRM hotplug path runs. + */ +static void dm_test_dmub_hpd_callback_hpd_type(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + dc->links[0] = link; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_hpd_irq_type - Test DMUB callback HPD_IRQ dispatch + * @test: The KUnit test context + * + * A matched connector with notification type DMUB_NOTIFICATION_HPD_IRQ logs + * the HPD RX callback and dispatches to handle_hpd_rx_irq(). The HPD RX handler + * stub reports no left work, so the function returns through the early goto + * without scheduling offload work; an MST-branch link skips the trailing + * drm_dp_cec_irq(). + */ +static void dm_test_dmub_hpd_callback_hpd_irq_type(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + adev->dm.hpd_rx_offload_wq = offload_wq; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + link->type = dc_connection_mst_branch; + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + dc->links[0] = link; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + mutex_init(&aconn->hpd_lock); + aconn->mst_mgr.mst_state = true; + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD_IRQ; + + dmub_hpd_callback(adev, ¬ify); +} + +/* Tests for amdgpu_dm_register_hpd_handlers() */ + +/** + * dm_test_register_hpd_handlers_empty - Test HPD registration with no connectors + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); +} + +/** + * dm_test_register_hpd_handlers_valid - Test HPD/HPDRX registration succeeds + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_valid(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1RX])); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_invalid_hpd - Test invalid HPD source fails + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_invalid_hpd(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1RX; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), -EINVAL); + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_invalid_hpd_rx - Test invalid HPDRX source fails + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_invalid_hpd_rx(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_INVALID; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), -EINVAL); + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_dmub_outbox - Test DMUB outbox callbacks register + * @test: The KUnit test context + * + * Enables DMUB outbox support so dc_is_dmub_outbox_supported() returns true, + * exercising the dm_register_dmub_notify_callback() block. Asserts that the + * HPD, HPD_IRQ and HPD_SENSE_NOTIFY callbacks are registered. + */ +static void dm_test_register_hpd_handlers_dmub_outbox(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = dm_test_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + + /* Make dc_is_dmub_outbox_supported() return true. */ + dc->caps.dmcub_support = true; + dc->ctx->asic_id.chip_family = AMDGPU_FAMILY_GC_11_0_1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD], + (dmub_notify_interrupt_callback_t)dmub_hpd_callback); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD_IRQ], + (dmub_notify_interrupt_callback_t)dmub_hpd_callback); + KUNIT_EXPECT_PTR_EQ(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD_SENSE_NOTIFY], + (dmub_notify_interrupt_callback_t)dmub_hpd_sense_callback); + + amdgpu_dm_irq_fini(adev); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -2934,6 +3339,19 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_handle_hpd_rx_irq_msg_rdy), KUNIT_CASE(dm_test_handle_hpd_rx_irq_link_loss), KUNIT_CASE(dm_test_schedule_hpd_rx_offload_work), + /* dmub_hpd_callback/dmub_hpd_sense_callback */ + KUNIT_CASE(dm_test_dmub_hpd_callback_null_inputs), + KUNIT_CASE(dm_test_dmub_hpd_callback_invalid_index), + KUNIT_CASE(dm_test_dmub_hpd_callback_empty_connectors), + KUNIT_CASE(dm_test_dmub_hpd_callback_unknown_type_match), + KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_type), + KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_irq_type), + /* amdgpu_dm_register_hpd_handlers */ + KUNIT_CASE(dm_test_register_hpd_handlers_empty), + KUNIT_CASE(dm_test_register_hpd_handlers_valid), + KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd), + KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd_rx), + KUNIT_CASE(dm_test_register_hpd_handlers_dmub_outbox), {} }; From aec345ba11e8d570e3dc94101d6472884d822b38 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jun 2026 23:54:58 -0600 Subject: [PATCH 054/155] drm/amd/display: Test high-IRQ and handler registration [WHAT] Add the final batch of amdgpu_dm_irq KUnit tests covering the high-IRQ callbacks (dm_pflip_high_irq, dm_vupdate_high_irq, dm_crtc_high_irq), dm_handle_hpd_work, the DMUB outbox1 low IRQ, and the per-ASIC IRQ handler registration helpers (DCE110, DCN10 and outbox). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 18 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 606 ++++++++++++++++++ 3 files changed, 624 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 74fe12faeb5b..1928b83fc31e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1775,7 +1775,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_get_crtc_by_otg_inst); * Handles the pageflip interrupt by notifying all interested parties * that the pageflip has been completed. */ -static void dm_pflip_high_irq(void *interrupt_params) +STATIC_IFN_KUNIT void dm_pflip_high_irq(void *interrupt_params) { struct amdgpu_crtc *amdgpu_crtc; struct common_irq_params *irq_params = interrupt_params; @@ -1871,6 +1871,7 @@ static void dm_pflip_high_irq(void *interrupt_params) "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e); } +EXPORT_IF_KUNIT(dm_pflip_high_irq); static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) { @@ -1918,7 +1919,7 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, queue_work(system_percpu_wq, &offload_work->work); } -static void dm_vupdate_high_irq(void *interrupt_params) +STATIC_IFN_KUNIT void dm_vupdate_high_irq(void *interrupt_params) { struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; @@ -1981,6 +1982,7 @@ static void dm_vupdate_high_irq(void *interrupt_params) } } } +EXPORT_IF_KUNIT(dm_vupdate_high_irq); /** * dm_crtc_high_irq() - Handles CRTC interrupt @@ -1989,7 +1991,7 @@ static void dm_vupdate_high_irq(void *interrupt_params) * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK * event handler. */ -static void dm_crtc_high_irq(void *interrupt_params) +STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) { struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; @@ -2088,6 +2090,7 @@ static void dm_crtc_high_irq(void *interrupt_params) spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); } +EXPORT_IF_KUNIT(dm_crtc_high_irq); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) /** @@ -2112,7 +2115,7 @@ static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) } #endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ -static void dm_handle_hpd_work(struct work_struct *work) +STATIC_IFN_KUNIT void dm_handle_hpd_work(struct work_struct *work) { struct dmub_hpd_work *dmub_hpd_wrk; @@ -2132,6 +2135,7 @@ static void dm_handle_hpd_work(struct work_struct *work) kfree(dmub_hpd_wrk); } +EXPORT_IF_KUNIT(dm_handle_hpd_work); STATIC_IFN_KUNIT const char *dmub_notification_type_str(enum dmub_notification_type e) { @@ -2166,7 +2170,7 @@ EXPORT_IF_KUNIT(dmub_notification_type_str); * Handles the Outbox Interrupt * event handler. */ -static void dm_dmub_outbox1_low_irq(void *interrupt_params) +STATIC_IFN_KUNIT void dm_dmub_outbox1_low_irq(void *interrupt_params) { struct dmub_notification notify = {0}; struct common_irq_params *irq_params = interrupt_params; @@ -2230,6 +2234,7 @@ static void dm_dmub_outbox1_low_irq(void *interrupt_params) } while (notify.pending_notification); } } +EXPORT_IF_KUNIT(dm_dmub_outbox1_low_irq); /* Register IRQ sources and initialize IRQ callbacks */ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev) @@ -2368,6 +2373,7 @@ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev) return r; } +EXPORT_IF_KUNIT(amdgpu_dm_dce110_register_irq_handlers); /* Register IRQ sources and initialize IRQ callbacks */ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) @@ -2549,6 +2555,7 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) return r; } +EXPORT_IF_KUNIT(amdgpu_dm_dcn10_register_irq_handlers); /* Register Outbox IRQ sources and initialize IRQ callbacks */ int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev) @@ -2586,3 +2593,4 @@ int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_register_outbox_irq_handlers); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 7200d3754905..da698f224d85 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -173,6 +173,11 @@ void dmub_hpd_callback(struct amdgpu_device *adev, struct dmub_notification *notify); void dmub_hpd_sense_callback(struct amdgpu_device *adev, struct dmub_notification *notify); +void dm_pflip_high_irq(void *interrupt_params); +void dm_vupdate_high_irq(void *interrupt_params); +void dm_crtc_high_irq(void *interrupt_params); +void dm_handle_hpd_work(struct work_struct *work); +void dm_dmub_outbox1_low_irq(void *interrupt_params); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index eb74883cb6d9..c356c2c836b4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -184,6 +184,24 @@ static bool dm_test_allow_hpd_rx_irq_true(const struct dc_link *link) } +static uint32_t dm_test_dmub_get_outbox0_wptr(struct dmub_srv *dmub) +{ + return 0; +} + +static uint32_t dm_test_dmub_get_outbox1_wptr(struct dmub_srv *dmub) +{ + return 0; +} + +static int dm_test_dmub_notify_count; + +static void dm_test_dmub_notify_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + dm_test_dmub_notify_count++; +} + static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) { struct dc_context *ctx; @@ -200,6 +218,37 @@ static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) return dc; } +static enum dc_irq_source dm_test_to_dal_irq_source_dce110( + struct irq_service *irq_service, + uint32_t src_id, + uint32_t ext_id) +{ + switch (src_id) { + case VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0: + return DC_IRQ_SOURCE_VBLANK1; + case VISLANDS30_IV_SRCID_D1_V_UPDATE_INT: + return DC_IRQ_SOURCE_VUPDATE1; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP: + return DC_IRQ_SOURCE_PFLIP1; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 2: + return DC_IRQ_SOURCE_PFLIP2; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 4: + return DC_IRQ_SOURCE_PFLIP3; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 6: + return DC_IRQ_SOURCE_PFLIP4; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 8: + return DC_IRQ_SOURCE_PFLIP5; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 10: + return DC_IRQ_SOURCE_PFLIP6; + default: + return DC_IRQ_SOURCE_INVALID; + } +} + +static const struct irq_service_funcs dm_test_irq_service_funcs_dce110 = { + .to_dal_irq_source = dm_test_to_dal_irq_source_dce110 +}; + static enum dc_irq_source dm_test_to_dal_irq_source_dcn10( struct irq_service *irq_service, uint32_t src_id, @@ -274,6 +323,32 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, return dc; } +static void dm_test_free_irq_sources(void *data) +{ + struct amdgpu_device *adev = data; + int i; + + for (i = 0; i < AMDGPU_IRQ_CLIENTID_MAX; i++) { + kfree(adev->irq.client[i].sources); + adev->irq.client[i].sources = NULL; + } + + kfree(adev->crtc_irq.enabled_types); + adev->crtc_irq.enabled_types = NULL; + kfree(adev->vline0_irq.enabled_types); + adev->vline0_irq.enabled_types = NULL; + kfree(adev->vupdate_irq.enabled_types); + adev->vupdate_irq.enabled_types = NULL; + kfree(adev->pageflip_irq.enabled_types); + adev->pageflip_irq.enabled_types = NULL; + kfree(adev->dmub_outbox_irq.enabled_types); + adev->dmub_outbox_irq.enabled_types = NULL; + kfree(adev->dmub_trace_irq.enabled_types); + adev->dmub_trace_irq.enabled_types = NULL; + kfree(adev->hpd_irq.enabled_types); + adev->hpd_irq.enabled_types = NULL; +} + static void dm_test_crtc_list_del(void *data) { struct amdgpu_crtc *acrtc = data; @@ -3217,6 +3292,516 @@ static void dm_test_register_hpd_handlers_dmub_outbox(struct kunit *test) amdgpu_dm_irq_fini(adev); } +/* Tests for CRTC/pflip/vupdate high IRQ callbacks */ + +/** + * dm_test_pflip_high_irq_no_crtc - Test pflip high IRQ with no matching CRTC + * @test: The KUnit test context + */ +static void dm_test_pflip_high_irq_no_crtc(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_PFLIP; + + dm_pflip_high_irq(¶ms); +} + +/** + * dm_test_pflip_high_irq_not_submitted - Test pflip high IRQ early status exit + * @test: The KUnit test context + */ +static void dm_test_pflip_high_irq_not_submitted(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->base.dev = &adev->ddev; + acrtc->otg_inst = 0; + acrtc->pflip_status = AMDGPU_FLIP_NONE; + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crtc_list_del, acrtc), 0); + + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_PFLIP; + + dm_pflip_high_irq(¶ms); + KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE); +} + +/** + * dm_test_vupdate_high_irq_no_crtc - Test vupdate high IRQ with no CRTC + * @test: The KUnit test context + */ +static void dm_test_vupdate_high_irq_no_crtc(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VUPDATE; + + dm_vupdate_high_irq(¶ms); +} + +/** + * dm_test_crtc_high_irq_no_crtc - Test crtc high IRQ with no CRTC + * @test: The KUnit test context + */ +static void dm_test_crtc_high_irq_no_crtc(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK; + + dm_crtc_high_irq(¶ms); +} + +/** + * dm_test_crtc_high_irq_vrr_pre_ai - Test crtc high IRQ VRR path on pre-AI ASIC + * @test: The KUnit test context + * + * With a matching CRTC, no writeback, VRR active (so the !vrr_active vblank + * handler is skipped) and a pre-AI family, the handler runs through CRC + * handling and returns before the freesync section. + */ +static void dm_test_crtc_high_irq_vrr_pre_ai(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->base.dev = &adev->ddev; + acrtc->otg_inst = 0; + /* VRR active so the !vrr_active vblank handler is skipped. */ + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crtc_list_del, acrtc), 0); + + /* Pre-AI family returns right after CRC handling. */ + adev->family = AMDGPU_FAMILY_SI; + + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK; + + dm_crtc_high_irq(¶ms); +} + +/** + * dm_test_crtc_high_irq_vrr_ai_no_stream - Test crtc high IRQ AI path, no stream + * @test: The KUnit test context + * + * On an AI+ family the handler runs the post-CRC freesync section. With no + * stream and no pending flip, both inner blocks are skipped and the handler + * completes through the event-lock critical section, leaving pflip_status + * untouched. + */ +static void dm_test_crtc_high_irq_vrr_ai_no_stream(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->base.dev = &adev->ddev; + acrtc->otg_inst = 0; + acrtc->pflip_status = AMDGPU_FLIP_NONE; + /* VRR active so the !vrr_active vblank handler is skipped. */ + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crtc_list_del, acrtc), 0); + + /* AI+ family runs the freesync section; no stream skips it. */ + adev->family = AMDGPU_FAMILY_AI; + + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK; + + dm_crtc_high_irq(¶ms); + KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE); +} + +/* Tests for dm_handle_hpd_work() */ + +/** + * dm_test_handle_hpd_work_out_of_range - Test HPD work frees unknown notification + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_work_out_of_range(struct kunit *test) +{ + struct dmub_hpd_work *hpd_work; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + hpd_work = kzalloc(sizeof(*hpd_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work); + hpd_work->dmub_notify = kzalloc(sizeof(*hpd_work->dmub_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work->dmub_notify); + hpd_work->dmub_notify->type = + (enum dmub_notification_type)ARRAY_SIZE(adev->dm.dmub_callback); + hpd_work->adev = adev; + INIT_WORK(&hpd_work->handle_hpd_work, dm_handle_hpd_work); + + dm_handle_hpd_work(&hpd_work->handle_hpd_work); +} + +/* Tests for dm_dmub_outbox1_low_irq() */ + +/** + * dm_test_dmub_outbox1_low_irq_empty - Test outbox low IRQ with empty trace queue + * @test: The KUnit test context + */ +static void dm_test_dmub_outbox1_low_irq_empty(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct dc_dmub_srv *dc_dmub_srv; + struct amdgpu_device *adev; + struct dmub_srv *dmub; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = dm_test_alloc_dc_with_ctx(test); + dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); + dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub); + + dmub->hw_funcs.get_outbox0_wptr = dm_test_dmub_get_outbox0_wptr; + dc_dmub_srv->dmub = dmub; + dc->ctx->dmub_srv = dc_dmub_srv; + adev->dm.dc = dc; + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_dmub_outbox1_low_irq(¶ms); +} + +/* + * dm_test_alloc_adev_outbox_notify - Build an adev wired for DMUB outbox + * notification handling. + * + * Configures dc/dc_dmub_srv/dmub so that the trace queue is empty and + * dc_enable_dmub_notifications() returns true, allowing the notification + * handling block of dm_dmub_outbox1_low_irq() to execute. The outbox1 ring + * buffer is left empty, so each notification read returns + * DMUB_NOTIFICATION_NO_DATA with no pending notification. + */ +static struct amdgpu_device *dm_test_alloc_adev_outbox_notify(struct kunit *test) +{ + struct dc_dmub_srv *dc_dmub_srv; + struct amdgpu_device *adev; + struct dmub_srv *dmub; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = dm_test_alloc_dc_with_ctx(test); + dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); + dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub); + + dmub->hw_init = true; + dmub->hw_funcs.get_outbox0_wptr = dm_test_dmub_get_outbox0_wptr; + dmub->hw_funcs.get_outbox1_wptr = dm_test_dmub_get_outbox1_wptr; + dc_dmub_srv->dmub = dmub; + dc->ctx->dmub_srv = dc_dmub_srv; + + /* Make dc_enable_dmub_notifications() return true. */ + dc->caps.dmcub_support = true; + dc->ctx->asic_id.chip_family = AMDGPU_FAMILY_GC_11_0_1; + + adev->dm.dc = dc; + + return adev; +} + +/** + * dm_test_dmub_outbox1_low_irq_no_handler - Test notification with no handler + * @test: The KUnit test context + * + * Notifications are enabled but no callback is registered for the returned + * notification type, exercising the skip-with-warning path. + */ +static void dm_test_dmub_outbox1_low_irq_no_handler(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_test_alloc_adev_outbox_notify(test); + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_dmub_outbox1_low_irq(¶ms); +} + +/** + * dm_test_dmub_outbox1_low_irq_direct_callback - Test direct callback dispatch + * @test: The KUnit test context + * + * Notifications are enabled with a registered callback and thread offload + * disabled, so the callback is invoked directly from the IRQ handler. + */ +static void dm_test_dmub_outbox1_low_irq_direct_callback(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_test_alloc_adev_outbox_notify(test); + adev->dm.dmub_callback[DMUB_NOTIFICATION_NO_DATA] = dm_test_dmub_notify_callback; + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_NO_DATA] = false; + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_test_dmub_notify_count = 0; + + dm_dmub_outbox1_low_irq(¶ms); + + KUNIT_EXPECT_EQ(test, dm_test_dmub_notify_count, 1); +} + +/** + * dm_test_dmub_outbox1_low_irq_offload - Test offloaded callback dispatch + * @test: The KUnit test context + * + * Notifications are enabled with a registered callback and thread offload + * enabled, so the callback is dispatched via the delayed HPD work queue. + */ +static void dm_test_dmub_outbox1_low_irq_offload(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_test_alloc_adev_outbox_notify(test); + adev->dm.dmub_callback[DMUB_NOTIFICATION_NO_DATA] = dm_test_dmub_notify_callback; + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_NO_DATA] = true; + adev->dm.delayed_hpd_wq = create_singlethread_workqueue("dm_irq_test_outbox"); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.delayed_hpd_wq); + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_test_dmub_notify_count = 0; + + dm_dmub_outbox1_low_irq(¶ms); + + flush_workqueue(adev->dm.delayed_hpd_wq); + destroy_workqueue(adev->dm.delayed_hpd_wq); + + KUNIT_EXPECT_EQ(test, dm_test_dmub_notify_count, 1); +} + + +/** + * dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources - Test DCE110 error + * @test: The KUnit test context + */ +static void dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = dm_test_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL); +} + +/** + * dm_test_dce110_register_irq_handlers_one_crtc - Test DCE110 with 1 CRTC + * @test: The KUnit test context + * + * Exercises the VBLANK, VUPDATE, PFLIP and HPD for-loop bodies with a + * fake IRQ service that maps source IDs to DC IRQ sources. + */ +static void dm_test_dce110_register_irq_handlers_one_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dce110); + dc->ctx->dce_version = DCE_VERSION_11_0; + adev->dm.dc = dc; + adev->mode_info.num_crtc = 1; + adev->mode_info.num_hpd = 1; + amdgpu_dm_set_irq_funcs(adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), 0); + + /* Verify VBLANK params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vblank_params[0].irq_src, + (int)DC_IRQ_SOURCE_VBLANK1); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.vblank_params[0].adev, adev); + + /* Verify VUPDATE params were populated (VRR supported on DCE 11) */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vupdate_params[0].irq_src, + (int)DC_IRQ_SOURCE_VUPDATE1); + + /* Verify PFLIP params were populated (6 fixed entries) */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[0].irq_src, + (int)DC_IRQ_SOURCE_PFLIP1); + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[5].irq_src, + (int)DC_IRQ_SOURCE_PFLIP6); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_dcn10_register_irq_handlers_zero_crtc - Test DCN10 zero-CRTC registration + * @test: The KUnit test context + */ +static void dm_test_dcn10_register_irq_handlers_zero_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + adev->mode_info.num_hpd = 1; + amdgpu_dm_set_irq_funcs(adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), 0); + KUNIT_ASSERT_NOT_NULL(test, adev->irq.client[SOC15_IH_CLIENTID_DCE].sources); + KUNIT_EXPECT_PTR_EQ(test, + adev->irq.client[SOC15_IH_CLIENTID_DCE].sources[DCN_1_0__SRCID__DC_HPD1_INT], + &adev->hpd_irq); +} + +/** + * dm_test_dcn10_register_irq_handlers_one_crtc - Test DCN10 with 1 CRTC + * @test: The KUnit test context + * + * Exercises the VSTARTUP, VUPDATE, and PFLIP for-loop bodies with a + * fake IRQ service. + */ +static void dm_test_dcn10_register_irq_handlers_one_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + adev->dm.dc = dc; + adev->mode_info.num_crtc = 1; + adev->mode_info.num_hpd = 1; + dc->caps.max_otg_num = 1; + amdgpu_dm_set_irq_funcs(adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), 0); + + /* Verify VBLANK params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vblank_params[0].irq_src, + (int)DC_IRQ_SOURCE_VBLANK1); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.vblank_params[0].adev, adev); + + /* Verify VUPDATE params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vupdate_params[0].irq_src, + (int)DC_IRQ_SOURCE_VUPDATE1); + + /* Verify PFLIP params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[0].irq_src, + (int)DC_IRQ_SOURCE_PFLIP1); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_outbox_irq_handlers_without_dmub - Test outbox registration without DMUB + * @test: The KUnit test context + */ +static void dm_test_register_outbox_irq_handlers_without_dmub(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + amdgpu_dm_set_irq_funcs(adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_outbox_irq_handlers(adev), 0); + KUNIT_ASSERT_NOT_NULL(test, adev->irq.client[SOC15_IH_CLIENTID_DCE].sources); + KUNIT_EXPECT_PTR_EQ(test, + adev->irq.client[SOC15_IH_CLIENTID_DCE].sources[ + DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT], + &adev->dmub_outbox_irq); +} + +/** + * dm_test_register_outbox_irq_handlers_with_dmub - Test outbox registration with DMUB + * @test: The KUnit test context + * + * Exercises the dc->ctx->dmub_srv branch which maps the outbox source and + * registers dm_dmub_outbox1_low_irq in the low IRQ context table. + */ +static void dm_test_register_outbox_irq_handlers_with_dmub(struct kunit *test) +{ + struct dc_dmub_srv *dc_dmub_srv; + struct amdgpu_device *adev; + struct list_head *hnd_list; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); + dc->ctx->dmub_srv = dc_dmub_srv; + adev->dm.dc = dc; + amdgpu_dm_set_irq_funcs(adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_outbox_irq_handlers(adev), 0); + + /* Verify the outbox IRQ params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.dmub_outbox_params[0].irq_src, + (int)DC_IRQ_SOURCE_DMCUB_OUTBOX); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_outbox_params[0].adev, adev); + + /* Verify a low-context handler was registered for the outbox source */ + hnd_list = &adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_DMCUB_OUTBOX]; + KUNIT_EXPECT_FALSE(test, list_empty(hnd_list)); + + amdgpu_dm_irq_fini(adev); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -3352,6 +3937,27 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd), KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd_rx), KUNIT_CASE(dm_test_register_hpd_handlers_dmub_outbox), + /* pflip/vupdate/crtc high IRQ callbacks */ + KUNIT_CASE(dm_test_pflip_high_irq_no_crtc), + KUNIT_CASE(dm_test_pflip_high_irq_not_submitted), + KUNIT_CASE(dm_test_vupdate_high_irq_no_crtc), + KUNIT_CASE(dm_test_crtc_high_irq_no_crtc), + KUNIT_CASE(dm_test_crtc_high_irq_vrr_pre_ai), + KUNIT_CASE(dm_test_crtc_high_irq_vrr_ai_no_stream), + /* dm_handle_hpd_work */ + KUNIT_CASE(dm_test_handle_hpd_work_out_of_range), + /* dm_dmub_outbox1_low_irq */ + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_empty), + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_no_handler), + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_direct_callback), + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_offload), + /* IRQ handler registration helpers */ + KUNIT_CASE(dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources), + KUNIT_CASE(dm_test_dce110_register_irq_handlers_one_crtc), + KUNIT_CASE(dm_test_dcn10_register_irq_handlers_zero_crtc), + KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc), + KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub), + KUNIT_CASE(dm_test_register_outbox_irq_handlers_with_dmub), {} }; From b11706cb7d185aba510887a584ec4f7da513b60e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 25 Jun 2026 12:01:11 -0600 Subject: [PATCH 055/155] drm/amd/display: Test top-level IRQ handler and vmin/vmax worker [WHAT] Add KUnit coverage for the two remaining uncovered functions in amdgpu_dm_irq.c: amdgpu_dm_irq_handler() and dm_handle_vmin_vmax_update(). amdgpu_dm_irq_handler() is driven with a fake dc whose irq_service maps the hardware IRQ entry to a DC source and whose per-source info table lets dc_interrupt_ack() succeed; high- and low-context counting handlers verify the immediate and scheduled dispatch paths. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 6 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 125 ++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 1928b83fc31e..7398987ee8a7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -679,7 +679,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); * Calls all registered high irq work immediately, and schedules work for low * irq. The DM IRQ table is used to find the corresponding handlers. */ -static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry) { @@ -699,6 +699,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_handler); STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) { @@ -1873,7 +1874,7 @@ STATIC_IFN_KUNIT void dm_pflip_high_irq(void *interrupt_params) } EXPORT_IF_KUNIT(dm_pflip_high_irq); -static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) +STATIC_IFN_KUNIT void dm_handle_vmin_vmax_update(struct work_struct *offload_work) { struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); struct amdgpu_device *adev = work->adev; @@ -1888,6 +1889,7 @@ static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) kfree(work->adjust); kfree(work); } +EXPORT_IF_KUNIT(dm_handle_vmin_vmax_update); static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, struct dc_stream_state *stream, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index da698f224d85..4c200a9614a7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -124,6 +124,7 @@ int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) struct amdgpu_irq_src; +struct amdgpu_iv_entry; enum amdgpu_interrupt_state; enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); @@ -178,6 +179,10 @@ void dm_vupdate_high_irq(void *interrupt_params); void dm_crtc_high_irq(void *interrupt_params); void dm_handle_hpd_work(struct work_struct *work); void dm_dmub_outbox1_low_irq(void *interrupt_params); +int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry); +void dm_handle_vmin_vmax_update(struct work_struct *offload_work); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index c356c2c836b4..5a4fecdf3135 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -3802,6 +3802,127 @@ static void dm_test_register_outbox_irq_handlers_with_dmub(struct kunit *test) amdgpu_dm_irq_fini(adev); } +/* Tests for amdgpu_dm_irq_handler() */ + +/** + * dm_test_irq_handler_dispatches_work - Test the top-level IRQ handler + * @test: The KUnit test context + * + * amdgpu_dm_irq_handler() translates the hardware IRQ entry to a DC IRQ + * source, acknowledges it, then dispatches to the high-context (immediate) + * and low-context (scheduled) handler lists. A fake dc with a stubbed + * irq_service maps the source id to DC_IRQ_SOURCE_VBLANK1 and lets the ack + * succeed without touching hardware registers. + */ +static void dm_test_irq_handler_dispatches_work(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_iv_entry entry = { 0 }; + struct amdgpu_device *adev; + int high_count = 0; + int low_count = 0; + void *handler; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + dc = dm_test_alloc_dc_with_irq_service(test, + &dm_test_irq_service_funcs_dce110); + adev->dm.dc = dc; + + /* High-context (immediate) handler on VBLANK1. */ + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_VBLANK1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, + &high_count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Low-context (scheduled) handler on VBLANK1. */ + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, + &low_count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* src_id maps to DC_IRQ_SOURCE_VBLANK1 via the dce110 stub. */ + entry.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0; + entry.src_data[0] = 0; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_handler(adev, NULL, &entry), 0); + + /* High-context handler runs synchronously in-place. */ + KUNIT_EXPECT_EQ(test, high_count, 1); + + /* + * Low-context work runs asynchronously; amdgpu_dm_irq_fini() flushes + * each pending work item before freeing, so it has run afterwards. + */ + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, low_count, 1); +} + +/* Tests for dm_handle_vmin_vmax_update() */ + +/** + * dm_test_handle_vmin_vmax_update - Test the deferred vmin/vmax worker + * @test: The KUnit test context + * + * The worker applies the cached timing adjust to the stream via + * dc_stream_adjust_vmin_vmax(), drops the stream reference taken when the + * work was scheduled, and frees the work and its adjust copy. A fake dc with + * a current_state lets the adjust walk an empty pipe list without touching + * hardware. The work and adjust are kmalloc'd because the worker frees them. + */ +static void dm_test_handle_vmin_vmax_update(struct kunit *test) +{ + struct dc_crtc_timing_adjust *adjust; + struct vupdate_offload_work *work; + struct dc_stream_state *stream; + struct amdgpu_device *adev; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + mutex_init(&adev->dm.dc_lock); + + dc = dm_test_alloc_dc_with_ctx(test); + dc->current_state = kunit_kzalloc(test, sizeof(*dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc->current_state); + adev->dm.dc = dc; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + /* + * Start at two references: the worker's dc_stream_release() drops one, + * leaving the kunit-managed allocation intact (no kfree). + */ + kref_init(&stream->refcount); + kref_get(&stream->refcount); + + /* The worker kfree()s both, so they must come from the slab. */ + work = kzalloc(sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work); + adjust = kzalloc(sizeof(*adjust), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adjust); + + work->adev = adev; + work->stream = stream; + work->adjust = adjust; + adjust->v_total_min = 1000; + adjust->v_total_max = 1100; + + dm_handle_vmin_vmax_update(&work->work); + + /* The adjust was applied and one stream reference was dropped. */ + KUNIT_EXPECT_EQ(test, stream->adjust.v_total_min, 1000); + KUNIT_EXPECT_EQ(test, stream->adjust.v_total_max, 1100); + KUNIT_EXPECT_EQ(test, kref_read(&stream->refcount), 1); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -3958,6 +4079,10 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc), KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub), KUNIT_CASE(dm_test_register_outbox_irq_handlers_with_dmub), + /* amdgpu_dm_irq_handler */ + KUNIT_CASE(dm_test_irq_handler_dispatches_work), + /* dm_handle_vmin_vmax_update */ + KUNIT_CASE(dm_test_handle_vmin_vmax_update), {} }; From 11a90eaf5c808ba800249dda0d481c35d0888589 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 24 Jun 2026 16:54:30 -0400 Subject: [PATCH 056/155] drm/amd/display: Fix 8K Mode Not Parsed by EDID [why] The 8K120/8K240 timings live in DisplayID extension blocks 2 and 3 of this EDID. The EDID is a 4-block (512-byte) HDMI 2.1 EDID that uses HF-EEODB. drm core reads and parses this correctly, but amdgpu rebuilds its own copy. Only 2 of 4 blocks were copied into sink->dc_edid, that leads to drm_edid_connector_add_modes() never sees blocks 2 and 3. [how] Directly populate edid_blob_ptr with a blob whose length is the full, and HF-EEODB-aware size. Reviewed-by: Sun peng (Leo) Li Signed-off-by: Fangzhi Zuo Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 86f979262f28..30ac1f32fad5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1256,11 +1256,25 @@ enum dc_edid_status dm_helpers_read_local_edid( continue; edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - if (!edid || - edid->extensions >= sizeof(sink->dc_edid.raw_edid) / EDID_LENGTH) + /* + * Use the length of the EDID property blob populated by + * drm_edid_connector_update() above. It reflects the true number + * of EDID blocks, including any HDMI Forum EDID Extension Override + * Data Block (HF-EEODB) count, which the raw byte 0x7e extension + * count can hide (e.g. HDMI 8K sinks). + */ + if (!edid || !connector->edid_blob_ptr || + connector->edid_blob_ptr->length > sizeof(sink->dc_edid.raw_edid)) return EDID_BAD_INPUT; - sink->dc_edid.length = EDID_LENGTH * (edid->extensions + 1); + /* + * FIXME: amdgpu_dm today does not consider the HF-EEODB, which + * may contain additional mode info for sinks. This is a + * workaround until dc_edid is refactored out from DC into + * amdgpu_dm's ownership, allowing amdgpu_dm to use drm_edid + * directly + */ + sink->dc_edid.length = connector->edid_blob_ptr->length; memmove(sink->dc_edid.raw_edid, (uint8_t *)edid, sink->dc_edid.length); /* We don't need the original edid anymore */ From 98e08a815ef604837c4c7d7991ba8772107332da Mon Sep 17 00:00:00 2001 From: Tom Chung Date: Tue, 23 Jun 2026 15:35:30 +0800 Subject: [PATCH 057/155] drm/amd/display: Tear down dangling pipe on boot to fix s0i3 [Why] If an external monitor is connected at power-on and then unplugged before the driver loads (e.g. at the GRUB menu), the system can no longer enter the s0i3 deepest suspend state, even though all connectors report disconnected. The pre-OS firmware (GOP/vBIOS) lights up a front-end for the display that is present at power-on. The driver never fully tears this inherited pipe down, and the leftover pipe keeps the DCN block from reaching idle, which blocks s0i3. [How] Add dc_disable_dangling_timing_generators() to DC core and call it from amdgpu_dm right after dc_hardware_init(). It scans every enabled timing generator and classifies it as in-use or dangling. If a dangling pipe exists and nothing needs to be preserved, power down the hw blocks so DCN can reach idle. Assisted-by: Cursor:claude-opus-4.8 Reviewed-by: Mario Limonciello Signed-off-by: Tom Chung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 8 ++ drivers/gpu/drm/amd/display/dc/core/dc.c | 109 ++++++++++++++++++ drivers/gpu/drm/amd/display/dc/dc.h | 2 + 3 files changed, 119 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index fb19a0781e59..65e7d49a5f93 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -779,6 +779,14 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) dc_hardware_init(adev->dm.dc); + /* GOP/vBIOS may leave an OPTC enabled for a display present at power-on + * but no longer driven (e.g. an external DP unplugged at boot). Such a + * dangling pipe keeps DCN out of idle and blocks s0i3. Power it down + * here when nothing needs to be preserved. + */ + if (adev->flags & AMD_IS_APU) + dc_disable_dangling_timing_generators(adev->dm.dc); + adev->dm.hpd_rx_offload_wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); if (!adev->dm.hpd_rx_offload_wq) { drm_err(adev_to_drm(adev), "failed to create hpd rx offload workqueue.\n"); diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index ec5d6ddeefbe..7934341b4715 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6233,6 +6233,115 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) dal_irq_service_ack(dc->res_pool->irqs, src); } +/* Preserve this tg if a physical link is still lighting a present display */ +static bool should_preserve_tg(struct dc *dc, struct timing_generator *tg) +{ + unsigned int i, j; + + /* Check if a physical link is lighting this tg */ + for (i = 0; i < dc->link_count; i++) { + struct dc_link *link = dc->links[i]; + int fe; + + if (!link || link->ep_type != DISPLAY_ENDPOINT_PHY || + !link->link_enc || + !link->link_enc->funcs->is_dig_enabled || + !link->link_enc->funcs->is_dig_enabled(link->link_enc) || + !link->link_enc->funcs->get_dig_frontend) + continue; + + /* Get the DIG front-end this link's encoder drives; skip if none */ + fe = link->link_enc->funcs->get_dig_frontend(link->link_enc); + if (fe == ENGINE_ID_UNKNOWN) + continue; + + /* Find the stream encoder bound to this link's front-end */ + for (j = 0; j < dc->res_pool->stream_enc_count; j++) { + struct stream_encoder *se = dc->res_pool->stream_enc[j]; + + /* Skip unless this stream encoder feeds our front-end and drives this tg */ + if (se->id != fe || !se->funcs->dig_source_otg || + (int)se->funcs->dig_source_otg(se) != tg->inst) + continue; + + /* This link drives the OTG: keep a seamless-boot eDP, or + * any external link whose sink is still connected. + */ + if (link->connector_signal == SIGNAL_TYPE_EDP) + return true; + if (link->link_enc->funcs->get_hpd_state && + dc->link_srv->get_hpd_state(link)) + return true; + } + } + + return false; +} + +/* + * GOP/vBIOS may leave an OPTC enabled for a display present at power-on but no + * longer driven (e.g. external DP unplugged at boot). Such a dangling pipe keeps + * DCN out of idle and blocks s0i3. If nothing needs to survive (no committed + * stream or seamless-boot eDP) and no sink is still connected, power down all hw + * blocks. + */ +void dc_disable_dangling_timing_generators(struct dc *dc) +{ + struct dce_hwseq *hws = dc->hwseq; + bool any_dangling = false; + bool any_preserved = false; + bool any_connected = false; + unsigned int i; + + /* No real hw to touch on a virtual/emulated environment */ + if (dc->ctx->dce_environment == DCE_ENV_VIRTUAL_HW) + return; + + /* Wake hw out of IPS before reading/touching tg state */ + dc_exit_ips_for_hw_access(dc); + + /* Classify every enabled tg as either to-preserve or dangling */ + for (i = 0; i < dc->res_pool->timing_generator_count; i++) { + struct timing_generator *tg = dc->res_pool->timing_generators[i]; + + if (!tg || !tg->funcs->is_tg_enabled || + !tg->funcs->is_tg_enabled(tg)) + continue; + + if (should_preserve_tg(dc, tg)) + any_preserved = true; + else + any_dangling = true; + } + + /* A physically connected sink (HPD asserted) will be re-lit by a + * subsequent atomic commit. For that case we don't call the global + * power_down(). + */ + for (i = 0; i < dc->link_count; i++) { + struct dc_link *link = dc->links[i]; + + if (link && link->ep_type == DISPLAY_ENDPOINT_PHY && + link->link_enc && link->link_enc->funcs && + link->link_enc->funcs->get_hpd_state && + dc->link_srv->get_hpd_state(link)) { + any_connected = true; + break; + } + } + + if (!any_dangling) + return; + + if (!any_preserved && !any_connected && hws && hws->funcs.power_down) { + /* Truly headless / all sinks unplugged: nothing to preserve */ + DC_LOG_DC("%s: powering down dangling hw blocks to allow idle\n", + __func__); + hws->funcs.power_down(dc); + return; + } +} + void dc_power_down_on_boot(struct dc *dc) { if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index a10ee711668e..0987258df40c 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -3012,6 +3012,8 @@ void dc_resume(struct dc *dc); void dc_power_down_on_boot(struct dc *dc); +void dc_disable_dangling_timing_generators(struct dc *dc); + /* * HDCP Interfaces */ From 5d00879f30a2f9464c785cfc962815ee5780d9a8 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 23 Jun 2026 10:44:10 -0400 Subject: [PATCH 058/155] drm/amd/display: Add more tests for HDCP Adds a few more test cases to the hdcp code - process_output(): make sure work gets requeued when callback and watchdog are stopped but also asked to run again - event_property_update(): bail out when the connector is NULL - hdcp_handle_cpirq(): schedules the work and picks the right link - hdcp_update_display(): schedules property validation on enable, resets status and cancels validation on disable - hdcp_create_workqueue(): returns NULL when max links is zero Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 48 ++-- .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 240 ++++++++++++++++++ 3 files changed, 276 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 5dbeb1e017d4..1ffe82f0a9f1 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 @@ -258,6 +258,30 @@ static void link_lock(struct hdcp_workqueue *work, bool lock) } } +STATIC_IFN_KUNIT +void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, + struct hdcp_workqueue *hdcp_w, + unsigned int conn_index, + bool enable_encryption) +{ + if (enable_encryption) { + /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp + * (s3 resume case) + */ + if (hdcp_work->srm_size > 0) + psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm, + hdcp_work->srm_size, + &hdcp_work->srm_version); + + schedule_delayed_work(&hdcp_w->property_validate_dwork, + msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); + } else { + hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + cancel_delayed_work(&hdcp_w->property_validate_dwork); + } +} +EXPORT_IF_KUNIT(hdcp_update_display_encryption_control); + void hdcp_update_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, struct amdgpu_dm_connector *aconnector, @@ -281,22 +305,8 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, dc->debug.hdcp_lc_force_fw_enable, dc->debug.hdcp_lc_enable_sw_fallback, &link_adjust, &display_adjust); - - if (enable_encryption) { - /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp - * (s3 resume case) - */ - if (hdcp_work->srm_size > 0) - psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm, - hdcp_work->srm_size, - &hdcp_work->srm_version); - - schedule_delayed_work(&hdcp_w->property_validate_dwork, - msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); - } else { - hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; - cancel_delayed_work(&hdcp_w->property_validate_dwork); - } + hdcp_update_display_encryption_control(hdcp_work, hdcp_w, conn_index, + enable_encryption); mod_hdcp_update_display(&hdcp_w->hdcp, conn_index, &link_adjust, &display_adjust, &hdcp_w->output); @@ -363,6 +373,7 @@ void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index schedule_work(&hdcp_w->cpirq_work); } +EXPORT_IF_KUNIT(hdcp_handle_cpirq); static void event_callback(struct work_struct *work) { @@ -381,7 +392,8 @@ static void event_callback(struct work_struct *work) process_output(hdcp_work); } -static void event_property_update(struct work_struct *work) +STATIC_IFN_KUNIT +void event_property_update(struct work_struct *work) { struct hdcp_workqueue *hdcp_work = container_of(work, struct hdcp_workqueue, property_update_work); @@ -440,6 +452,7 @@ static void event_property_update(struct work_struct *work) drm_modeset_unlock(&dev->mode_config.connection_mutex); } } +EXPORT_IF_KUNIT(event_property_update); static void event_property_validate(struct work_struct *work) { @@ -872,4 +885,5 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, return NULL; } +EXPORT_IF_KUNIT(hdcp_create_workqueue); 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 3ba5823aed9f..a82a20b80518 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 @@ -108,6 +108,11 @@ void hdcp_get_link_display_adjustments( bool hdcp_lc_enable_sw_fallback, struct mod_hdcp_link_adjustment *link_adjust, struct mod_hdcp_display_adjustment *display_adjust); +void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, + struct hdcp_workqueue *hdcp_w, + unsigned int conn_index, + bool enable_encryption); +void event_property_update(struct work_struct *work); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 619b4a80c82b..dbcff92672e6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -402,8 +402,236 @@ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *te cancel_delayed_work_sync(&work->watchdog_timer_dwork); cancel_delayed_work_sync(&work->property_validate_dwork); } + +/** + * dm_test_process_output_callback_stop_and_needed_requeues - stop+needed requeues callback work + * @test: KUnit test context + * + * When callback_stop and callback_needed are both set, process_output() + * should cancel the previous callback_dwork and queue it again. + */ +static void dm_test_process_output_callback_stop_and_needed_requeues(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + 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; + work->output.callback_needed = true; + work->output.callback_delay = 300; + + 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); +} + +/** + * dm_test_process_output_watchdog_stop_and_needed_requeues - stop+needed requeues watchdog work + * @test: KUnit test context + * + * When watchdog_timer_stop and watchdog_timer_needed are both set, + * process_output() should cancel previous watchdog work and queue it again. + */ +static void dm_test_process_output_watchdog_stop_and_needed_requeues(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + 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; + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 700; + + 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); +} /* End of tests for process_output() */ +/* Tests for event_property_update() */ + +/** + * alloc_test_workqueue_for_property_update - allocate minimal workqueue for callback tests + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with property_update_work initialised + * so event_property_update() can resolve container_of() safely. + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work->property_update_work, dummy_work_fn); + + return work; +} + +/** + * dm_test_event_property_update_skips_null_connector - null connector is ignored + * @test: KUnit test context + * + * If aconnector entry is NULL, event_property_update() should skip it + * without modifying encryption_status. + */ +static void dm_test_event_property_update_skips_null_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + enum mod_hdcp_encryption_status before; + + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON; + before = work->encryption_status[0]; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[0], before); +} + +/* End of tests for event_property_update() */ + +/* Tests for hdcp_handle_cpirq() */ + +/** + * dm_test_hdcp_handle_cpirq_schedules_work - cpirq handler queues cpirq_work + * @test: KUnit test context + * + * hdcp_handle_cpirq() should schedule cpirq_work for the selected link. + */ +static void dm_test_hdcp_handle_cpirq_schedules_work(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work->cpirq_work, dummy_work_fn); + + hdcp_handle_cpirq(work, 0); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->cpirq_work)); + + cancel_work_sync(&work->cpirq_work); +} + +/** + * dm_test_hdcp_handle_cpirq_selects_link_index - only selected link work is queued + * @test: KUnit test context + * + * hdcp_handle_cpirq() should schedule cpirq_work for the selected index + * and not queue unrelated links. + */ +static void dm_test_hdcp_handle_cpirq_selects_link_index(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kcalloc(test, 2, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work[0].cpirq_work, dummy_work_fn); + INIT_WORK(&work[1].cpirq_work, dummy_work_fn); + + hdcp_handle_cpirq(work, 1); + + KUNIT_EXPECT_FALSE(test, work_pending(&work[0].cpirq_work)); + KUNIT_EXPECT_TRUE(test, work_pending(&work[1].cpirq_work)); + + cancel_work_sync(&work[1].cpirq_work); +} + +/* End of tests for hdcp_handle_cpirq() */ + +/* Tests for hdcp_update_display() helper logic */ + +/** + * dm_test_hdcp_update_display_enable_schedules_property_validate - enable path queues validate work + * @test: KUnit test context + * + * hdcp_update_display() should schedule property_validate_dwork when + * encryption is enabled. + */ +static void dm_test_hdcp_update_display_enable_schedules_property_validate(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->property_validate_dwork, dummy_work_fn); + work->srm_size = 0; + + hdcp_update_display_encryption_control(work, work, 0, true); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->property_validate_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate - disable path state update + * @test: KUnit test context + * + * hdcp_update_display() should set encryption_status to HDCP_OFF and + * cancel property_validate_dwork when encryption is disabled. + */ +static void dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate(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->property_validate_dwork, dummy_work_fn); + schedule_delayed_work(&work->property_validate_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->property_validate_dwork)); + + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + hdcp_update_display_encryption_control(work, work, 3, false); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->property_validate_dwork)); +} + +/* End of tests for hdcp_update_display() helper logic */ + +/* Tests for hdcp_create_workqueue() */ + +/** + * dm_test_hdcp_create_workqueue_zero_max_links_returns_null - zero-link creation fails early + * @test: KUnit test context + * + * When dc->caps.max_links is zero, hdcp_create_workqueue() should fail + * the initial allocation path and return NULL. + */ +static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kunit *test) +{ + struct cp_psp cp_psp = {0}; + struct dc *dc; + struct hdcp_workqueue *work; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + dc->caps.max_links = 0; + + work = hdcp_create_workqueue(NULL, &cp_psp, dc); + + KUNIT_EXPECT_PTR_EQ(test, work, NULL); +} + +/* End of tests for hdcp_create_workqueue() */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -423,6 +651,18 @@ static struct kunit_case dm_hdcp_test_cases[] = { 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), + KUNIT_CASE(dm_test_process_output_callback_stop_and_needed_requeues), + KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues), + /* event_property_update() */ + KUNIT_CASE(dm_test_event_property_update_skips_null_connector), + /* hdcp_handle_cpirq() */ + KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work), + KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index), + /* hdcp_update_display() helper logic */ + KUNIT_CASE(dm_test_hdcp_update_display_enable_schedules_property_validate), + KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate), + /* hdcp_create_workqueue() */ + KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null), {} }; From 47c62f92ff85277453cd5e035d152c7b688c6934 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Mon, 22 Jun 2026 14:57:38 -0400 Subject: [PATCH 059/155] drm/amd/display: More Kunit tests for audio Split audio init to make the initialization modular Test the audio pin initialization Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 40 ++++-- .../amd/display/amdgpu_dm/amdgpu_dm_audio.h | 2 + .../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 118 ++++++++++++++++++ 3 files changed, 147 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c index 13c9a9d145ba..fd4e185bf801 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -116,8 +116,31 @@ static const struct component_ops amdgpu_dm_audio_component_bind_ops = { .unbind = amdgpu_dm_audio_component_unbind, }; +STATIC_IFN_KUNIT +void amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count, + const unsigned int *inst_array) +{ + int i; + + adev->mode_info.audio.num_pins = audio_count; + + for (i = 0; i < audio_count; i++) { + adev->mode_info.audio.pin[i].channels = -1; + adev->mode_info.audio.pin[i].rate = -1; + adev->mode_info.audio.pin[i].bits_per_sample = -1; + adev->mode_info.audio.pin[i].status_bits = 0; + adev->mode_info.audio.pin[i].category_code = 0; + adev->mode_info.audio.pin[i].connected = false; + adev->mode_info.audio.pin[i].id = inst_array[i]; + adev->mode_info.audio.pin[i].offset = 0; + } +} +EXPORT_IF_KUNIT(amdgpu_dm_audio_init_pins); + int amdgpu_dm_audio_init(struct amdgpu_device *adev) { + unsigned int inst_array[MAX_AUDIOS]; + int audio_count; int i, ret; if (!amdgpu_audio) @@ -125,19 +148,10 @@ int amdgpu_dm_audio_init(struct amdgpu_device *adev) adev->mode_info.audio.enabled = true; - adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count; - - for (i = 0; i < adev->mode_info.audio.num_pins; i++) { - adev->mode_info.audio.pin[i].channels = -1; - adev->mode_info.audio.pin[i].rate = -1; - adev->mode_info.audio.pin[i].bits_per_sample = -1; - adev->mode_info.audio.pin[i].status_bits = 0; - adev->mode_info.audio.pin[i].category_code = 0; - adev->mode_info.audio.pin[i].connected = false; - adev->mode_info.audio.pin[i].id = - adev->dm.dc->res_pool->audios[i]->inst; - adev->mode_info.audio.pin[i].offset = 0; - } + audio_count = adev->dm.dc->res_pool->audio_count; + for (i = 0; i < audio_count; i++) + inst_array[i] = adev->dm.dc->res_pool->audios[i]->inst; + amdgpu_dm_audio_init_pins(adev, audio_count, inst_array); ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); if (ret < 0) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h index 7acfc5ef69b3..018e9919a118 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -51,6 +51,8 @@ void amdgpu_dm_audio_component_unbind(struct device *kdev, void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); int amdgpu_dm_audio_get_param(void); void amdgpu_dm_audio_set_param(int val); +void amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count, + const unsigned int *inst_array); #endif #endif /* __AMDGPU_DM_AUDIO_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c index 79ff5d9b3fa5..3f0108e9a951 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c @@ -6,10 +6,13 @@ */ #include +#include #include #include "dc.h" +#include "dc/inc/core_types.h" +#include "dc/inc/hw/audio.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" @@ -38,6 +41,57 @@ static void dm_test_audio_init_disabled(struct kunit *test) amdgpu_dm_audio_set_param(saved_audio); } +/** + * dm_test_audio_init_enabled_success - Test init deeper path when audio is enabled + * @test: The KUnit test context + */ +static void dm_test_audio_init_enabled_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct resource_pool *res_pool; + struct audio *audio0; + struct audio *audio1; + struct device *dev; + int saved_audio = amdgpu_dm_audio_get_param(); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + audio0 = kunit_kzalloc(test, sizeof(*audio0), GFP_KERNEL); + audio1 = kunit_kzalloc(test, sizeof(*audio1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio1); + + dev = root_device_register("kunit-dm-audio-init"); + KUNIT_ASSERT_FALSE(test, IS_ERR(dev)); + + audio0->inst = 2; + audio1->inst = 6; + res_pool->audio_count = 2; + res_pool->audios[0] = audio0; + res_pool->audios[1] = audio1; + dc->res_pool = res_pool; + adev->dm.dc = dc; + adev->dev = dev; + + amdgpu_dm_audio_set_param(1); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_audio_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->mode_info.audio.enabled); + KUNIT_EXPECT_TRUE(test, adev->dm.audio_registered); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.num_pins, 2); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[0].id, 2U); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[1].id, 6U); + + amdgpu_dm_audio_fini(adev); + root_device_unregister(dev); + amdgpu_dm_audio_set_param(saved_audio); +} + /* Tests for amdgpu_dm_audio_fini() */ /** @@ -455,9 +509,73 @@ static void dm_test_eld_notify_null_callback(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); } +/* Tests for amdgpu_dm_audio_init_pins() */ + +/** + * dm_test_audio_init_pins_sets_defaults - pin entries are initialised to default values + * @test: The KUnit test context + * + * amdgpu_dm_audio_init_pins() must set num_pins from audio_count, reset every + * pin to the sentinel defaults (-1 for rate/channels/bits, 0 for the rest) + * and copy each pin's hardware instance index from res_pool->audios[i]->inst. + */ +static void dm_test_audio_init_pins_sets_defaults(struct kunit *test) +{ + struct amdgpu_device *adev; + const unsigned int inst_array[] = {3, 7}; + int i; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + amdgpu_dm_audio_init_pins(adev, 2, inst_array); + + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.num_pins, 2); + + for (i = 0; i < 2; i++) { + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].channels, -1); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].rate, -1); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].bits_per_sample, -1); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].status_bits, 0); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].category_code, 0); + KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.pin[i].connected); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].offset, 0); + } + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[0].id, 3U); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[1].id, 7U); +} + +/** + * dm_test_audio_init_pins_zero_count - zero audio_count leaves num_pins at zero + * @test: The KUnit test context + * + * When res_pool->audio_count is 0, num_pins must be 0 and no pins touched. + */ +static void dm_test_audio_init_pins_zero_count(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Pre-fill a sentinel so we can confirm the loop never ran. */ + adev->mode_info.audio.pin[0].channels = 99; + + amdgpu_dm_audio_init_pins(adev, 0, NULL); + + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.num_pins, 0); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[0].channels, 99); +} + +/* End of tests for amdgpu_dm_audio_init_pins() */ + static struct kunit_case dm_audio_test_cases[] = { /* amdgpu_dm_audio_init */ KUNIT_CASE(dm_test_audio_init_disabled), + KUNIT_CASE(dm_test_audio_init_enabled_success), + /* amdgpu_dm_audio_init_pins */ + KUNIT_CASE(dm_test_audio_init_pins_sets_defaults), + KUNIT_CASE(dm_test_audio_init_pins_zero_count), /* amdgpu_dm_audio_fini */ KUNIT_CASE(dm_test_audio_fini_without_enabled_audio), /* amdgpu_dm_fill_audio_info */ From 9790ecb376309173ba7cb7194954d42d81f553e3 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Thu, 25 Jun 2026 13:15:28 -0400 Subject: [PATCH 060/155] drm/amd/display: Add color space and timing tests for connector Add KUnit coverage for adjust_colour_depth_from_display_info() 420 reduction, amdgpu_dm_get_output_color_space() Y_ONLY/YCbCr601/BT2020 fallbacks, to_drm_connector_type() dual-link DVI-D, and copy_crtc_timing_for_drm_display_mode(). Export copy_crtc_timing_for_drm_display_mode via EXPORT_IF_KUNIT so the tests can reach it. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 3 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 2 + .../tests/amdgpu_dm_connector_test.c | 186 ++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index dfce7f79dfe0..1a1c3642c456 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -972,7 +972,7 @@ static void fill_stream_properties_from_drm_display_mode( stream->content_type = get_output_content_type(connector_state); } -static void +STATIC_IFN_KUNIT void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, struct drm_display_mode *dst_mode) { @@ -991,6 +991,7 @@ copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end; dst_mode->crtc_vtotal = src_mode->crtc_vtotal; } +EXPORT_IF_KUNIT(copy_crtc_timing_for_drm_display_mode); STATIC_IFN_KUNIT void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 23843ac99642..ca0fdb0dc41a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -155,6 +155,8 @@ bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, int to_drm_connector_type(enum signal_type st, uint32_t connector_id); bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode); enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in); +void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, + struct drm_display_mode *dst_mode); void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 45210c64a000..91f9543dd7bb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -290,6 +290,30 @@ static void dm_test_adjust_colour_depth_420_halves_clk(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); } +/** + * dm_test_adjust_colour_depth_420_reduces - Test Adjust colour depth 420 reduces + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_420_reduces(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 4K @ 594000 KHz = 5940000 in 100Hz units */ + timing.pix_clk_100hz = 5940000; + timing.display_color_depth = COLOR_DEPTH_121212; + timing.pixel_encoding = PIXEL_ENCODING_YCBCR420; + /* + * With 420: effective = 594000/2 = 297000. + * 12bpc = 297000*36/24 = 445500 (exceeds limit), + * 10bpc = 297000*30/24 = 371250 (fits). + */ + info.max_tmds_clock = 400000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + /** * dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc - Test Adjust colour * depth reduces 12bpc to 10bpc @@ -550,6 +574,78 @@ static void dm_test_output_color_space_bt2020_ycc(struct kunit *test) (int)COLOR_SPACE_2020_YCBCR_LIMITED); } +/** + * dm_test_output_color_space_default_ycbcr709_y_only - Test Output color space + * default ycbcr709 limited via Y_ONLY at high pixel clock + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr709_y_only(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 300000; + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709_LIMITED); +} + +/** + * dm_test_output_color_space_default_ycbcr601 - Test Output color space default + * ycbcr601 full range at low pixel clock + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr601(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 270300; + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601); +} + +/** + * dm_test_output_color_space_bt2020_ycc_rgb_encoding - Test Output color space + * bt2020 ycc with rgb pixel encoding falls back to full range rgb + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_FULLRANGE); +} + +/** + * dm_test_output_color_space_bt2020_rgb_ycc_encoding - Test Output color space + * bt2020 rgb with non-rgb pixel encoding falls back to limited ycbcr + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_ycc_encoding(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_YCBCR_LIMITED); +} + /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc() */ /** @@ -834,6 +930,17 @@ static void dm_test_to_connector_type_dvi_dvid(struct kunit *test) KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID); } +/** + * dm_test_to_connector_type_dual_link_dvid - Test To connector type dual link dvid + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dual_link_dvid(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_DUAL_LINK, CONNECTOR_ID_DUAL_LINK_DVID); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID); +} + /** * dm_test_to_connector_type_virtual - Test To connector type virtual * @test: The KUnit test context @@ -1124,6 +1231,76 @@ static void dm_test_aspect_ratio_256_135(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_256_135); } +/* Tests for copy_crtc_timing_for_drm_display_mode() */ + +/** + * dm_test_copy_crtc_timing_copies_all_fields - Test all crtc timing fields copied + * @test: The KUnit test context + */ +static void dm_test_copy_crtc_timing_copies_all_fields(struct kunit *test) +{ + struct drm_display_mode src = {}; + struct drm_display_mode dst = {}; + + src.crtc_hdisplay = 1920; + src.crtc_vdisplay = 1080; + src.crtc_clock = 148500; + src.crtc_hblank_start = 1920; + src.crtc_hblank_end = 2200; + src.crtc_hsync_start = 2008; + src.crtc_hsync_end = 2052; + src.crtc_htotal = 2200; + src.crtc_hskew = 1; + src.crtc_vblank_start = 1080; + src.crtc_vblank_end = 1125; + src.crtc_vsync_start = 1084; + src.crtc_vsync_end = 1089; + src.crtc_vtotal = 1125; + + copy_crtc_timing_for_drm_display_mode(&src, &dst); + + KUNIT_EXPECT_EQ(test, dst.crtc_hdisplay, 1920); + KUNIT_EXPECT_EQ(test, dst.crtc_vdisplay, 1080); + KUNIT_EXPECT_EQ(test, dst.crtc_clock, 148500); + KUNIT_EXPECT_EQ(test, dst.crtc_hblank_start, 1920); + KUNIT_EXPECT_EQ(test, dst.crtc_hblank_end, 2200); + KUNIT_EXPECT_EQ(test, dst.crtc_hsync_start, 2008); + KUNIT_EXPECT_EQ(test, dst.crtc_hsync_end, 2052); + KUNIT_EXPECT_EQ(test, dst.crtc_htotal, 2200); + KUNIT_EXPECT_EQ(test, dst.crtc_hskew, 1); + KUNIT_EXPECT_EQ(test, dst.crtc_vblank_start, 1080); + KUNIT_EXPECT_EQ(test, dst.crtc_vblank_end, 1125); + KUNIT_EXPECT_EQ(test, dst.crtc_vsync_start, 1084); + KUNIT_EXPECT_EQ(test, dst.crtc_vsync_end, 1089); + KUNIT_EXPECT_EQ(test, dst.crtc_vtotal, 1125); +} + +/** + * dm_test_copy_crtc_timing_leaves_non_crtc_fields - Test non-crtc fields untouched + * @test: The KUnit test context + */ +static void dm_test_copy_crtc_timing_leaves_non_crtc_fields(struct kunit *test) +{ + struct drm_display_mode src = {}; + struct drm_display_mode dst = {}; + + src.crtc_hdisplay = 1280; + src.crtc_vdisplay = 720; + + /* Non-crtc geometry on dst must be preserved by the copy */ + dst.hdisplay = 1920; + dst.vdisplay = 1080; + dst.clock = 148500; + + copy_crtc_timing_for_drm_display_mode(&src, &dst); + + KUNIT_EXPECT_EQ(test, dst.crtc_hdisplay, 1280); + KUNIT_EXPECT_EQ(test, dst.crtc_vdisplay, 720); + KUNIT_EXPECT_EQ(test, dst.hdisplay, 1920); + KUNIT_EXPECT_EQ(test, dst.vdisplay, 1080); + KUNIT_EXPECT_EQ(test, dst.clock, 148500); +} + /* Tests for decide_crtc_timing_for_drm_display_mode() */ /** @@ -2372,6 +2549,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_adjust_colour_depth_reduces_to_888), KUNIT_CASE(dm_test_adjust_colour_depth_10bpc_passes), KUNIT_CASE(dm_test_adjust_colour_depth_420_halves_clk), + KUNIT_CASE(dm_test_adjust_colour_depth_420_reduces), KUNIT_CASE(dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc), KUNIT_CASE(dm_test_adjust_colour_depth_16bpc_no_fallback), KUNIT_CASE(dm_test_adjust_colour_depth_none_fits), @@ -2388,6 +2566,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr601), + KUNIT_CASE(dm_test_output_color_space_bt2020_ycc_rgb_encoding), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_ycc_encoding), /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */ KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings), KUNIT_CASE(dm_test_convert_color_depth_bpc_unknown), @@ -2412,6 +2594,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_to_connector_type_dvi_dvii), KUNIT_CASE(dm_test_to_connector_type_dual_link_dvii), KUNIT_CASE(dm_test_to_connector_type_dvi_dvid), + KUNIT_CASE(dm_test_to_connector_type_dual_link_dvid), KUNIT_CASE(dm_test_to_connector_type_virtual), KUNIT_CASE(dm_test_to_connector_type_unknown), /* is_duplicate_mode */ @@ -2433,6 +2616,9 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_aspect_ratio_16_9), KUNIT_CASE(dm_test_aspect_ratio_64_27), KUNIT_CASE(dm_test_aspect_ratio_256_135), + /* copy_crtc_timing_for_drm_display_mode */ + KUNIT_CASE(dm_test_copy_crtc_timing_copies_all_fields), + KUNIT_CASE(dm_test_copy_crtc_timing_leaves_non_crtc_fields), /* decide_crtc_timing_for_drm_display_mode */ KUNIT_CASE(dm_test_decide_crtc_timing_scale_enabled), KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), From 9ce016cfdc6e7bc87faf42f723c54f5bca5ea028 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Thu, 25 Jun 2026 14:53:11 -0400 Subject: [PATCH 061/155] drm/amd/display: Add subconnector property tests for connector Add KUnit coverage for update_subconnector_property() on the amdgpu_dm_connector suite: - DP connector with a sink: subconnector property is resolved from the dongle type (VGA converter -> VGA). - DP connector without a sink: dongle type is ignored and the property stays Unknown. - Non-DP connector: function early-returns and leaves a pre-seeded property value untouched. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 3 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 1 + .../tests/amdgpu_dm_connector_test.c | 142 ++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 1a1c3642c456..b24a4afb9153 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -235,7 +235,7 @@ STATIC_IFN_KUNIT enum drm_mode_subconnector get_subconnector_type(struct dc_link } EXPORT_IF_KUNIT(get_subconnector_type); -static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void update_subconnector_property(struct amdgpu_dm_connector *aconnector) { struct dc_link *link = aconnector->dc_link; struct drm_connector *connector = &aconnector->base; @@ -251,6 +251,7 @@ static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) connector->dev->mode_config.dp_subconnector_property, subconnector); } +EXPORT_IF_KUNIT(update_subconnector_property); static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index ca0fdb0dc41a..ecf23d0fb72e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -147,6 +147,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); +void update_subconnector_property(struct amdgpu_dm_connector *aconnector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 91f9543dd7bb..782a22e4f074 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -2527,6 +2527,144 @@ static void dm_test_set_panel_type_defaults_to_lcd(struct kunit *test) (int)PANEL_TYPE_LCD); } +/* Tests for update_subconnector_property() */ + +/** + * dm_test_update_subconnector_dp_with_sink - Test subconnector property is set + * from the dongle type for a DisplayPort connector with a sink + * @test: The KUnit test context + */ +static void dm_test_update_subconnector_dp_with_sink(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + uint64_t val = 0; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + drm_connector_attach_dp_subconnector_property(&aconnector->base); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; + aconnector->dc_link = link; + /* Any non-NULL sink enables dongle-type resolution */ + aconnector->dc_sink = kunit_kzalloc(test, sizeof(*aconnector->dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector->dc_sink); + + update_subconnector_property(aconnector); + + KUNIT_EXPECT_EQ(test, drm_object_property_get_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_VGA); +} + +/** + * dm_test_update_subconnector_dp_no_sink - Test subconnector property stays + * unknown for a DisplayPort connector without a sink + * @test: The KUnit test context + */ +static void dm_test_update_subconnector_dp_no_sink(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + uint64_t val = 0; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + drm_connector_attach_dp_subconnector_property(&aconnector->base); + + /* Dongle type is set, but no sink means it must not be consulted */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + aconnector->dc_link = link; + aconnector->dc_sink = NULL; + + update_subconnector_property(aconnector); + + KUNIT_EXPECT_EQ(test, drm_object_property_get_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_Unknown); +} + +/** + * dm_test_update_subconnector_non_dp_noop - Test non-DisplayPort connector is + * left untouched (early return) + * @test: The KUnit test context + */ +static void dm_test_update_subconnector_non_dp_noop(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + uint64_t val = 0; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, NULL); + drm_connector_attach_dp_subconnector_property(&aconnector->base); + + /* Pre-seed the property to a non-default value */ + drm_object_property_set_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + DRM_MODE_SUBCONNECTOR_VGA); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + aconnector->dc_link = link; + aconnector->dc_sink = kunit_kzalloc(test, sizeof(*aconnector->dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector->dc_sink); + + update_subconnector_property(aconnector); + + /* Non-DP connector: value must remain what we seeded */ + KUNIT_EXPECT_EQ(test, drm_object_property_get_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_VGA); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2664,6 +2802,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode), KUNIT_CASE(dm_test_is_freesync_video_mode_match), KUNIT_CASE(dm_test_is_freesync_video_mode_no_match), + /* update_subconnector_property */ + KUNIT_CASE(dm_test_update_subconnector_dp_with_sink), + KUNIT_CASE(dm_test_update_subconnector_dp_no_sink), + KUNIT_CASE(dm_test_update_subconnector_non_dp_noop), /* amdgpu_dm_update_cacp_caps */ KUNIT_CASE(dm_test_cacp_caps_unsupported_ip), KUNIT_CASE(dm_test_cacp_caps_excluded_ip_316), From 5788ae1fb5effd6be75b13021c7e61e4f0a26fb3 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 23 Jun 2026 14:41:01 -0400 Subject: [PATCH 062/155] drm/amd/display: Add FBC init tests for connector Add KUnit coverage for amdgpu_dm_fbc_init() on the amdgpu_dm_connector tests: - No FBC present - Non-eDP link - Buffer already allocated Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 3 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 1 + .../tests/amdgpu_dm_connector_test.c | 112 ++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index b24a4afb9153..82eb8b0bcc4b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -255,7 +255,7 @@ EXPORT_IF_KUNIT(update_subconnector_property); static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); -static void amdgpu_dm_fbc_init(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_fbc_init(struct drm_connector *connector) { struct amdgpu_device *adev = drm_to_adev(connector->dev); struct dm_compressor_info *compressor = &adev->dm.compressor; @@ -293,6 +293,7 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector) } } +EXPORT_IF_KUNIT(amdgpu_dm_fbc_init); int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index ecf23d0fb72e..7d351587c42a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -148,6 +148,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); void update_subconnector_property(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_fbc_init(struct drm_connector *connector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 782a22e4f074..aa274f5e4b84 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -2665,6 +2665,113 @@ static void dm_test_update_subconnector_non_dp_noop(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_VGA); } +/* Tests for amdgpu_dm_fbc_init() */ + +/* + * Build an amdgpu_dm_connector wired to a kunit-allocated amdgpu_device so + * that drm_to_adev() and to_amdgpu_dm_connector() resolve correctly, with a + * dc, dc_link and an empty modes list ready for amdgpu_dm_fbc_init(). + */ +struct dm_test_fbc_ctx { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc *dc; + struct dc_link *link; +}; + +static struct dm_test_fbc_ctx *dm_test_fbc_ctx_alloc(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->adev = kunit_kzalloc(test, sizeof(*ctx->adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->adev); + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc); + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + + ctx->aconnector->base.dev = &ctx->adev->ddev; + INIT_LIST_HEAD(&ctx->aconnector->base.modes); + ctx->adev->dm.dc = ctx->dc; + ctx->aconnector->dc_link = ctx->link; + + /* Default to the fully-enabled path so each test only flips one knob */ + ctx->link->connector_signal = SIGNAL_TYPE_EDP; + ctx->dc->fbc_compressor = + (struct compressor *)kunit_kzalloc(test, sizeof(void *), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc->fbc_compressor); + + return ctx; +} + +/** + * dm_test_fbc_init_no_compressor - Test fbc_init is a no-op without a compressor + * @test: The KUnit test context + */ +static void dm_test_fbc_init_no_compressor(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + + ctx->dc->fbc_compressor = NULL; + + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); +} + +/** + * dm_test_fbc_init_non_edp - Test fbc_init is a no-op for non-eDP links + * @test: The KUnit test context + */ +static void dm_test_fbc_init_non_edp(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); +} + +/** + * dm_test_fbc_init_already_allocated - Test fbc_init keeps an existing buffer + * @test: The KUnit test context + */ +static void dm_test_fbc_init_already_allocated(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + struct amdgpu_bo *existing; + + existing = kunit_kzalloc(test, sizeof(void *), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, existing); + ctx->adev->dm.compressor.bo_ptr = existing; + + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + /* Buffer already present → left untouched, no reallocation */ + KUNIT_EXPECT_PTR_EQ(test, ctx->adev->dm.compressor.bo_ptr, existing); +} + +/** + * dm_test_fbc_init_no_modes - Test fbc_init skips allocation with no modes + * @test: The KUnit test context + */ +static void dm_test_fbc_init_no_modes(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + + /* All prerequisites met but the modes list is empty → max_size 0 */ + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2822,6 +2929,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_set_panel_type_did_lcd), KUNIT_CASE(dm_test_set_panel_type_vendor_lum_heuristic), KUNIT_CASE(dm_test_set_panel_type_defaults_to_lcd), + /* amdgpu_dm_fbc_init */ + KUNIT_CASE(dm_test_fbc_init_no_compressor), + KUNIT_CASE(dm_test_fbc_init_non_edp), + KUNIT_CASE(dm_test_fbc_init_already_allocated), + KUNIT_CASE(dm_test_fbc_init_no_modes), {} }; From da2fe19f1b0a9a3ca03001c6c741605a9586f352 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 063/155] drm/amd/display: Test format info and blending [WHAT] Add KUnit coverage for amdgpu_dm_plane_get_format_info(), the overlay universal-plane path of get_plane_formats(), the D-swizzle rejection path of format_mod_supported(), and the DCN4.2 variant of fill_blending_from_plane_state(). These exercise format-query and blending code paths that the existing suite did not cover. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 141 +++++++++++++++++- 1 file changed, 134 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 46c9af432e37..512c51e60559 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -5,13 +5,16 @@ * Copyright 2026 Advanced Micro Devices, Inc. */ - #include - #include - #include "link_enc_cfg.h" - #include "amdgpu_dm_plane.h" - #include - #include - +#include +#include +#include +#include "link_enc_cfg.h" +#include "amdgpu_dm_plane.h" +#include "amdgpu_rlc.h" +#include "gc/gc_11_0_0_offset.h" +#include "gc/gc_11_0_0_sh_mask.h" +#include +#include struct dm_test_dcc_cap_ctx { bool callback_ret; @@ -1162,19 +1165,142 @@ static void dm_test_fill_gfx9_tiling_info_from_modifier_nv(struct kunit *test) KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); } +/** + * dm_test_get_format_info() - Verify modifier-based format info lookup. + * @test: KUnit test context. + * + * Verify if non-AMD modifiers return NULL and AMD DCC modifiers resolve a + * dedicated format info structure. + */ +static void dm_test_get_format_info(struct kunit *test) +{ + u64 dcc_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(DCC, 1); + const struct drm_format_info *format_info; + + KUNIT_EXPECT_PTR_EQ(test, + (void *)amdgpu_dm_plane_get_format_info(DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR), + NULL); + format_info = amdgpu_dm_plane_get_format_info(DRM_FORMAT_XRGB8888, dcc_mod); + KUNIT_EXPECT_NOT_NULL(test, format_info); +} + +/** + * dm_test_fill_blending_global_alpha_dcn42() - Verify DCN 4.2 alpha scaling. + * @test: KUnit test context. + * + * Verify if DCN 4.2 scales the 16-bit DRM alpha down by 4 bits instead of 8. + */ +static void dm_test_fill_blending_global_alpha_dcn42(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane *plane; + struct drm_plane_state *state; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0); + plane->dev = &adev->ddev; + state->plane = plane; + state->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state->alpha = 0x8000; + + amdgpu_dm_plane_fill_blending_from_plane_state(state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_TRUE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0x800); +} + +/** + * dm_test_get_plane_formats_overlay_universal_cap() - Verify universal overlay. + * @test: KUnit test context. + * + * Verify if an overlay plane with a DCN universal plane cap reports the RGB + * format list instead of the overlay-only list. + */ +static void dm_test_get_plane_formats_overlay_universal_cap(struct kunit *test) +{ + struct drm_plane *plane; + struct dc_plane_cap *cap; + u32 formats[32] = {0}; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + cap = kunit_kzalloc(test, sizeof(*cap), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, cap); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + cap->type = DC_PLANE_TYPE_DCN_UNIVERSAL; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_get_plane_formats(plane, cap, formats, 32), + 14); +} + +/** + * dm_test_format_mod_supported_d_swizzle_reject() - Verify D swizzle rejection. + * @test: KUnit test context. + * + * Verify if a D micro-swizzle modifier is rejected for formats narrower than + * 8 bytes per pixel. + */ +static void dm_test_format_mod_supported_d_swizzle_reject(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane *plane; + u64 listed_mod; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); + + adev->family = AMDGPU_FAMILY_RV; + plane->dev = &adev->ddev; + + listed_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9); + plane->modifiers = &listed_mod; + plane->modifier_count = 1; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, + listed_mod)); +} + static struct kunit_case amdgpu_dm_plane_test_cases[] = { /* amdgpu_dm_plane_is_video_format() */ KUNIT_CASE(dm_test_plane_is_video_format_known_video), + /* amdgpu_dm_plane_get_format_info() */ + KUNIT_CASE(dm_test_get_format_info), /* amdgpu_dm_plane_fill_blending_from_plane_state() */ KUNIT_CASE(dm_test_fill_blending_defaults), KUNIT_CASE(dm_test_fill_blending_premulti_alpha_format), KUNIT_CASE(dm_test_fill_blending_coverage_alpha_format), KUNIT_CASE(dm_test_fill_blending_global_alpha), + KUNIT_CASE(dm_test_fill_blending_global_alpha_dcn42), /* amdgpu_dm_plane_modifier_* helpers() */ KUNIT_CASE(dm_test_modifier_has_dcc), KUNIT_CASE(dm_test_modifier_gfx9_swizzle_mode), /* amdgpu_dm_plane_get_plane_formats() */ KUNIT_CASE(dm_test_get_plane_formats), + KUNIT_CASE(dm_test_get_plane_formats_overlay_universal_cap), /* amdgpu_dm_plane_get_plane_modifiers() */ KUNIT_CASE(dm_test_get_plane_modifiers), /* amdgpu_dm_plane_fill_dc_scaling_info() */ @@ -1187,6 +1313,7 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_get_cursor_position), /* amdgpu_dm_plane_format_mod_supported() */ KUNIT_CASE(dm_test_format_mod_supported), + KUNIT_CASE(dm_test_format_mod_supported_d_swizzle_reject), /* amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ From 05b3588830bf0665f9e952a92b75f774b4af8217 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 064/155] drm/amd/display: Test GFX9/GFX10 plane modifiers [WHAT] Add KUnit tests for amdgpu_dm_plane_get_plane_modifiers() on GFX9, Raven, Raven constant-encode, GFX10.1 and GFX10.3 devices. Introduce the dm_test_expect_mods_terminated(), dm_test_mods_contain() and dm_test_get_primary_mods() helpers used to validate the generated modifier lists. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 512c51e60559..9cf43e732eb5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -1226,6 +1226,48 @@ static void dm_test_fill_blending_global_alpha_dcn42(struct kunit *test) KUNIT_EXPECT_EQ(test, global_alpha_value, 0x800); } +static void dm_test_expect_mods_terminated(struct kunit *test, struct amdgpu_device *adev) +{ + u64 *mods = NULL; + int ret; + int i; + + ret = amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_NULL(test, mods); + + for (i = 0; mods[i] != DRM_FORMAT_MOD_INVALID; i++) + ; + + KUNIT_EXPECT_GT(test, i, 0); + KUNIT_EXPECT_EQ(test, mods[i - 1], DRM_FORMAT_MOD_LINEAR); + kfree(mods); +} + +static bool dm_test_mods_contain(const u64 *mods, u64 expected) +{ + int i; + + for (i = 0; mods[i] != DRM_FORMAT_MOD_INVALID; i++) { + if (mods[i] == expected) + return true; + } + + return false; +} + +static u64 *dm_test_get_primary_mods(struct kunit *test, struct amdgpu_device *adev) +{ + u64 *mods = NULL; + int ret; + + ret = amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_NULL(test, mods); + + return mods; +} + /** * dm_test_get_plane_formats_overlay_universal_cap() - Verify universal overlay. * @test: KUnit test context. @@ -1252,6 +1294,178 @@ static void dm_test_get_plane_formats_overlay_universal_cap(struct kunit *test) 14); } +/** + * dm_test_get_plane_modifiers_gfx9() - Verify GFX9 modifier list generation. + * @test: KUnit test context. + * + * Verify if the GFX9 family produces a non-empty modifier list terminated by + * LINEAR and INVALID entries. + */ +static void dm_test_get_plane_modifiers_gfx9(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_AI; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 8; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + + dm_test_expect_mods_terminated(test, adev); +} + +/** + * dm_test_get_plane_modifiers_rv() - Verify RV modifier list generation. + * @test: KUnit test context. + * + * Verify if pre-Raven2 RV devices add RV-specific S-swizzle modifiers and + * non-constant-encode DCC modifiers. + */ +static void dm_test_get_plane_modifiers_rv(struct kunit *test) +{ + struct amdgpu_device *adev; + u64 *mods; + u64 dcc_mod; + u64 s_x_mod; + u64 s_mod; + int pipes = 2; + int pipe_xor_bits = 3; + int bank_xor_bits = 2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_RV; + adev->asic_type = CHIP_RAVEN; + adev->external_rev_id = 0x80; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + + mods = dm_test_get_primary_mods(test, adev); + dcc_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, bank_xor_bits) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_64B) | + AMD_FMT_MOD_SET(DCC_CONSTANT_ENCODE, 0); + s_x_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, bank_xor_bits); + s_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9); + + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod)); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod | + AMD_FMT_MOD_SET(DCC_RETILE, 1) | + AMD_FMT_MOD_SET(RB, 2) | + AMD_FMT_MOD_SET(PIPE, pipes))); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, s_x_mod)); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, s_mod)); + + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_rv_constant_encode() - Verify Raven2+ modifiers. + * @test: KUnit test context. + * + * Verify if Raven2 and later RV devices add the constant-encode modifier + * variants. + */ +static void dm_test_get_plane_modifiers_rv_constant_encode(struct kunit *test) +{ + struct amdgpu_device *adev; + u64 *mods; + u64 dcc_mod; + int pipes = 2; + int pipe_xor_bits = 3; + int bank_xor_bits = 2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_RV; + adev->asic_type = CHIP_RAVEN; + adev->external_rev_id = 0x81; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + + mods = dm_test_get_primary_mods(test, adev); + dcc_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, bank_xor_bits) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_64B) | + AMD_FMT_MOD_SET(DCC_CONSTANT_ENCODE, 1); + + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod)); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod | + AMD_FMT_MOD_SET(DCC_RETILE, 1) | + AMD_FMT_MOD_SET(RB, 2) | + AMD_FMT_MOD_SET(PIPE, pipes))); + + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_gfx10_1() - Verify GFX10.1 modifier list generation. + * @test: KUnit test context. + * + * Verify if a pre-10.3 NV family device dispatches to the GFX10.1 modifier + * builder and produces a terminated list. + */ +static void dm_test_get_plane_modifiers_gfx10_1(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 1, 0); + + dm_test_expect_mods_terminated(test, adev); +} + +/** + * dm_test_get_plane_modifiers_gfx10_3() - Verify GFX10.3 modifier list generation. + * @test: KUnit test context. + * + * Verify if a 10.3+ NV family device dispatches to the GFX10.3 modifier + * builder and produces a terminated list. + */ +static void dm_test_get_plane_modifiers_gfx10_3(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + dm_test_expect_mods_terminated(test, adev); +} + /** * dm_test_format_mod_supported_d_swizzle_reject() - Verify D swizzle rejection. * @test: KUnit test context. @@ -1303,6 +1517,11 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_get_plane_formats_overlay_universal_cap), /* amdgpu_dm_plane_get_plane_modifiers() */ KUNIT_CASE(dm_test_get_plane_modifiers), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx9), + KUNIT_CASE(dm_test_get_plane_modifiers_rv), + KUNIT_CASE(dm_test_get_plane_modifiers_rv_constant_encode), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx10_1), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx10_3), /* amdgpu_dm_plane_fill_dc_scaling_info() */ KUNIT_CASE(dm_test_fill_dc_scaling_info), /* amdgpu_dm_plane_get_min_max_dc_plane_scaling() */ From f160af13dc582dace0ba64dd687bf148dfa8ac96 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 065/155] drm/amd/display: Test GFX11/GFX12 plane modifiers [WHAT] Add KUnit tests for amdgpu_dm_plane_get_plane_modifiers() on GFX11 (64K-first and 256K-first) and GFX12 devices. Add a register-read mock (dm_test_gfx11_reg_ctx and friends) and device-setup helpers so the GFX11 DCC modifier ordering can be checked without real hardware. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 9cf43e732eb5..e1171c6dafe3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -26,6 +26,39 @@ struct dm_test_dcc_cap_ctx { static struct dm_test_dcc_cap_ctx *dm_test_dcc_ctx; +struct dm_test_gfx11_reg_ctx { + u32 gb_addr_config; + u32 gc_reg_offsets[1]; + u32 expected_reg; + u32 captured_reg; + u32 captured_acc_flags; + u32 captured_hwip; + u32 captured_xcc_id; + bool called; +}; + +static struct dm_test_gfx11_reg_ctx *dm_test_gfx11_reg_ctx; + +static u32 dm_test_gfx11_rreg32(struct amdgpu_device *adev, + u32 reg, u32 acc_flags, u32 hwip, + u32 xcc_id) +{ + if (!dm_test_gfx11_reg_ctx) + return 0; + + dm_test_gfx11_reg_ctx->called = true; + dm_test_gfx11_reg_ctx->captured_reg = reg; + dm_test_gfx11_reg_ctx->captured_acc_flags = acc_flags; + dm_test_gfx11_reg_ctx->captured_hwip = hwip; + dm_test_gfx11_reg_ctx->captured_xcc_id = xcc_id; + + return dm_test_gfx11_reg_ctx->gb_addr_config; +} + +static const struct amdgpu_rlc_reg_funcs dm_test_gfx11_reg_funcs = { + .rreg32 = dm_test_gfx11_rreg32, +}; + static bool dm_test_get_dcc_compression_cap(const struct dc *dc, const struct dc_dcc_surface_param *input, struct dc_surface_dcc_cap *output) @@ -1268,6 +1301,48 @@ static u64 *dm_test_get_primary_mods(struct kunit *test, struct amdgpu_device *a return mods; } +static void dm_test_setup_gfx11_device(struct amdgpu_device *adev, + struct dm_test_gfx11_reg_ctx *ctx, + u32 num_pkrs_log2, u32 num_pipes_log2) +{ + ctx->gb_addr_config = + REG_SET_FIELD(0, GB_ADDR_CONFIG, NUM_PKRS, num_pkrs_log2) | + REG_SET_FIELD(0, GB_ADDR_CONFIG, NUM_PIPES, num_pipes_log2); + ctx->gc_reg_offsets[regGB_ADDR_CONFIG_BASE_IDX] = 0; + ctx->expected_reg = ctx->gc_reg_offsets[regGB_ADDR_CONFIG_BASE_IDX] + + regGB_ADDR_CONFIG; + dm_test_gfx11_reg_ctx = ctx; + + adev->family = AMDGPU_FAMILY_GC_11_0_0; + adev->reg_offset[GC_HWIP][0] = ctx->gc_reg_offsets; + adev->gfx.rlc.reg_funcs = &dm_test_gfx11_reg_funcs; +} + +static u64 dm_test_gfx11_dcc_best_modifier(u32 pipe_xor_bits, u32 pkrs, u32 tile) +{ + return AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(TILE, tile) | + AMD_FMT_MOD_SET(PACKERS, pkrs) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_128B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_128B); +} + +static u64 dm_test_gfx11_dcc_4k_modifier(u32 pipe_xor_bits, u32 pkrs, u32 tile) +{ + return AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(TILE, tile) | + AMD_FMT_MOD_SET(PACKERS, pkrs) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_128B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_64B); +} + /** * dm_test_get_plane_formats_overlay_universal_cap() - Verify universal overlay. * @test: KUnit test context. @@ -1466,6 +1541,107 @@ static void dm_test_get_plane_modifiers_gfx10_3(struct kunit *test) dm_test_expect_mods_terminated(test, adev); } +/** + * dm_test_get_plane_modifiers_gfx11_64k_first() - Verify GFX11 small-pipe order. + * @test: KUnit test context. + * + * Verify if GFX11 modifier generation reads GB_ADDR_CONFIG through the RLC + * register callback and prefers 64K_R_X when the pipe count is 16 or lower. + */ +static void dm_test_get_plane_modifiers_gfx11_64k_first(struct kunit *test) +{ + struct dm_test_gfx11_reg_ctx ctx = {0}; + struct amdgpu_device *adev; + u64 *mods; + u32 pipe_xor_bits = 4; + u32 pkrs = 1; + u32 tile = AMD_FMT_MOD_TILE_GFX9_64K_R_X; + u64 dcc_best; + u64 dcc_4k; + u64 d_mod; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + dm_test_setup_gfx11_device(adev, &ctx, pkrs, pipe_xor_bits); + + mods = dm_test_get_primary_mods(test, adev); + dcc_best = dm_test_gfx11_dcc_best_modifier(pipe_xor_bits, pkrs, tile); + dcc_4k = dm_test_gfx11_dcc_4k_modifier(pipe_xor_bits, pkrs, tile); + d_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11) | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_D); + + KUNIT_EXPECT_TRUE(test, ctx.called); + KUNIT_EXPECT_EQ(test, ctx.captured_reg, ctx.expected_reg); + KUNIT_EXPECT_EQ(test, ctx.captured_acc_flags, 0U); + KUNIT_EXPECT_EQ(test, ctx.captured_hwip, (u32)GC_HWIP); + KUNIT_EXPECT_EQ(test, ctx.captured_xcc_id, 0U); + KUNIT_EXPECT_EQ(test, mods[0], dcc_best); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_4k)); + KUNIT_EXPECT_TRUE(test, + dm_test_mods_contain(mods, + dcc_best | AMD_FMT_MOD_SET(DCC_RETILE, 1))); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, d_mod)); + + dm_test_gfx11_reg_ctx = NULL; + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_gfx11_256k_first() - Verify GFX11 large-pipe order. + * @test: KUnit test context. + * + * Verify if GFX11 modifier generation prefers 256K_R_X when more than 16 pipes + * are reported by GB_ADDR_CONFIG. + */ +static void dm_test_get_plane_modifiers_gfx11_256k_first(struct kunit *test) +{ + struct dm_test_gfx11_reg_ctx ctx = {0}; + struct amdgpu_device *adev; + u64 *mods; + u32 pipe_xor_bits = 5; + u32 pkrs = 2; + u32 tile = AMD_FMT_MOD_TILE_GFX11_256K_R_X; + u32 fallback_tile = AMD_FMT_MOD_TILE_GFX9_64K_R_X; + u64 dcc_best; + u64 fallback_dcc_best = dm_test_gfx11_dcc_best_modifier(pipe_xor_bits, pkrs, + fallback_tile); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + dm_test_setup_gfx11_device(adev, &ctx, pkrs, pipe_xor_bits); + + mods = dm_test_get_primary_mods(test, adev); + dcc_best = dm_test_gfx11_dcc_best_modifier(pipe_xor_bits, pkrs, tile); + + KUNIT_EXPECT_TRUE(test, ctx.called); + KUNIT_EXPECT_EQ(test, ctx.captured_reg, ctx.expected_reg); + KUNIT_EXPECT_EQ(test, mods[0], dcc_best); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, fallback_dcc_best)); + + dm_test_gfx11_reg_ctx = NULL; + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_gfx12() - Verify GFX12 modifier list generation. + * @test: KUnit test context. + * + * Verify if the GFX12 family dispatches to the GFX12 modifier builder and + * produces a terminated list. + */ +static void dm_test_get_plane_modifiers_gfx12(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + + dm_test_expect_mods_terminated(test, adev); +} + /** * dm_test_format_mod_supported_d_swizzle_reject() - Verify D swizzle rejection. * @test: KUnit test context. @@ -1522,6 +1698,9 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_get_plane_modifiers_rv_constant_encode), KUNIT_CASE(dm_test_get_plane_modifiers_gfx10_1), KUNIT_CASE(dm_test_get_plane_modifiers_gfx10_3), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx11_64k_first), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx11_256k_first), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx12), /* amdgpu_dm_plane_fill_dc_scaling_info() */ KUNIT_CASE(dm_test_fill_dc_scaling_info), /* amdgpu_dm_plane_get_min_max_dc_plane_scaling() */ From bfbaf20d046be3e9a6c3d3ba4b45b1a15e3d23ab Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 066/155] drm/amd/display: Test plane scaling info paths [WHAT] Add KUnit tests covering the NV12 DCN1.x and plane-caps paths of amdgpu_dm_plane_fill_dc_scaling_info() and the FP16 path of amdgpu_dm_plane_get_min_max_dc_plane_scaling(). Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index e1171c6dafe3..5d0691473a56 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -1642,6 +1642,128 @@ static void dm_test_get_plane_modifiers_gfx12(struct kunit *test) dm_test_expect_mods_terminated(test, adev); } +/** + * dm_test_get_min_max_dc_plane_scaling_fp16() - Verify fp16 cap selection. + * @test: KUnit test context. + * + * Verify if 64bpp fp16 formats use the fp16 scaling caps. + */ +static void dm_test_get_min_max_dc_plane_scaling_fp16(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_framebuffer *fb; + int min_downscale = 0; + int max_upscale = 0; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.fp16 = 2000; + dc->caps.planes[0].max_downscale_factor.fp16 = 500; + + fb->format = drm_format_info(DRM_FORMAT_ARGB16161616F); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + amdgpu_dm_plane_get_min_max_dc_plane_scaling(&adev->ddev, fb, + &min_downscale, &max_upscale); + KUNIT_EXPECT_EQ(test, min_downscale, 500); + KUNIT_EXPECT_EQ(test, max_upscale, 2000); +} + +/** + * dm_test_fill_dc_scaling_info_nv12_dcn1x() - Verify NV12 DCN1x rejection. + * @test: KUnit test context. + * + * Verify if a non-zero NV12 source origin is rejected on DCN 1.0 to avoid the + * known DCN1x hang. + */ +static void dm_test_fill_dc_scaling_info_nv12_dcn1x(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane_state state = {0}; + struct drm_framebuffer fb = {0}; + struct dc_scaling_info info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0); + fb.format = drm_format_info(DRM_FORMAT_NV12); + KUNIT_ASSERT_NOT_NULL(test, fb.format); + + state.fb = &fb; + state.src_x = 10 << 16; + state.src_y = 0; + state.src_w = 100 << 16; + state.src_h = 100 << 16; + state.crtc_w = 100; + state.crtc_h = 100; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), + -EINVAL); + + state.src_x = 0; + state.src_y = 10 << 16; + memset(&info, 0, sizeof(info)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), + -EINVAL); +} + +/** + * dm_test_fill_dc_scaling_info_plane_caps() - Verify scaling caps path. + * @test: KUnit test context. + * + * Verify if scaling info uses plane caps when the state references a plane, + * device, and framebuffer. + */ +static void dm_test_fill_dc_scaling_info_plane_caps(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_framebuffer *fb; + struct dc_scaling_info info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.argb8888 = 16000; + dc->caps.planes[0].max_downscale_factor.argb8888 = 250; + + plane->dev = &adev->ddev; + fb->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + + state->plane = plane; + state->fb = fb; + state->src_w = 100 << 16; + state->src_h = 100 << 16; + state->crtc_w = 100; + state->crtc_h = 100; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_dc_scaling_info(adev, state, &info), + 0); +} + /** * dm_test_format_mod_supported_d_swizzle_reject() - Verify D swizzle rejection. * @test: KUnit test context. @@ -1703,8 +1825,11 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_get_plane_modifiers_gfx12), /* amdgpu_dm_plane_fill_dc_scaling_info() */ KUNIT_CASE(dm_test_fill_dc_scaling_info), + KUNIT_CASE(dm_test_fill_dc_scaling_info_nv12_dcn1x), + KUNIT_CASE(dm_test_fill_dc_scaling_info_plane_caps), /* amdgpu_dm_plane_get_min_max_dc_plane_scaling() */ KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling), + KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling_fp16), /* amdgpu_dm_plane_fill_plane_buffer_attributes() */ KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx8), /* amdgpu_dm_plane_get_cursor_position() */ From bc35db67a6b4a27c5781b8442d7b48d742632793 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 067/155] drm/amd/display: Test plane buffer attributes [WHAT] Add KUnit tests for the video and GFX12 paths of amdgpu_dm_plane_fill_plane_buffer_attributes(). Introduce the dm_test_plane_attrs(), dm_test_video_attrs() and dm_test_graphics_attrs() helpers that drive the buffer-attribute fill with representative plane states. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 5d0691473a56..e05c9f71d451 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -1301,6 +1301,41 @@ static u64 *dm_test_get_primary_mods(struct kunit *test, struct amdgpu_device *a return mods; } +static int dm_test_plane_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + enum surface_pixel_format format, + struct dc_tiling_info *tiling_info, + struct plane_size *plane_size, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, format, + ROTATION_ANGLE_0, 0, tiling_info, plane_size, dcc, address, + false); +} + +static int dm_test_video_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + struct dc_tiling_info *tiling_info, + struct plane_size *plane_size, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return dm_test_plane_attrs(adev, afb, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + tiling_info, plane_size, dcc, address); +} + +static int dm_test_graphics_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + struct dc_tiling_info *tiling_info, + struct plane_size *plane_size, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return dm_test_plane_attrs(adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + tiling_info, plane_size, dcc, address); +} + static void dm_test_setup_gfx11_device(struct amdgpu_device *adev, struct dm_test_gfx11_reg_ctx *ctx, u32 num_pkrs_log2, u32 num_pipes_log2) @@ -1642,6 +1677,104 @@ static void dm_test_get_plane_modifiers_gfx12(struct kunit *test) dm_test_expect_mods_terminated(test, adev); } +/** + * dm_test_fill_plane_buffer_attributes_video() - Verify NV12 attributes. + * @test: KUnit test context. + * + * Verify if a video pixel format fills chroma plane size and the progressive + * video address type on a GFX9 family device. + */ +static void dm_test_fill_plane_buffer_attributes_video(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_framebuffer *afb; + struct dc_tiling_info tiling_info; + struct plane_size plane_size; + struct dc_plane_dcc_param dcc; + struct dc_plane_address address; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + tiling_info = (struct dc_tiling_info){0}; + plane_size = (struct plane_size){0}; + dcc = (struct dc_plane_dcc_param){0}; + address = (struct dc_plane_address){0}; + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + afb->address = 0x80000000ULL; + afb->base.width = 1920; + afb->base.height = 1080; + afb->base.offsets[0] = 0; + afb->base.offsets[1] = 0x200000; + afb->base.pitches[0] = 1920; + afb->base.pitches[1] = 1920; + afb->base.format = drm_format_info(DRM_FORMAT_NV12); + afb->base.modifier = DRM_FORMAT_MOD_LINEAR; + KUNIT_ASSERT_NOT_NULL(test, afb->base.format); + + ret = dm_test_video_attrs(adev, afb, &tiling_info, &plane_size, &dcc, + &address); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, plane_size.surface_size.width, 1920); + KUNIT_EXPECT_EQ(test, plane_size.chroma_size.width, 960U); + KUNIT_EXPECT_EQ(test, plane_size.chroma_size.height, 540U); + KUNIT_EXPECT_EQ(test, address.type, + (int)PLN_ADDR_TYPE_VIDEO_PROGRESSIVE); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion9); +} + +/** + * dm_test_fill_plane_buffer_attributes_gfx12() - Verify GFX12 dispatch path. + * @test: KUnit test context. + * + * Verify if a GFX12 family device fills graphics attributes via the GFX12 + * modifier path and reports the GFX addr3 version. + */ +static void dm_test_fill_plane_buffer_attributes_gfx12(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct dc_tiling_info tiling_info; + struct plane_size plane_size; + struct dc_plane_dcc_param dcc; + struct dc_plane_address address; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + tiling_info = (struct dc_tiling_info){0}; + plane_size = (struct plane_size){0}; + dcc = (struct dc_plane_dcc_param){0}; + address = (struct dc_plane_address){0}; + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + afb->address = 0x80000000ULL; + afb->base.width = 1920; + afb->base.height = 1080; + afb->base.pitches[0] = 7680; + afb->base.format = drm_format_info(DRM_FORMAT_XRGB8888); + afb->base.modifier = DRM_FORMAT_MOD_LINEAR; + KUNIT_ASSERT_NOT_NULL(test, afb->base.format); + + ret = dm_test_graphics_attrs(adev, afb, &tiling_info, &plane_size, &dcc, + &address); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, address.type, (int)PLN_ADDR_TYPE_GRAPHICS); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxAddr3); +} + /** * dm_test_get_min_max_dc_plane_scaling_fp16() - Verify fp16 cap selection. * @test: KUnit test context. @@ -1832,6 +1965,8 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling_fp16), /* amdgpu_dm_plane_fill_plane_buffer_attributes() */ KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx8), + KUNIT_CASE(dm_test_fill_plane_buffer_attributes_video), + KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx12), /* amdgpu_dm_plane_get_cursor_position() */ KUNIT_CASE(dm_test_get_cursor_position), /* amdgpu_dm_plane_format_mod_supported() */ From fd1522c72df03ed7eb43dbcd1f85d299a9aac7be Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 068/155] drm/amd/display: Test GFX9 DCC plane attributes [WHAT] Add KUnit tests for amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() covering DCC enablement, validation failure, and the RB+ 64B/128B and GFX9 64B/unconstrained block modes. Add the dm_test_gfx9_attrs(), dm_test_setup_gfx9_dcc_device() and dm_test_gfx9_dcc_modifier() helpers used to build the GFX9 DCC inputs. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index e05c9f71d451..4b7d9a2f798d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -1301,6 +1301,18 @@ static u64 *dm_test_get_primary_mods(struct kunit *test, struct amdgpu_device *a return mods; } +static int dm_test_gfx9_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(adev, + afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, + plane_size, tiling_info, dcc, address); +} + static int dm_test_plane_attrs(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, enum surface_pixel_format format, @@ -1336,6 +1348,34 @@ static int dm_test_graphics_attrs(struct amdgpu_device *adev, tiling_info, plane_size, dcc, address); } +static void dm_test_setup_gfx9_dcc_device(struct amdgpu_device *adev, + struct dc *dc, + struct dm_test_dcc_cap_ctx *ctx, + bool output_independent_64b_blks) +{ + adev->family = AMDGPU_FAMILY_NV; + adev->dm.dc = dc; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + ctx->callback_ret = true; + ctx->capable = true; + ctx->output_independent_64b_blks = output_independent_64b_blks; + dm_test_dcc_ctx = ctx; +} + +static u64 dm_test_gfx9_dcc_modifier(u64 tile_version, bool independent_64b_blks, + bool independent_128b_blks) +{ + return AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, tile_version) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, independent_64b_blks) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_128B, independent_128b_blks); +} + static void dm_test_setup_gfx11_device(struct amdgpu_device *adev, struct dm_test_gfx11_reg_ctx *ctx, u32 num_pkrs_log2, u32 num_pipes_log2) @@ -1677,6 +1717,310 @@ static void dm_test_get_plane_modifiers_gfx12(struct kunit *test) dm_test_expect_mods_terminated(test, adev); } +/** + * dm_test_fill_gfx9_plane_attributes_dcc() - Verify GFX9 DCC modifier path. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS DCC modifier enables DCC and selects the 64B + * independent block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = true, + }; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->dm.dc = dc; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.pitches[1] = 256; + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion9); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_64b); + KUNIT_EXPECT_EQ(test, dcc.meta_pitch, 256U); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_validate_fails() - Verify GFX9 error path. + * @test: KUnit test context. + * + * Verify if GFX9 modifier parsing returns validation errors from the shared DCC + * validation helper. + */ +static void dm_test_fill_gfx9_plane_attributes_validate_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->dm.dc = dc; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, true, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_rbplus_64b_no_128bcl() - Verify block mode. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS modifier with both 64B and 128B independent block + * bits selects the 64B-no-128BCL block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_rbplus_64b_no_128bcl(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, true); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, true, true); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_64b_no_128bcl); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_rbplus_128b() - Verify 128B block mode. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS modifier with only the 128B independent block bit + * selects the 128B block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_rbplus_128b(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, false); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, false, true); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_128b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_rbplus_unconstrained() - Verify block mode. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS modifier without independent block bits selects the + * unconstrained block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_rbplus_unconstrained(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, false); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, false, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_unconstrained); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_gfx9_64b() - Verify legacy 64B mode. + * @test: KUnit test context. + * + * Verify if a pre-RBPLUS GFX9 modifier with the 64B independent block bit + * selects the 64B block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_gfx9_64b(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, true); + afb->base.modifier = dm_test_gfx9_dcc_modifier(AMD_FMT_MOD_TILE_VER_GFX9, + true, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_64b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained() - Verify legacy mode. + * @test: KUnit test context. + * + * Verify if a pre-RBPLUS GFX9 modifier without the 64B independent block bit + * selects the unconstrained block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, false); + afb->base.modifier = dm_test_gfx9_dcc_modifier(AMD_FMT_MOD_TILE_VER_GFX9, + false, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_unconstrained); + + dm_test_dcc_ctx = NULL; +} + /** * dm_test_fill_plane_buffer_attributes_video() - Verify NV12 attributes. * @test: KUnit test context. @@ -1976,6 +2320,13 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_from_modifiers), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_validate_fails), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_rbplus_64b_no_128bcl), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_rbplus_128b), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_rbplus_unconstrained), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_gfx9_64b), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained), /* amdgpu_dm_plane_helper_check_state() */ KUNIT_CASE(dm_test_helper_check_state_viewport_reject), /* amdgpu_dm_plane_add_modifier() */ From 551d117ff4e1badbddaae2639fb9e62146327972 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 069/155] drm/amd/display: Test GFX12 DCC plane attributes [WHAT] Add KUnit tests for amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() covering the block0, unconstrained-block and validation-failure cases. Add the dm_test_gfx12_attrs() helper used to build the GFX12 inputs. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 4b7d9a2f798d..47da180cf3a0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -1313,6 +1313,18 @@ static int dm_test_gfx9_attrs(struct amdgpu_device *adev, plane_size, tiling_info, dcc, address); } +static int dm_test_gfx12_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(adev, + afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, + plane_size, tiling_info, dcc, address); +} + static int dm_test_plane_attrs(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, enum surface_pixel_format format, @@ -2021,6 +2033,153 @@ static void dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained(struct kun dm_test_dcc_ctx = NULL; } +/** + * dm_test_fill_gfx12_plane_attributes_block0() - Verify GFX12 64B max-compressed-block path. + * @test: KUnit test context. + * + * Verify if a zero max-compressed-block modifier selects the 64B independent + * block mode on GFX12. + */ +static void dm_test_fill_gfx12_plane_attributes_block0(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = false, + }; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 0); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx12_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_TRUE(test, dcc.independent_64b_blks); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_64b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx12_plane_attributes_block_unconstrained() - Verify block path. + * @test: KUnit test context. + * + * Verify if a max-compressed-block value above one selects the unconstrained + * independent block mode on GFX12. + */ +static void dm_test_fill_gfx12_plane_attributes_block_unconstrained(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = false, + }; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 2); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx12_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_FALSE(test, dcc.independent_64b_blks); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_unconstrained); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx12_plane_attributes_validate_fails() - Verify GFX12 error path. + * @test: KUnit test context. + * + * Verify if GFX12 modifier parsing returns validation errors from the shared + * DCC validation helper. + */ +static void dm_test_fill_gfx12_plane_attributes_validate_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 1); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx12_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + /** * dm_test_fill_plane_buffer_attributes_video() - Verify NV12 attributes. * @test: KUnit test context. @@ -2318,6 +2477,9 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_format_mod_supported_d_swizzle_reject), /* amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block0), + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block_unconstrained), + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_validate_fails), /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_from_modifiers), KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc), From b3a436a6d76aac8ece0d7cc0f2b49c8eaad08c98 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 070/155] drm/amd/display: Test DCC and cursor edge cases [WHAT] Add KUnit tests for the capability-callback-failure and not-capable paths of amdgpu_dm_plane_validate_dcc(), and for the bad-size path of amdgpu_dm_plane_get_cursor_position(). Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 47da180cf3a0..e1841a30fc32 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -785,6 +785,77 @@ static void dm_test_validate_dcc_missing_cap_func_fails(struct kunit *test) -EINVAL); } +/** + * dm_test_validate_dcc_cap_callback_fails() - Verify callback failure path. + * @test: KUnit test context. + * + * Verify if validation fails when the DCC capability callback returns false. + */ +static void dm_test_validate_dcc_cap_callback_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + enum surface_pixel_format format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + enum dc_rotation_angle rotation = ROTATION_ANGLE_0; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = false, + .capable = true, + }; + int ret; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, &tiling_info, + &dcc, &address, &plane_size); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_TRUE(test, ctx.called); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_validate_dcc_not_capable_fails() - Verify not-capable callback output. + * @test: KUnit test context. + * + * Verify if validation fails when the DCC capability callback reports that the + * surface is not DCC capable. + */ +static void dm_test_validate_dcc_not_capable_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + enum surface_pixel_format format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + enum dc_rotation_angle rotation = ROTATION_ANGLE_0; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = false, + }; + int ret; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, &tiling_info, + &dcc, &address, &plane_size); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_TRUE(test, ctx.called); + + dm_test_dcc_ctx = NULL; +} + /** * dm_test_validate_dcc_success_and_scan_mapping() - Verify success path and rotation-to-scan mapping. * @test: KUnit test context. @@ -2400,6 +2471,45 @@ static void dm_test_fill_dc_scaling_info_plane_caps(struct kunit *test) 0); } +/** + * dm_test_get_cursor_position_bad_size() - Verify oversized cursor rejection. + * @test: KUnit test context. + * + * Verify if a cursor larger than the CRTC maximum is rejected. + */ +static void dm_test_get_cursor_position_bad_size(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *amdgpu_crtc; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_framebuffer *fb; + struct dc_cursor_position position = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + amdgpu_crtc = kunit_kzalloc(test, sizeof(*amdgpu_crtc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, amdgpu_crtc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + amdgpu_crtc->max_cursor_width = 64; + amdgpu_crtc->max_cursor_height = 64; + plane->dev = &adev->ddev; + plane->state = state; + state->fb = fb; + state->crtc_w = 128; + state->crtc_h = 32; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_get_cursor_position(plane, &amdgpu_crtc->base, &position), + -EINVAL); +} + /** * dm_test_format_mod_supported_d_swizzle_reject() - Verify D swizzle rejection. * @test: KUnit test context. @@ -2472,6 +2582,7 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx12), /* amdgpu_dm_plane_get_cursor_position() */ KUNIT_CASE(dm_test_get_cursor_position), + KUNIT_CASE(dm_test_get_cursor_position_bad_size), /* amdgpu_dm_plane_format_mod_supported() */ KUNIT_CASE(dm_test_format_mod_supported), KUNIT_CASE(dm_test_format_mod_supported_d_swizzle_reject), @@ -2510,6 +2621,8 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_validate_dcc_disabled_returns_success), KUNIT_CASE(dm_test_validate_dcc_video_non_gfx12_fails), KUNIT_CASE(dm_test_validate_dcc_missing_cap_func_fails), + KUNIT_CASE(dm_test_validate_dcc_cap_callback_fails), + KUNIT_CASE(dm_test_validate_dcc_not_capable_fails), KUNIT_CASE(dm_test_validate_dcc_success_and_scan_mapping), KUNIT_CASE(dm_test_validate_dcc_independent_64b_mismatch_fails), {} From e0e426f10ad653c5e9dd33fb4a76d55f06101992 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 071/155] drm/amd/display: Test helper_check_state rejects [WHAT] Add KUnit tests for amdgpu_dm_plane_helper_check_state() covering the small viewport width, small viewport height, bottom-edge clipping and scaling-caps rejection paths. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index e1841a30fc32..f36c5c139554 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -2382,6 +2382,190 @@ static void dm_test_get_min_max_dc_plane_scaling_fp16(struct kunit *test) KUNIT_EXPECT_EQ(test, max_upscale, 2000); } +/** + * dm_test_helper_check_state_small_viewport_width() - Verify width rejection. + * @test: KUnit test context. + * + * Verify if a viewport width below the minimum pipe-split width is rejected. + */ +static void dm_test_helper_check_state_small_viewport_width(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = 0; + state->crtc_y = 0; + state->crtc_w = 10; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -EINVAL); +} + +/** + * dm_test_helper_check_state_small_viewport_height() - Verify height rejection. + * @test: KUnit test context. + * + * Verify if a negative-offset viewport with a too-small height is rejected. + */ +static void dm_test_helper_check_state_small_viewport_height(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = -2; + state->crtc_y = -95; + state->crtc_w = 100; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -EINVAL); +} + +/** + * dm_test_helper_check_state_bottom_clipped_height() - Verify bottom clipping. + * @test: KUnit test context. + * + * Verify if a viewport clipped by the bottom edge to below the minimum height + * is rejected. + */ +static void dm_test_helper_check_state_bottom_clipped_height(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = 0; + state->crtc_y = 95; + state->crtc_w = 100; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 100; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -EINVAL); +} + +/** + * dm_test_helper_check_state_scaling_caps() - Verify DC scaling caps are applied. + * @test: KUnit test context. + * + * Verify if helper_check_state converts DC plane scaling caps to DRM scale + * limits and rejects scaling outside those limits. + */ +static void dm_test_helper_check_state_scaling_caps(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.argb8888 = 1; + dc->caps.planes[0].max_downscale_factor.argb8888 = 1; + + plane->type = DRM_PLANE_TYPE_OVERLAY; + plane->dev = &adev->ddev; + crtc->dev = &adev->ddev; + fb->width = 100; + fb->height = 100; + fb->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->src_w = 100 << 16; + state->src_h = 100 << 16; + state->crtc_w = 200; + state->crtc_h = 200; + new_crtc_state->crtc = crtc; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -ERANGE); +} + /** * dm_test_fill_dc_scaling_info_nv12_dcn1x() - Verify NV12 DCN1x rejection. * @test: KUnit test context. @@ -2602,6 +2786,10 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained), /* amdgpu_dm_plane_helper_check_state() */ KUNIT_CASE(dm_test_helper_check_state_viewport_reject), + KUNIT_CASE(dm_test_helper_check_state_small_viewport_width), + KUNIT_CASE(dm_test_helper_check_state_small_viewport_height), + KUNIT_CASE(dm_test_helper_check_state_bottom_clipped_height), + KUNIT_CASE(dm_test_helper_check_state_scaling_caps), /* amdgpu_dm_plane_add_modifier() */ KUNIT_CASE(dm_test_add_modifier_appends_value), KUNIT_CASE(dm_test_add_modifier_grows_capacity), From 4133f1caf3c8d07d4a6b9814eeea5eeef475300e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 17:35:52 -0600 Subject: [PATCH 072/155] drm/amd/display: Test plane atomic check and hooks [WHAT] Add KUnit tests for amdgpu_dm_plane_atomic_async_check(), amdgpu_dm_plane_atomic_check(), amdgpu_dm_plane_panic_flush() and the DRM plane reset, duplicate-state and destroy-state hooks. Add the dm_test_init_atomic_check_state() helper and plane reset vtable used by the new tests. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 25 +- .../amd/display/amdgpu_dm/amdgpu_dm_plane.h | 9 + .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 457 ++++++++++++++++++ 3 files changed, 482 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 1b564cfe2120..fa05734047e0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -1254,8 +1254,8 @@ int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_dc_scaling_info); -static int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, - struct drm_atomic_commit *state) +STATIC_IFN_KUNIT int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) { struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); @@ -1300,9 +1300,10 @@ static int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_atomic_check); -static int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, - struct drm_atomic_commit *state, bool flip) +STATIC_IFN_KUNIT int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, + struct drm_atomic_commit *state, bool flip) { struct drm_crtc_state *new_crtc_state; struct drm_plane_state *new_plane_state; @@ -1324,6 +1325,7 @@ static int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_atomic_async_check); int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc *crtc, struct dc_cursor_position *position) @@ -1483,7 +1485,7 @@ static void amdgpu_dm_plane_atomic_async_update(struct drm_plane *plane, amdgpu_dm_plane_handle_cursor_update(plane, old_state); } -static void amdgpu_dm_plane_panic_flush(struct drm_plane *plane) +STATIC_IFN_KUNIT void amdgpu_dm_plane_panic_flush(struct drm_plane *plane) { struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane->state); struct drm_framebuffer *fb = plane->state->fb; @@ -1496,6 +1498,7 @@ static void amdgpu_dm_plane_panic_flush(struct drm_plane *plane) dc_plane_force_dcc_and_tiling_disable(dc_plane_state, fb->modifier ? true : false); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_panic_flush); static const struct drm_plane_helper_funcs dm_plane_helper_funcs = { .prepare_fb = amdgpu_dm_plane_helper_prepare_fb, @@ -1515,7 +1518,7 @@ static const struct drm_plane_helper_funcs dm_primary_plane_helper_funcs = { .panic_flush = amdgpu_dm_plane_panic_flush, }; -static void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) +STATIC_IFN_KUNIT void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) { struct dm_plane_state *amdgpu_state; @@ -1532,8 +1535,10 @@ static void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) amdgpu_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; amdgpu_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_drm_plane_reset); -static struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane) +STATIC_IFN_KUNIT struct drm_plane_state * +amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane) { struct dm_plane_state *dm_plane_state, *old_dm_plane_state; @@ -1572,6 +1577,7 @@ static struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct return &dm_plane_state->base; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_drm_plane_duplicate_state); STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, uint32_t format, @@ -1636,8 +1642,8 @@ STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *pla } EXPORT_IF_KUNIT(amdgpu_dm_plane_format_mod_supported); -static void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, - struct drm_plane_state *state) +STATIC_IFN_KUNIT void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) { struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); @@ -1657,6 +1663,7 @@ static void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, drm_atomic_helper_plane_destroy_state(plane, state); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_drm_plane_destroy_state); #ifdef AMD_PRIVATE_COLOR static void diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h index 911fb2d73e22..ad22324a5d2e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h @@ -115,5 +115,14 @@ void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, struct drm_framebuffer *fb, int *min_downscale, int *max_upscale); +int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, + struct drm_atomic_commit *state, bool flip); +int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state); +void amdgpu_dm_plane_panic_flush(struct drm_plane *plane); +void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane); +struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane); +void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index f36c5c139554..df69e2235e24 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -2726,6 +2726,446 @@ static void dm_test_format_mod_supported_d_swizzle_reject(struct kunit *test) listed_mod)); } +/** + * dm_test_atomic_async_check_rejects() - Verify async check rejections. + * @test: KUnit test context. + * + * Verify if async flip on non-overlay planes and async cursor update on + * non-cursor planes are rejected. + */ +static void dm_test_atomic_async_check_rejects(struct kunit *test) +{ + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + + plane->type = DRM_PLANE_TYPE_PRIMARY; + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, NULL, true), + -EINVAL); + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, NULL, false), + -EINVAL); +} + +/** + * dm_test_atomic_async_check_overlay_cursor() - Verify overlay cursor rejection. + * @test: KUnit test context. + * + * Verify if async cursor updates are rejected while the CRTC is using an + * overlay cursor mode. + */ +static void dm_test_atomic_async_check_overlay_cursor(struct kunit *test) +{ + struct drm_atomic_commit *state; + struct __drm_planes_state *planes; + struct __drm_crtcs_state *crtcs; + struct drm_plane *plane; + struct drm_plane_state *plane_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + crtcs = kunit_kzalloc(test, sizeof(*crtcs), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + plane_state = kunit_kzalloc(test, sizeof(*plane_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, crtcs); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, plane_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + plane->type = DRM_PLANE_TYPE_CURSOR; + plane->index = 0; + crtc->index = 0; + plane_state->crtc = crtc; + dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE; + state->planes = planes; + state->crtcs = crtcs; + state->planes[0].new_state = plane_state; + state->crtcs[0].new_state = &dm_crtc_state->base; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, state, false), + -EINVAL); + + dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE; + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, state, false), + 0); +} + +static struct amdgpu_device *dm_test_init_atomic_check_state(struct kunit *test, + struct drm_atomic_commit **state, + struct drm_plane **plane, + struct dm_plane_state **dm_plane_state, + struct drm_crtc_state **new_crtc_state, + struct drm_framebuffer **fb) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct __drm_planes_state *planes; + struct __drm_crtcs_state *crtcs; + struct dc_plane_state *dc_plane_state; + struct drm_crtc *crtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + *state = kunit_kzalloc(test, sizeof(**state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + crtcs = kunit_kzalloc(test, sizeof(*crtcs), GFP_KERNEL); + *plane = kunit_kzalloc(test, sizeof(**plane), GFP_KERNEL); + *dm_plane_state = kunit_kzalloc(test, sizeof(**dm_plane_state), GFP_KERNEL); + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + *new_crtc_state = kunit_kzalloc(test, sizeof(**new_crtc_state), GFP_KERNEL); + *fb = kunit_kzalloc(test, sizeof(**fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, *state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, crtcs); + KUNIT_ASSERT_NOT_NULL(test, *plane); + KUNIT_ASSERT_NOT_NULL(test, *dm_plane_state); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, *new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, *fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.argb8888 = 1000; + dc->caps.planes[0].max_downscale_factor.argb8888 = 1000; + dc->caps.planes[0].max_upscale_factor.nv12 = 1000; + dc->caps.planes[0].max_downscale_factor.nv12 = 1000; + + (*plane)->dev = &adev->ddev; + (*plane)->index = 0; + (*plane)->type = DRM_PLANE_TYPE_OVERLAY; + (*plane)->name = "kunit-plane"; + crtc->dev = &adev->ddev; + crtc->index = 0; + (*fb)->width = 100; + (*fb)->height = 100; + (*fb)->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, (*fb)->format); + + (*dm_plane_state)->base.plane = *plane; + (*dm_plane_state)->base.state = *state; + (*dm_plane_state)->base.crtc = crtc; + (*dm_plane_state)->base.fb = *fb; + (*dm_plane_state)->base.src_w = 100 << 16; + (*dm_plane_state)->base.src_h = 100 << 16; + (*dm_plane_state)->base.crtc_w = 100; + (*dm_plane_state)->base.crtc_h = 100; + (*dm_plane_state)->dc_state = dc_plane_state; + + (*new_crtc_state)->crtc = crtc; + (*new_crtc_state)->enable = true; + (*new_crtc_state)->mode.crtc_hdisplay = 1920; + (*new_crtc_state)->mode.crtc_vdisplay = 1080; + + (*state)->planes = planes; + (*state)->crtcs = crtcs; + (*state)->planes[0].new_state = &(*dm_plane_state)->base; + (*state)->crtcs[0].new_state = *new_crtc_state; + + return adev; +} + +/** + * dm_test_atomic_check_no_dc_state() - Verify missing DC plane state succeeds. + * @test: KUnit test context. + * + * Verify if atomic_check exits before deeper validation when the DM plane state + * has no DC plane state attached. + */ +static void dm_test_atomic_check_no_dc_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct __drm_planes_state *planes; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + plane->dev = &adev->ddev; + plane->index = 0; + dm_plane_state->base.plane = plane; + state->planes = planes; + state->planes[0].new_state = &dm_plane_state->base; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), 0); +} + +/** + * dm_test_atomic_check_missing_crtc_state() - Verify missing CRTC state fails. + * @test: KUnit test context. + * + * Verify if atomic_check rejects a plane with DC state when the atomic CRTC + * state is absent. + */ +static void dm_test_atomic_check_missing_crtc_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct __drm_planes_state *planes; + struct __drm_crtcs_state *crtcs; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct dc_plane_state *dc_plane_state; + struct drm_crtc *crtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + crtcs = kunit_kzalloc(test, sizeof(*crtcs), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, crtcs); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + plane->dev = &adev->ddev; + plane->index = 0; + crtc->index = 0; + dm_plane_state->base.plane = plane; + dm_plane_state->base.crtc = crtc; + dm_plane_state->dc_state = dc_plane_state; + state->planes = planes; + state->crtcs = crtcs; + state->planes[0].new_state = &dm_plane_state->base; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_atomic_check_helper_failure() - Verify helper-check failures return. + * @test: KUnit test context. + * + * Verify if atomic_check returns before DC validation when the DRM helper state + * validation rejects the plane. + */ +static void dm_test_atomic_check_helper_failure(struct kunit *test) +{ + struct drm_atomic_commit *state; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + dm_test_init_atomic_check_state(test, &state, &plane, &dm_plane_state, + &new_crtc_state, &fb); + dm_plane_state->base.crtc_w = 10; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_atomic_check_color_pipeline_conflict() - Verify color conflict rejection. + * @test: KUnit test context. + * + * Verify if atomic_check rejects use of both plane COLOR_PIPELINE and CRTC + * DEGAMMA_LUT before DC validation. + */ +static void dm_test_atomic_check_color_pipeline_conflict(struct kunit *test) +{ + struct drm_atomic_commit *state; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + void *color_pipeline; + void *degamma_lut; + + dm_test_init_atomic_check_state(test, &state, &plane, &dm_plane_state, + &new_crtc_state, &fb); + color_pipeline = kunit_kzalloc(test, 1, GFP_KERNEL); + degamma_lut = kunit_kzalloc(test, 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, color_pipeline); + KUNIT_ASSERT_NOT_NULL(test, degamma_lut); + + dm_plane_state->base.color_pipeline = color_pipeline; + new_crtc_state->degamma_lut = degamma_lut; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_atomic_check_scaling_failure() - Verify scaling-info failures return. + * @test: KUnit test context. + * + * Verify if atomic_check returns the scaling-info error before DC validation. + */ +static void dm_test_atomic_check_scaling_failure(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + adev = dm_test_init_atomic_check_state(test, &state, &plane, &dm_plane_state, + &new_crtc_state, &fb); + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0); + fb->width = 200; + fb->format = drm_format_info(DRM_FORMAT_NV12); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + dm_plane_state->base.src_x = 1 << 16; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_panic_flush_no_dc_state() - Verify panic flush exits without DC state. + * @test: KUnit test context. + * + * Verify if panic_flush returns without dereferencing DC state when the current + * plane state has no DC plane state attached. + */ +static void dm_test_panic_flush_no_dc_state(struct kunit *test) +{ + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + plane->state = &dm_plane_state->base; + + amdgpu_dm_plane_panic_flush(plane); +} + +static const struct drm_plane_funcs dm_test_plane_reset_funcs = { + .atomic_destroy_state = amdgpu_dm_plane_drm_plane_destroy_state, +}; + +/** + * dm_test_plane_reset_initializes_state() - Verify reset installs default state. + * @test: KUnit test context. + * + * Verify amdgpu_dm_plane_drm_plane_reset() destroys the existing plane state, + * allocates a fresh dm_plane_state, and initializes the AMD-specific transfer + * function and HDR multiplier defaults. + */ +static void dm_test_plane_reset_initializes_state(struct kunit *test) +{ + struct dm_plane_state *old_state; + struct dm_plane_state *new_state; + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + + /* + * Provide an existing state plus a funcs table so reset exercises the + * destroy-existing-state path. The destroy hook frees this state, so it + * must be a plain (non-KUnit-managed) allocation. + */ + old_state = kzalloc(sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_state); + plane->funcs = &dm_test_plane_reset_funcs; + plane->state = &old_state->base; + + amdgpu_dm_plane_drm_plane_reset(plane); + + KUNIT_ASSERT_NOT_NULL(test, plane->state); + new_state = to_dm_plane_state(plane->state); + KUNIT_EXPECT_EQ(test, new_state->degamma_tf, AMDGPU_TRANSFER_FUNCTION_DEFAULT); + KUNIT_EXPECT_EQ(test, new_state->hdr_mult, AMDGPU_HDR_MULT_DEFAULT); + KUNIT_EXPECT_EQ(test, new_state->shaper_tf, AMDGPU_TRANSFER_FUNCTION_DEFAULT); + KUNIT_EXPECT_EQ(test, new_state->blend_tf, AMDGPU_TRANSFER_FUNCTION_DEFAULT); + + kfree(new_state); +} + +/** + * dm_test_plane_duplicate_state_copies_fields() - Verify state duplication. + * @test: KUnit test context. + * + * Verify amdgpu_dm_plane_drm_plane_duplicate_state() allocates a new state and + * copies the transfer-function and HDR-multiplier fields from the current + * plane state when no DC state or color blob is attached. + */ +static void dm_test_plane_duplicate_state_copies_fields(struct kunit *test) +{ + struct dm_plane_state *old_state; + struct drm_plane_state *dup_base; + struct dm_plane_state *dup_state; + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, old_state); + + old_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; + old_state->hdr_mult = 0x123456789ULL; + old_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_IDENTITY; + old_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + plane->state = &old_state->base; + + dup_base = amdgpu_dm_plane_drm_plane_duplicate_state(plane); + KUNIT_ASSERT_NOT_NULL(test, dup_base); + + dup_state = to_dm_plane_state(dup_base); + KUNIT_EXPECT_EQ(test, dup_state->degamma_tf, AMDGPU_TRANSFER_FUNCTION_PQ_EOTF); + KUNIT_EXPECT_EQ(test, dup_state->hdr_mult, 0x123456789ULL); + KUNIT_EXPECT_EQ(test, dup_state->shaper_tf, AMDGPU_TRANSFER_FUNCTION_IDENTITY); + KUNIT_EXPECT_EQ(test, dup_state->blend_tf, AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF); + KUNIT_EXPECT_NULL(test, dup_state->dc_state); + + kfree(dup_state); +} + +/** + * dm_test_plane_destroy_state_minimal() - Verify destroy of a minimal state. + * @test: KUnit test context. + * + * Verify amdgpu_dm_plane_drm_plane_destroy_state() tears down a plane state + * that has no color blobs or DC plane state attached without dereferencing + * NULL resources. + */ +static void dm_test_plane_destroy_state_minimal(struct kunit *test) +{ + struct dm_plane_state *dm_plane_state; + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + + /* destroy_state frees the state itself, so use a plain allocation. */ + dm_plane_state = kzalloc(sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + amdgpu_dm_plane_drm_plane_destroy_state(plane, &dm_plane_state->base); +} + static struct kunit_case amdgpu_dm_plane_test_cases[] = { /* amdgpu_dm_plane_is_video_format() */ KUNIT_CASE(dm_test_plane_is_video_format_known_video), @@ -2790,6 +3230,23 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_helper_check_state_small_viewport_height), KUNIT_CASE(dm_test_helper_check_state_bottom_clipped_height), KUNIT_CASE(dm_test_helper_check_state_scaling_caps), + /* amdgpu_dm_plane_atomic_async_check() */ + KUNIT_CASE(dm_test_atomic_async_check_rejects), + KUNIT_CASE(dm_test_atomic_async_check_overlay_cursor), + /* amdgpu_dm_plane_atomic_check() */ + KUNIT_CASE(dm_test_atomic_check_no_dc_state), + KUNIT_CASE(dm_test_atomic_check_missing_crtc_state), + KUNIT_CASE(dm_test_atomic_check_helper_failure), + KUNIT_CASE(dm_test_atomic_check_color_pipeline_conflict), + KUNIT_CASE(dm_test_atomic_check_scaling_failure), + /* amdgpu_dm_plane_panic_flush() */ + KUNIT_CASE(dm_test_panic_flush_no_dc_state), + /* amdgpu_dm_plane_drm_plane_reset() */ + KUNIT_CASE(dm_test_plane_reset_initializes_state), + /* amdgpu_dm_plane_drm_plane_duplicate_state() */ + KUNIT_CASE(dm_test_plane_duplicate_state_copies_fields), + /* amdgpu_dm_plane_drm_plane_destroy_state() */ + KUNIT_CASE(dm_test_plane_destroy_state_minimal), /* amdgpu_dm_plane_add_modifier() */ KUNIT_CASE(dm_test_add_modifier_appends_value), KUNIT_CASE(dm_test_add_modifier_grows_capacity), From 1d8327353de6f1f34063840f362c208c84064f54 Mon Sep 17 00:00:00 2001 From: Iswara Nagulendran Date: Tue, 16 Jun 2026 15:31:03 -0400 Subject: [PATCH 073/155] drm/amd/display: Fix VABC Init and OLED Support [Why] TCONs supporting VESA AUX Backlight does not always support VESA Smooth Brightness. Driver software needs to implement smooth brightness by periodically updating the brightness by small steps. Driver implementation is completed and verified on LCD panels but solution is missing for OLED panels. [How] Updated driver to include smooth brightness for OLED panels using VESA Aux Backlight. Revert original workaround for VABC on oled issue, as it is no longer needed. Add VABC backlight Initialization sequence from driver side. Add fix to remove conflict between FW and driver backlight programming for future ASIC. Enable VABC for all future asic revisions. Reviewed-by: Anthony Koo Reviewed-by: Dillon Varone Signed-off-by: Iswara Nagulendran Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/hwss/dcn31/dcn31_hwseq.c | 41 ++++++++++++++++++- .../amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 2 + .../link/protocols/link_edp_panel_control.c | 13 +++--- .../drm/amd/display/modules/power/power_abm.c | 10 ++++- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index e72e10c276de..b6f3c0480ab6 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c @@ -719,6 +719,34 @@ static void dmub_abm_set_backlight(struct dc_context *dc, dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); } +static bool dmub_cacp_set_backlight(struct dc_context *dc, + struct set_backlight_level_params *backlight_level_params, + unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_backlight.header.type = DMUB_CMD__CACP; + cmd.cacp_set_backlight.header.sub_type = DMUB_CMD__CACP_SET_BACKLIGHT; + cmd.cacp_set_backlight.cacp_set_backlight_data.aux_inst = backlight_level_params->aux_inst; + cmd.cacp_set_backlight.cacp_set_backlight_data.frame_ramp = backlight_level_params->frame_ramp; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_user_level = + backlight_level_params->backlight_pwm_u16_16; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_control_type = + (enum dmub_backlight_control_type)backlight_level_params->control_type; + cmd.cacp_set_backlight.cacp_set_backlight_data.min_luminance = backlight_level_params->min_luminance; + cmd.cacp_set_backlight.cacp_set_backlight_data.max_luminance = backlight_level_params->max_luminance; + cmd.cacp_set_backlight.cacp_set_backlight_data.min_backlight_pwm = backlight_level_params->min_backlight_pwm; + cmd.cacp_set_backlight.cacp_set_backlight_data.max_backlight_pwm = backlight_level_params->max_backlight_pwm; + cmd.cacp_set_backlight.cacp_set_backlight_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_backlight.cacp_set_backlight_data.panel_mask = (0x01 << panel_inst); + cmd.cacp_set_backlight.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_backlight_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + bool dcn31_set_backlight_level(struct pipe_ctx *pipe_ctx, struct set_backlight_level_params *backlight_level_params) { @@ -746,8 +774,19 @@ bool dcn31_set_backlight_level(struct pipe_ctx *pipe_ctx, panel_cntl->inst, panel_cntl->pwrseq_inst); - if (backlight_level_params->control_type != BACKLIGHT_CONTROL_AMD_AUX) + if (link && link->panel_type == PANEL_TYPE_OLED) { + /* For OLED panel with AMD AUX, skip set backlight call */ + if (backlight_level_params->control_type == BACKLIGHT_CONTROL_VESA_AUX) + dmub_cacp_set_backlight(dc, backlight_level_params, panel_cntl->inst); + } else if (link && link->panel_type == PANEL_TYPE_MINILED) { + /* For MiniLED panel we need to check if CACP or ABM is being used */ + if (link->panel_config.cacp.cacp_supported) + dmub_cacp_set_backlight(dc, backlight_level_params, panel_cntl->inst); + else + dmub_abm_set_backlight(dc, backlight_level_params, panel_cntl->inst); + } else { dmub_abm_set_backlight(dc, backlight_level_params, panel_cntl->inst); + } return true; } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c index f415473517d4..cc8e96ffe7d1 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c @@ -281,6 +281,8 @@ void dcn42_init_hw(struct dc *dc) dc->caps.dmub_caps.psr = dc->ctx->dmub_srv->dmub->feature_caps.psr; dc->caps.dmub_caps.mclk_sw = dc->ctx->dmub_srv->dmub->feature_caps.fw_assisted_mclk_switch_ver > 0; dc->caps.dmub_caps.fams_ver = dc->ctx->dmub_srv->dmub->feature_caps.fw_assisted_mclk_switch_ver; + dc->caps.dmub_caps.aux_backlight_support = + dc->ctx->dmub_srv->dmub->feature_caps.abm_aux_backlight_support; /* sw and fw FAMS versions must match for support */ dc->debug.fams2_config.bits.enable &= diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c index baf57692bbb5..16951a9550f2 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c @@ -161,9 +161,7 @@ bool edp_set_backlight_level_nits(struct dc_link *link, if (link->is_dds && !link->dpcd_caps.panel_luminance_control) return true; - // use internal backlight control if dmub capabilities are not present - if (link->backlight_control_type == BACKLIGHT_CONTROL_VESA_AUX && - !link->dc->caps.dmub_caps.aux_backlight_support) { + if (link->backlight_control_type == BACKLIGHT_CONTROL_VESA_AUX) { uint8_t backlight_enable = 0; struct target_luminance_value *target_luminance = NULL; @@ -273,10 +271,11 @@ bool edp_backlight_enable_aux(struct dc_link *link, bool enable) if (link->is_dds) return true; - if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_ENABLE, - &backlight_enable, 1) != DC_OK) - return false; - + if (!link->dpcd_caps.panel_luminance_control) { + if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_ENABLE, + &backlight_enable, 1) != DC_OK) + return false; + } return true; } diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index 5b972f04bf7a..b26ceaba940d 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -716,8 +716,9 @@ void mod_power_update_backlight_on_mode_change( { struct set_backlight_level_params backlight_level_params = { 0 }; - if (link->dpcd_sink_ext_caps.bits.hdr_aux_backlight_control == 1 || - link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control == 1) + if ((link->dpcd_sink_ext_caps.bits.hdr_aux_backlight_control == 1 || + link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control == 1) && + link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX) dc_link_set_backlight_level_nits(link, core_power->bl_state[panel_inst].isHDR, core_power->bl_state[panel_inst].backlight_millinit, 0); @@ -746,6 +747,11 @@ static bool set_backlight_millinits_aux(struct core_power *core_power, link = dc_stream_get_link(stream); + // only use internal backlight control if dmub capabilities are not present + if (link->backlight_control_type == BACKLIGHT_CONTROL_VESA_AUX && + link->dc->caps.dmub_caps.aux_backlight_support) + return true; + return dc_link_set_backlight_level_nits(link, core_power->bl_state[inst].isHDR, backlight_millinits, transition_time_millisec); } From a637a140ab860310818a14fb4d37e39e51a06be4 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Thu, 18 Jun 2026 15:02:13 -0400 Subject: [PATCH 074/155] drm/amd/display: Allocate DCN32/1 dmub memory to GTT [Why] On DCN32/321 sometimes DMCUB takes a long time to flush. This adds a delay before the data is available on the driver side even after driver receives notification from DMCUB via inbox [How] Allocating DMUB memory to GTT gives much better latency. Limit this to DCN32/1 for now; it will be made general to all other dGPUs later. Reviewed-by: Aurabindo Pillai Reviewed-by: Sun peng (Leo) Li Signed-off-by: Fangzhi Zuo Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 00ea0dbd0f3f..c17488e57eae 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -506,6 +506,7 @@ int dm_dmub_sw_init(struct amdgpu_device *adev) DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_CURSOR_OFFLOAD */ }; int r; + int mem_domain = AMDGPU_GEM_DOMAIN_GTT; switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(2, 1, 0): @@ -639,13 +640,19 @@ int dm_dmub_sw_init(struct amdgpu_device *adev) return -EINVAL; } + /* Limit to allocate dmub to GTT on DCN32/1 + * TODO: Other asics with GDDR7 may have worse latency + */ + if (dmub_asic != DMUB_ASIC_DCN32 && + dmub_asic != DMUB_ASIC_DCN321) + mem_domain |= AMDGPU_GEM_DOMAIN_VRAM; + /* * Allocate a framebuffer based on the total size of all the regions. * TODO: Move this into GART. */ r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_VRAM | - AMDGPU_GEM_DOMAIN_GTT, + mem_domain, &adev->dm.dmub_bo, &adev->dm.dmub_bo_gpu_addr, &adev->dm.dmub_bo_cpu_addr); From c6a13d145cc811b19c8a0620f25868cf7d5700c0 Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Thu, 25 Jun 2026 13:56:52 -0400 Subject: [PATCH 075/155] drm/amd/display: remove unused dcn42 socbb values These values should be unset for dcn42 Reviewed-by: Charlene Liu Reviewed-by: Dillon Varone Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h index 040d89f6de35..51cae3efe310 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h @@ -205,7 +205,6 @@ static const struct dml2_soc_bb dml2_socbb_dcn42 = { .xtalclk_mhz = 24, .pcie_refclk_mhz = 100, .dchub_refclk_mhz = 50, - .mall_allocated_for_dcn_mbytes = 0, .max_outstanding_reqs = 256, .fabric_datapath_to_dcn_data_return_bytes = 32, .return_bus_width_bytes = 64, @@ -216,11 +215,6 @@ static const struct dml2_soc_bb dml2_socbb_dcn42 = { .phy_downspread_percent = 0.38, .dcn_downspread_percent = 0.38, .dispclk_dppclk_vco_speed_mhz = 3000, - .do_urgent_latency_adjustment = 0, - .mem_word_bytes = 32, - .num_dcc_mcaches = 8, - .mcache_size_bytes = 2048, - .mcache_line_size_bytes = 32, .max_fclk_for_uclk_dpm_khz = 2200 * 1000, }; From d5164580a99477dcfe15cea101b153b1e63f1535 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 24 Jun 2026 16:41:47 -0400 Subject: [PATCH 076/155] drm/amd/display: Skip Update HDCP Config In Transition State Transition state does not have a valid dm_stream_ctx that should skip configuring HDCP routine. The routine is valid to go through only when a valid stream is created. Reviewed-by: Aurabindo Pillai Signed-off-by: Fangzhi Zuo Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 1ffe82f0a9f1..5f43f6a74ffd 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 @@ -589,15 +589,27 @@ static bool enable_assr(void *handle, struct dc_link *link) static void update_config(void *handle, struct cp_psp_stream_config *config) { struct hdcp_workqueue *hdcp_work = handle; - struct amdgpu_dm_connector *aconnector = config->dm_stream_ctx; - int link_index = aconnector->dc_link->link_index; - unsigned int conn_index = aconnector->base.index; - struct mod_hdcp_display *display = &hdcp_work[link_index].display; - struct mod_hdcp_link *link = &hdcp_work[link_index].link; - struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index]; + struct amdgpu_dm_connector *aconnector; + const struct dc *dc; + int link_index; + unsigned int conn_index; + struct mod_hdcp_display *display; + struct mod_hdcp_link *link; + struct hdcp_workqueue *hdcp_w; struct dc_sink *sink = NULL; bool link_is_hdcp14 = false; - const struct dc *dc = aconnector->dc_link->dc; + + aconnector = config->dm_stream_ctx; + if (!aconnector || !aconnector->dc_link) + return; + + link_index = aconnector->dc_link->link_index; + display = &hdcp_work[link_index].display; + link = &hdcp_work[link_index].link; + hdcp_w = &hdcp_work[link_index]; + + conn_index = aconnector->base.index; + dc = aconnector->dc_link->dc; if (config->dpms_off) { hdcp_remove_display(hdcp_work, link_index, aconnector); @@ -635,7 +647,7 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) link->dp.mst_enabled = config->mst_enabled; link->dp.dp2_enabled = config->dp2_enabled; link->dp.usb4_enabled = config->usb4_enabled; - if (aconnector->dc_sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) + if (sink && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) link->hdmi.frl_enabled = config->frl_enabled; display->adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; link->adjust.auth_delay = 2; From 72b94048020ce40ea8502ab47cf6c7fb33cbd78f Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Fri, 26 Jun 2026 11:39:04 -0400 Subject: [PATCH 077/155] drm/amd/display: limit reuse dsc capable bootup timing [why] Previous dcn315 BIOS has known dsc issue, not good for reuse their dsc settings. this pre-OS dsc issue is fixed in dcn42 bios. since there is no production bios update for older asic, limit our new code instead. Reviewed-by: Mohit Bawa Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 7934341b4715..dab19a360b02 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -1945,6 +1945,13 @@ bool dc_validate_boot_timing(const struct dc *dc, struct display_stream_compressor *dsc = NULL; struct dcn_dsc_state dsc_state = {0}; + if (dc->ctx->dce_version < DCN_VERSION_4_2) { + /*vbios enabled eDP dsc for one of DCN315 only but it has known issue, + since there is no production bios update, block it there*/ + DC_LOG_DEBUG("boot timing validation failed due to unsupported DSC on this ASIC\n"); + return false; + } + /* Find DSC associated with this timing generator */ if (tg_inst < (unsigned int)dc->res_pool->res_cap->num_dsc) { dsc = dc->res_pool->dscs[tg_inst]; From bd9e2b5b0473c75abc0f4134dfe79ecbfb16610d Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 29 Jun 2026 15:27:00 -0500 Subject: [PATCH 078/155] drm/amd/display: Fix backlight max_brightness to match exported range [Why] FWTS autobrightness fails on eDP panels because actual_brightness can read higher than the advertised max_brightness (e.g. 63576 vs 62451). The conversion helpers expose the firmware PWM range to userspace as [0..max]. But max_brightness is advertised as (max - min), which is smaller. So reading the level can return a value above max_brightness. This regressed in commit 4b61b8a39051 ("drm/amd/display: Add debugging message for brightness caps"), which changed max_brightness to (max - min) and undid commit 8dbd72cb7900 ("drm/amd/display: Export full brightness range to userspace"). [How] Advertise max_brightness as max, and scale the initial AC/DC brightness against max too. Update the KUnit expectations to match. Fixes: 4b61b8a39051 ("drm/amd/display: Add debugging message for brightness caps") Reviewed-by: Alex Hung Signed-off-by: Mario Limonciello Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 6 +++--- .../display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 11d54897a894..ca60c72855fd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -407,12 +407,12 @@ void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, if (get_brightness_range(caps, &min, &max)) { if (is_system_supplied) - props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, + props->brightness = DIV_ROUND_CLOSEST(max * caps->ac_level, 100); else - props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, + props->brightness = DIV_ROUND_CLOSEST(max * caps->dc_level, 100); - props->max_brightness = max - min; + props->max_brightness = max; } else { props->brightness = MAX_BACKLIGHT_LEVEL; props->max_brightness = MAX_BACKLIGHT_LEVEL; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index adb896022a27..8ebc0f263e3e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -799,8 +799,8 @@ static void dm_test_backlight_fill_props_ac_linear(struct kunit *test) amdgpu_dm_backlight_fill_props(&caps, true, false, &props); KUNIT_EXPECT_EQ(test, props.brightness, - DIV_ROUND_CLOSEST((max - min) * caps.ac_level, 100)); - KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + DIV_ROUND_CLOSEST(max * caps.ac_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max); KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR); KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); } @@ -825,8 +825,8 @@ static void dm_test_backlight_fill_props_dc_nonlinear(struct kunit *test) amdgpu_dm_backlight_fill_props(&caps, false, true, &props); KUNIT_EXPECT_EQ(test, props.brightness, - DIV_ROUND_CLOSEST((max - min) * caps.dc_level, 100)); - KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + DIV_ROUND_CLOSEST(max * caps.dc_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max); KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_NON_LINEAR); KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); } From f02b2e47355e9d381a7f34acc997e1c47879ca21 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 18:18:55 -0600 Subject: [PATCH 079/155] drm/amd/display: Add KUnit test for wb jobs [WHAT] Add KUnit tests to verify both functions return early when job->fb is NULL without touching any buffer object. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 6 ++- .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h | 4 ++ .../amdgpu_dm/tests/amdgpu_dm_wb_test.c | 44 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index 0bf82e46f773..1fbb568a5c80 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -85,7 +85,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_wb_connector_get_modes(struct drm_connector *conn } EXPORT_IF_KUNIT(amdgpu_dm_wb_connector_get_modes); -static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, +STATIC_IFN_KUNIT int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, struct drm_writeback_job *job) { struct amdgpu_framebuffer *afb; @@ -146,8 +146,9 @@ static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector amdgpu_bo_unreserve(rbo); return r; } +EXPORT_IF_KUNIT(amdgpu_dm_wb_prepare_job); -static void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, +STATIC_IFN_KUNIT void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, struct drm_writeback_job *job) { struct amdgpu_bo *rbo; @@ -167,6 +168,7 @@ static void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, amdgpu_bo_unreserve(rbo); amdgpu_bo_unref(&rbo); } +EXPORT_IF_KUNIT(amdgpu_dm_wb_cleanup_job); static const struct drm_encoder_helper_funcs amdgpu_dm_wb_encoder_helper_funcs = { .atomic_check = amdgpu_dm_wb_encoder_atomic_check, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h index 7e9fd7a036fa..5fd616bc43b5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h @@ -44,6 +44,10 @@ int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state); int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector); +int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, + struct drm_writeback_job *job); +void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, + struct drm_writeback_job *job); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c index c71f61a2438d..b43bc244487e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -364,6 +364,47 @@ static void dm_test_wb_connector_init_success(struct kunit *test) KUNIT_EXPECT_EQ(test, wbcon->base.encoder.possible_crtcs, 0x1); } +/* Tests for amdgpu_dm_wb_prepare_job / amdgpu_dm_wb_cleanup_job */ + +/** + * dm_test_wb_prepare_job_no_fb - Verify prepare_job early return without a framebuffer + * @test: KUnit test context + * + * When job->fb is NULL there is nothing to pin, so amdgpu_dm_wb_prepare_job() + * must return 0 without touching any buffer object. + */ +static void dm_test_wb_prepare_job_no_fb(struct kunit *test) +{ + struct drm_writeback_job *job; + int ret; + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + job->fb = NULL; + ret = amdgpu_dm_wb_prepare_job(NULL, job); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_cleanup_job_no_fb - Verify cleanup_job early return without a framebuffer + * @test: KUnit test context + * + * When job->fb is NULL there is nothing to unpin, so amdgpu_dm_wb_cleanup_job() + * must return immediately. + */ +static void dm_test_wb_cleanup_job_no_fb(struct kunit *test) +{ + struct drm_writeback_job *job; + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + job->fb = NULL; + /* Should return without dereferencing any buffer object. */ + amdgpu_dm_wb_cleanup_job(NULL, job); +} + static struct kunit_case dm_wb_test_cases[] = { /* amdgpu_dm_wb_encoder_atomic_check */ KUNIT_CASE(dm_test_wb_atomic_check_no_job), @@ -378,6 +419,9 @@ static struct kunit_case dm_wb_test_cases[] = { KUNIT_CASE(dm_test_wb_get_modes_bounded_by_max), /* amdgpu_dm_wb_connector_init */ KUNIT_CASE(dm_test_wb_connector_init_success), + /* amdgpu_dm_wb_prepare_job / amdgpu_dm_wb_cleanup_job */ + KUNIT_CASE(dm_test_wb_prepare_job_no_fb), + KUNIT_CASE(dm_test_wb_cleanup_job_no_fb), {} }; From 9a77c86ec8f5e3186b77437958d918698f1d53dd Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 26 Jun 2026 21:12:25 -0600 Subject: [PATCH 080/155] drm/amd/display: Test MST coverage gaps [WHAT] Add KUnit tests for uncovered MST paths: - dm_mst_get_pbn_divider with non-null link - retrieve_downstream_port_device AUX read failure - dm_dp_aux_transfer non-ACK reply handling - dm_handle_mst_sideband_msg_ready_event ESI read failure and ready-bit without MST state - dm_handle_mst_down_rep_msg_ready wrapper - amdgpu_dm_initialize_dp_connector eDP early return - amdgpu_dm_initialize_dp_connector non-eDP MST init path (dc_link_dp_get_max_link_enc_cap, drm_dp_mst_topology_mgr_init and subconnector property attach) The non-eDP MST test uses standard DRM KUnit helpers to build a full fixture (drmm_mode_config_init plus drm_connector_init), which the topology manager and subconnector property require, and stubs the DC link service get_max_link_enc_cap callback. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_mst_types.c | 4 +- .../display/amdgpu_dm/amdgpu_dm_mst_types.h | 1 + .../tests/amdgpu_dm_mst_types_test.c | 381 ++++++++++++++++-- 3 files changed, 361 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 0546efea5de1..a45d8d79f3b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -862,10 +862,11 @@ void dm_handle_mst_sideband_msg_ready_event( } EXPORT_IF_KUNIT(dm_handle_mst_sideband_msg_ready_event); -static void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr) +STATIC_IFN_KUNIT void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr) { dm_handle_mst_sideband_msg_ready_event(mgr, DOWN_REP_MSG_RDY_EVENT); } +EXPORT_IF_KUNIT(dm_handle_mst_down_rep_msg_ready); static const struct drm_dp_mst_topology_cbs dm_mst_cbs = { .add_connector = dm_dp_add_mst_connector, @@ -900,6 +901,7 @@ void amdgpu_dm_initialize_dp_connector(struct amdgpu_display_manager *dm, drm_connector_attach_dp_subconnector_property(&aconnector->base); } +EXPORT_IF_KUNIT(amdgpu_dm_initialize_dp_connector); uint32_t dm_mst_get_pbn_divider(struct dc_link *link) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index fecf108a9216..2ae9d4b88888 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -111,6 +111,7 @@ void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload); ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg); u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type); void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read); +void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr); struct drm_encoder *dm_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state); int dm_dp_mst_atomic_check(struct drm_connector *connector, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index eaffff61129e..caedad4197bd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -8,7 +8,11 @@ #include #include +#include #include +#include +#include +#include #include #include #include @@ -31,7 +35,9 @@ static u8 dm_mst_test_dpcd[0x10]; static u8 dm_mst_test_desc_dpcd[0x10]; static struct aux_payload dm_mst_test_last_payload; static int dm_mst_test_aux_transfer_raw_result; +static u8 dm_mst_test_aux_transfer_raw_reply; static enum aux_return_code_type dm_mst_test_aux_transfer_raw_operation_result; +static ssize_t dm_mst_test_aux_transfer_override; static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc, struct aux_payload *payload, @@ -41,6 +47,7 @@ static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc, dm_mst_test_last_payload = *payload; *operation_result = dm_mst_test_aux_transfer_raw_operation_result; + payload->reply[0] = dm_mst_test_aux_transfer_raw_reply; if (dm_mst_test_aux_transfer_raw_result) return dm_mst_test_aux_transfer_raw_result; @@ -64,6 +71,7 @@ static void dm_mst_test_setup_dm_aux(struct amdgpu_dm_dp_aux *dm_aux, { memset(&dm_mst_test_last_payload, 0, sizeof(dm_mst_test_last_payload)); dm_mst_test_aux_transfer_raw_result = 0; + dm_mst_test_aux_transfer_raw_reply = 0; dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_SUCCESS; link_srv->aux_transfer_raw = dm_mst_test_aux_transfer_raw; dc->link_srv = link_srv; @@ -87,6 +95,11 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { size_t i; + ssize_t ret; + + ret = dm_mst_test_aux_transfer_override; + if (ret) + return ret; switch (msg->request & ~DP_AUX_I2C_MOT) { case DP_AUX_NATIVE_READ: @@ -103,6 +116,53 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, } } +static struct amdgpu_dm_connector *dm_mst_test_alloc_sideband_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct link_service *link_srv; + struct dc_link *link; + struct dc *dc; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + + mutex_init(&aconnector->handle_mst_msg_ready); + link_srv->get_status = dm_mst_test_get_status; + dc->link_srv = link_srv; + link->dc = dc; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->link_status.dpcd_caps = &link->dpcd_caps; + aconnector->dc_link = link; + aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux"; + aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false); + + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + dm_mst_test_aux_transfer_override = 0; + + return aconnector; +} + +static uint32_t dm_mst_test_dp_link_bandwidth_kbps( + const struct dc_link *link, + const struct dc_link_settings *link_settings) +{ + return 4320000; +} + +static const struct dc_link_settings *dm_mst_test_dp_get_verified_link_cap( + const struct dc_link *link) +{ + return &link->verified_link_cap; +} + static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { @@ -254,6 +314,35 @@ static void dm_mst_test_pbn_divider_null_link(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(NULL), 0U); } +/** + * dm_mst_test_pbn_divider_uses_link_bandwidth - Test pbn_divider with link cap + * @test: KUnit test context + * + * Verify that dm_mst_get_pbn_divider() uses the DC link service to derive the + * fixed-point PBN divider when a link is present. + */ +static void dm_mst_test_pbn_divider_uses_link_bandwidth(struct kunit *test) +{ + struct link_service *link_srv; + struct dc_link *link; + struct dc *dc; + + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link_srv->dp_get_verified_link_cap = dm_mst_test_dp_get_verified_link_cap; + link_srv->dp_link_bandwidth_kbps = dm_mst_test_dp_link_bandwidth_kbps; + dc->link_srv = link_srv; + link->dc = dc; + + KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(link), + (uint32_t)(dfixed_const(1000) / 100)); +} + /* Tests for amdgpu_dm_mst_reset_mst_connector_setting */ /** @@ -331,6 +420,7 @@ static void dm_mst_test_retrieve_downstream_present(struct kunit *test) memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); /* PORT_PRESENT = 1, PORT_TYPE = 2 (0b101) */ dm_mst_test_dpcd[DP_DOWNSTREAMPORT_PRESENT] = 0x05; + dm_mst_test_aux_transfer_override = 0; aux->name = "dm_mst_test_aux"; aux->transfer = dm_mst_test_aux_transfer; @@ -345,6 +435,35 @@ static void dm_mst_test_retrieve_downstream_present(struct kunit *test) (int)aconnector->mst_downstream_port_present.fields.PORT_TYPE, 2); } +/** + * dm_mst_test_retrieve_downstream_aux_error - Test downstream read failure + * @test: KUnit test context + * + * Verify that retrieve_downstream_port_device() returns false when the AUX + * DPCD read fails. + */ +static void dm_mst_test_retrieve_downstream_aux_error(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, aux); + + dm_mst_test_aux_transfer_override = -EIO; + aux->name = "dm_mst_test_aux"; + aux->transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(aux); + drm_dp_dpcd_set_probe(aux, false); + aconnector->dsc_aux = aux; + + KUNIT_EXPECT_FALSE(test, retrieve_downstream_port_device(aconnector)); + + dm_mst_test_aux_transfer_override = 0; +} + /* Tests for retrieve_branch_specific_data */ /** @@ -710,6 +829,55 @@ static void dm_mst_test_aux_transfer_hpd_discon_quirk(struct kunit *test) DP_SIDEBAND_MSG_DOWN_REQ_BASE); } +/** + * dm_mst_test_aux_transfer_non_ack_reply - non-ACK AUX reply is logged. + * @test: KUnit test context. + * + * A successful read with a nonzero reply byte should still return the backend + * byte count while exercising the non-ACK reply handling path. + */ +static void dm_mst_test_aux_transfer_non_ack_reply(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 0 }; + struct drm_dp_aux_msg msg = { + .address = 4, + .request = DP_AUX_NATIVE_READ, + .buffer = buffer, + .size = sizeof(buffer), + }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + dm_mst_test_aux_transfer_raw_reply = DP_AUX_NATIVE_REPLY_NACK; + + ret = dm_dp_aux_transfer(&dm_aux->aux, &msg); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U); +} + /** * dm_mst_test_fill_payload_flags_native_write - native write request decode. * @test: KUnit test context. @@ -843,31 +1011,8 @@ static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test) static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test) { struct amdgpu_dm_connector *aconnector; - struct link_service *link_srv; - struct dc_link *link; - struct dc *dc; - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - KUNIT_ASSERT_NOT_NULL(test, link_srv); - KUNIT_ASSERT_NOT_NULL(test, link); - KUNIT_ASSERT_NOT_NULL(test, dc); - - mutex_init(&aconnector->handle_mst_msg_ready); - link_srv->get_status = dm_mst_test_get_status; - dc->link_srv = link_srv; - link->dc = dc; - link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link->link_status.dpcd_caps = &link->dpcd_caps; - aconnector->dc_link = link; - aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux"; - aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer; - drm_dp_aux_init(&aconnector->dm_dp_aux.aux); - drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false); - memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + aconnector = dm_mst_test_alloc_sideband_connector(test); dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_REP_MSG_RDY_EVENT); @@ -875,6 +1020,185 @@ static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); } +/** + * dm_mst_test_sideband_msg_ready_read_error - Test ESI read failure path + * @test: KUnit test context + * + * Verify that dm_handle_mst_sideband_msg_ready_event() returns cleanly when + * the DPCD read fails before a ready bit can be handled. + */ +static void dm_mst_test_sideband_msg_ready_read_error(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_mst_test_alloc_sideband_connector(test); + dm_mst_test_aux_transfer_override = -EIO; + + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, + DOWN_REP_MSG_RDY_EVENT); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); + dm_mst_test_aux_transfer_override = 0; +} + +/** + * dm_mst_test_sideband_msg_ready_without_mst_state - Test ready bit no-op path + * @test: KUnit test context + * + * Verify that a DOWN_REP ready bit is filtered and then ignored when the MST + * topology manager is not enabled. + */ +static void dm_mst_test_sideband_msg_ready_without_mst_state(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_mst_test_alloc_sideband_connector(test); + dm_mst_test_dpcd[(DP_SINK_COUNT_ESI + 1) & 0xf] = DP_DOWN_REP_MSG_RDY; + + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, + DOWN_REP_MSG_RDY_EVENT); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[(DP_SINK_COUNT_ESI + 1) & 0xf], + DP_DOWN_REP_MSG_RDY); +} + +/** + * dm_mst_test_down_rep_msg_ready_wrapper - Test DOWN_REP wrapper + * @test: KUnit test context + * + * Verify that dm_handle_mst_down_rep_msg_ready() forwards to the generic MST + * sideband handler with the DOWN_REP event selection. + */ +static void dm_mst_test_down_rep_msg_ready_wrapper(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_mst_test_alloc_sideband_connector(test); + + dm_handle_mst_down_rep_msg_ready(&aconnector->mst_mgr); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); +} + +/** + * dm_mst_test_initialize_dp_connector_edp - Test eDP initialization path + * @test: KUnit test context + * + * Verify that amdgpu_dm_initialize_dp_connector() initializes the DP AUX state + * and exits before MST topology setup for eDP connectors. + */ +static void dm_mst_test_initialize_dp_connector_edp(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + + adev->dm.adev = adev; + adev->dm.ddev = &adev->ddev; + link->ddc = ddc; + aconnector = dm_kunit_alloc_connector(test, adev, link); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + + amdgpu_dm_initialize_dp_connector(&adev->dm, aconnector, 5); + + KUNIT_EXPECT_TRUE(test, aconnector->dm_dp_aux.aux.transfer == dm_dp_aux_transfer); + KUNIT_EXPECT_PTR_EQ(test, aconnector->dm_dp_aux.aux.drm_dev, &adev->ddev); + KUNIT_EXPECT_PTR_EQ(test, aconnector->dm_dp_aux.ddc_service, ddc); + KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_mgr.dev, NULL); + KUNIT_EXPECT_NOT_NULL(test, aconnector->dm_dp_aux.aux.name); + if (aconnector->dm_dp_aux.aux.name) + KUNIT_EXPECT_NOT_NULL(test, strstr(aconnector->dm_dp_aux.aux.name, "5")); + + drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); + kfree(aconnector->dm_dp_aux.aux.name); +} + +static bool dm_mst_test_dp_get_max_link_enc_cap(const struct dc_link *link, + struct dc_link_settings *cap) +{ + return true; +} + +static void dm_mst_test_connector_destroy(struct drm_connector *connector) +{ +} + +static const struct drm_connector_funcs dm_mst_test_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .destroy = dm_mst_test_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/** + * dm_mst_test_initialize_dp_connector_mst - Test MST root initialization path + * @test: KUnit test context + * + * Verify that amdgpu_dm_initialize_dp_connector() initializes the MST topology + * manager for a non-eDP DisplayPort connector. This exercises the path past the + * eDP early return, including dc_link_dp_get_max_link_enc_cap() and + * drm_dp_mst_topology_mgr_init(). A fully initialized DRM mode config and + * connector are required because the topology manager registers a private + * atomic object and the subconnector property is attached to the connector. + */ +static void dm_mst_test_initialize_dp_connector_mst(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct link_service *link_srv; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + int ret; + + adev = dm_kunit_alloc_adev(test); + + ret = drmm_mode_config_init(&adev->ddev); + KUNIT_ASSERT_EQ(test, ret, 0); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = dm_kunit_alloc_link(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + + link_srv->dp_get_max_link_enc_cap = dm_mst_test_dp_get_max_link_enc_cap; + dc->link_srv = link_srv; + link->dc = dc; + link->ddc = ddc; + + adev->dm.adev = adev; + adev->dm.ddev = &adev->ddev; + + aconnector = dm_kunit_alloc_connector(test, adev, link); + + ret = drm_connector_init(&adev->ddev, &aconnector->base, + &dm_mst_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + KUNIT_ASSERT_EQ(test, ret, 0); + + amdgpu_dm_initialize_dp_connector(&adev->dm, aconnector, 7); + + KUNIT_EXPECT_TRUE(test, aconnector->dm_dp_aux.aux.transfer == dm_dp_aux_transfer); + KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_mgr.dev, &adev->ddev); + KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_mgr.aux, &aconnector->dm_dp_aux.aux); + KUNIT_EXPECT_EQ(test, aconnector->mst_mgr.max_payloads, 4); + KUNIT_EXPECT_TRUE(test, aconnector->mst_mgr.cbs != NULL); + + drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); + drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); + kfree(aconnector->dm_dp_aux.aux.name); + drm_connector_cleanup(&aconnector->base); +} + /** * dm_mst_test_atomic_best_encoder - Test MST encoder selection * @test: KUnit test context @@ -1040,11 +1364,13 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_zero_sink_count), /* dm_mst_get_pbn_divider tests */ KUNIT_CASE(dm_mst_test_pbn_divider_null_link), + KUNIT_CASE(dm_mst_test_pbn_divider_uses_link_bandwidth), /* amdgpu_dm_mst_reset_mst_connector_setting tests */ KUNIT_CASE(dm_mst_test_reset_connector_setting), /* retrieve_downstream_port_device tests */ KUNIT_CASE(dm_mst_test_retrieve_downstream_no_aux), KUNIT_CASE(dm_mst_test_retrieve_downstream_present), + KUNIT_CASE(dm_mst_test_retrieve_downstream_aux_error), /* retrieve_branch_specific_data tests */ KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent), KUNIT_CASE(dm_mst_test_retrieve_branch_reads_oui), @@ -1058,6 +1384,7 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_aux_transfer_partial_write), KUNIT_CASE(dm_mst_test_aux_transfer_error_result), KUNIT_CASE(dm_mst_test_aux_transfer_hpd_discon_quirk), + KUNIT_CASE(dm_mst_test_aux_transfer_non_ack_reply), /* dm_dp_aux_fill_payload_flags tests */ KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write), KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read), @@ -1070,6 +1397,12 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi), /* dm_handle_mst_sideband_msg_ready_event tests */ KUNIT_CASE(dm_mst_test_sideband_msg_ready_no_ready_bits), + KUNIT_CASE(dm_mst_test_sideband_msg_ready_read_error), + KUNIT_CASE(dm_mst_test_sideband_msg_ready_without_mst_state), + KUNIT_CASE(dm_mst_test_down_rep_msg_ready_wrapper), + /* amdgpu_dm_initialize_dp_connector tests */ + KUNIT_CASE(dm_mst_test_initialize_dp_connector_edp), + KUNIT_CASE(dm_mst_test_initialize_dp_connector_mst), /* dm_mst_atomic_best_encoder tests */ KUNIT_CASE(dm_mst_test_atomic_best_encoder), /* dm_dp_create_fake_mst_encoders tests */ From ee486cb800915b2fc7ecfd108982198045860060 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 29 Jun 2026 18:37:52 -0600 Subject: [PATCH 081/155] drm/amd/display: Test backlight level programming path [WHAT] Add KUnit coverage for the backlight level write path in amdgpu_dm_backlight.c. Tests cover stream lookup hit/miss, the connector-off and no-stream early returns, and the AUX/PWM/idle-reallow programming branches. The DC programming calls stay safe by injecting a NULL power module and keeping idle optimizations a no-op via disable_idle_power_optimizations. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 9 +- .../display/amdgpu_dm/amdgpu_dm_backlight.h | 5 + .../tests/amdgpu_dm_backlight_test.c | 260 ++++++++++++++++++ 3 files changed, 272 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index ca60c72855fd..d35b90912bd2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -219,7 +219,8 @@ u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, } EXPORT_IF_KUNIT(convert_brightness_to_user); -static struct dc_stream_state *dm_find_stream_with_link( +STATIC_IFN_KUNIT +struct dc_stream_state *dm_find_stream_with_link( struct amdgpu_display_manager *dm, struct dc_link *link) { @@ -235,6 +236,7 @@ static struct dc_stream_state *dm_find_stream_with_link( return NULL; } +EXPORT_IF_KUNIT(dm_find_stream_with_link); STATIC_IFN_KUNIT int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, @@ -346,8 +348,10 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, if (rc) dm->actual_brightness[bl_idx] = user_brightness; } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_set_level); -static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) +STATIC_IFN_KUNIT +int amdgpu_dm_backlight_update_status(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); int i = amdgpu_dm_backlight_get_device_index(dm, bd); @@ -356,6 +360,7 @@ static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_update_status); static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 98d612c60ae9..5fbaf67369f1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -28,6 +28,8 @@ struct amdgpu_display_manager; struct amdgpu_dm_connector; struct backlight_device; struct backlight_properties; +struct dc_link; +struct dc_stream_state; struct drm_connector; struct attribute_group; @@ -49,6 +51,9 @@ bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector); extern const struct attribute_group amdgpu_group; #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct dc_stream_state *dm_find_stream_with_link(struct amdgpu_display_manager *dm, + struct dc_link *link); +int amdgpu_dm_backlight_update_status(struct backlight_device *bd); int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, unsigned int *min, unsigned int *max); void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 8ebc0f263e3e..732ac8db742d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -9,6 +9,7 @@ #include #include "dc.h" +#include "dc_dmub_srv.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" @@ -40,6 +41,255 @@ static void setup_test_connector(struct kunit *test, fixture->link->connector_signal = signal; } +static void setup_test_dm_ddev(struct kunit *test, struct amdgpu_display_manager *dm) +{ + struct drm_device *ddev; + + ddev = kunit_kzalloc(test, sizeof(*ddev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddev); + + INIT_LIST_HEAD(&ddev->mode_config.connector_list); + spin_lock_init(&ddev->mode_config.connector_list_lock); + dm->ddev = ddev; +} + +/* Tests for dm_find_stream_with_link() */ + +/** + * dm_test_find_stream_with_link_returns_match - Test matching stream lookup + * @test: The KUnit test context + */ +static void dm_test_find_stream_with_link_returns_match(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *other_link = dm_kunit_alloc_link(test); + struct dc_link *target_link = dm_kunit_alloc_link(test); + struct dc_stream_state *stream; + + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, other_link); + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 1, target_link); + stream = dm_find_stream_with_link(dm, target_link); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_PTR_EQ(test, stream->link, target_link); +} + +/** + * dm_test_find_stream_with_link_missing - Test missing stream lookup + * @test: The KUnit test context + */ +static void dm_test_find_stream_with_link_missing(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *stream_link = dm_kunit_alloc_link(test); + struct dc_link *missing_link = dm_kunit_alloc_link(test); + + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, stream_link); + + KUNIT_EXPECT_NULL(test, dm_find_stream_with_link(dm, missing_link)); +} + +/* Tests for amdgpu_dm_backlight_set_level() */ + +/** + * dm_test_backlight_set_level_connector_off - Test connector-off cache path + * @test: The KUnit test context + * + * If the matching connector has no encoder, set_level() must cache the + * requested brightness and return before touching DC or backlight hardware. + */ +static void dm_test_backlight_set_level_connector_off(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct amdgpu_dm_connector *aconnector; + + setup_test_dm_ddev(test, dm); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + INIT_LIST_HEAD(&aconnector->base.head); + aconnector->bl_idx = 1; + aconnector->base.encoder = NULL; + list_add_tail(&aconnector->base.head, &dm->ddev->mode_config.connector_list); + + amdgpu_dm_backlight_set_level(dm, 1, 1234); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 1234U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_no_stream - Test no-stream early return + * @test: The KUnit test context + * + * With no stream for the backlight link, set_level() records the requested + * brightness and exits before calling the power-module programming path. + */ +static void dm_test_backlight_set_level_no_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + + setup_test_dm_ddev(test, dm); + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_aux_programs_power_module - Test AUX programming path + * @test: The KUnit test context + * + * With a matching stream present, set_level() walks into the DC programming + * path. A NULL power_module makes mod_power_set_backlight_nits() a safe + * early-false, and ips_support disabled leaves idle optimizations untouched. + * A non-matching connector exercises the connector-list skip, and a non-zero + * brightness_mask exercises the quirk-OR path. + */ +static void dm_test_backlight_set_level_aux_programs_power_module(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct amdgpu_dm_connector *other; + + setup_test_dm_ddev(test, dm); + mutex_init(&dm->dc_lock); + dm->power_module = NULL; + + /* Non-matching connector exercises the bl_idx skip (continue). */ + other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, other); + INIT_LIST_HEAD(&other->base.head); + other->bl_idx = 0; + list_add_tail(&other->base.head, &dm->ddev->mode_config.connector_list); + + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].aux_support = true; + dm->backlight_caps[1].brightness_mask = 0x3; + dm->backlight_caps[1].aux_min_input_signal = 1; + dm->backlight_caps[1].aux_max_input_signal = 512; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, link); + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + /* power_module is NULL so programming fails; actual stays unchanged. */ + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_pwm_programs_power_module - Test PWM programming path + * @test: The KUnit test context + * + * With aux_support cleared, set_level() takes the millipercent branch: + * get_brightness_range() + mod_power_set_backlight_percent(). A NULL + * power_module keeps the call a safe early-false. + */ +static void dm_test_backlight_set_level_pwm_programs_power_module(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + + setup_test_dm_ddev(test, dm); + mutex_init(&dm->dc_lock); + dm->power_module = NULL; + + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].aux_support = false; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, link); + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_reallows_idle - Test idle-optimization toggle path + * @test: The KUnit test context + * + * When ips_support is set and dmub idle is allowed, set_level() disables idle + * optimizations around the programming call and re-enables them afterwards. + * disable_idle_power_optimizations keeps dc_allow_idle_optimizations() a safe + * early return, and ctx->logger is wired because DC_LOG_* dereferences it. + */ +static void dm_test_backlight_set_level_reallows_idle(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct dc_dmub_srv *dmub_srv; + struct dal_logger *logger; + struct dc_context *ctx; + + setup_test_dm_ddev(test, dm); + mutex_init(&dm->dc_lock); + dm->power_module = NULL; + + /* dm_kunit_alloc_dm() leaves dc->ctx NULL; the idle path dereferences it. */ + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + dm->dc->ctx = ctx; + + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + logger->dev = &adev->ddev; + dm->dc->ctx->logger = logger; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + dmub_srv->idle_allowed = true; + dm->dc->ctx->dmub_srv = dmub_srv; + dm->dc->caps.ips_support = true; + /* Keep dc_allow_idle_optimizations() a safe early return. */ + dm->dc->debug.disable_idle_power_optimizations = true; + + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].aux_support = true; + dm->backlight_caps[1].aux_min_input_signal = 1; + dm->backlight_caps[1].aux_max_input_signal = 512; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, link); + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); +} + +/** + * dm_test_backlight_update_status_no_stream - Test update_status wrapper + * @test: The KUnit test context + */ +static void dm_test_backlight_update_status_no_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct backlight_device *bd; + + setup_test_dm_ddev(test, dm); + bd = kunit_kzalloc(test, sizeof(*bd), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bd); + dev_set_drvdata(&bd->dev, dm); + bd->props.brightness = 3456; + dm->num_of_edps = 2; + dm->backlight_dev[1] = bd; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_update_status(bd), 0); + KUNIT_EXPECT_EQ(test, dm->brightness[1], 3456U); +} + /* Tests for amdgpu_dm_backlight_get_device_index() */ /** @@ -1200,6 +1450,16 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) } static struct kunit_case dm_backlight_test_cases[] = { + /* dm_find_stream_with_link */ + KUNIT_CASE(dm_test_find_stream_with_link_returns_match), + KUNIT_CASE(dm_test_find_stream_with_link_missing), + /* amdgpu_dm_backlight_set_level / update_status */ + KUNIT_CASE(dm_test_backlight_set_level_connector_off), + KUNIT_CASE(dm_test_backlight_set_level_no_stream), + KUNIT_CASE(dm_test_backlight_set_level_aux_programs_power_module), + KUNIT_CASE(dm_test_backlight_set_level_pwm_programs_power_module), + KUNIT_CASE(dm_test_backlight_set_level_reallows_idle), + KUNIT_CASE(dm_test_backlight_update_status_no_stream), /* amdgpu_dm_backlight_get_device_index */ KUNIT_CASE(dm_test_backlight_device_index_matches_second), KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), From c50a4305dfc2e91681c3991af3006ccbb2ec0eb7 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 29 Jun 2026 18:39:36 -0600 Subject: [PATCH 082/155] drm/amd/display: Test backlight level readback path [WHAT] Add KUnit coverage for the backlight level read path. A faked link_service vtable returns canned PWM and AUX brightness values, letting the tests verify both the successful readback and the DC_ERROR/AUX-failure fallbacks to the cached brightness, plus the get_brightness() device-index wrapper. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 9 +- .../display/amdgpu_dm/amdgpu_dm_backlight.h | 2 + .../tests/amdgpu_dm_backlight_test.c | 156 ++++++++++++++++++ 3 files changed, 164 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index d35b90912bd2..452e772ef136 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -362,8 +362,8 @@ int amdgpu_dm_backlight_update_status(struct backlight_device *bd) } EXPORT_IF_KUNIT(amdgpu_dm_backlight_update_status); -static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, - int bl_idx) +STATIC_IFN_KUNIT +u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx) { int ret; struct amdgpu_dm_backlight_caps caps; @@ -387,14 +387,17 @@ static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, return convert_brightness_to_user(&caps, ret); } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_level); -static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) +STATIC_IFN_KUNIT +int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); int i = amdgpu_dm_backlight_get_device_index(dm, bd); return amdgpu_dm_backlight_get_level(dm, i); } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_brightness); static const struct backlight_ops amdgpu_dm_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 5fbaf67369f1..7b417c79980f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -54,6 +54,8 @@ extern const struct attribute_group amdgpu_group; struct dc_stream_state *dm_find_stream_with_link(struct amdgpu_display_manager *dm, struct dc_link *link); int amdgpu_dm_backlight_update_status(struct backlight_device *bd); +u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx); +int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd); int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, unsigned int *min, unsigned int *max); void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 732ac8db742d..10965306e29d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -16,6 +16,7 @@ #include "amdgpu_dm_backlight.h" #include "amdgpu_dm_kunit_test_helpers.h" #include "amd_shared.h" +#include "link_service.h" #include "dc/inc/hw/panel_cntl.h" struct dm_backlight_connector_fixture { @@ -290,6 +291,155 @@ static void dm_test_backlight_update_status_no_stream(struct kunit *test) KUNIT_EXPECT_EQ(test, dm->brightness[1], 3456U); } +static void setup_test_link_service(struct kunit *test, struct dc_link *link) +{ + struct link_service *link_srv; + struct dc_context *ctx; + struct dc *dc; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; +} + +static int dm_test_get_backlight_level_mid(const struct dc_link *link) +{ + return (0x101 * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) + 1000; +} + +static int dm_test_get_backlight_level_error(const struct dc_link *link) +{ + return DC_ERROR_UNEXPECTED; +} + +static bool dm_test_get_backlight_level_nits(struct dc_link *link, + uint32_t *avg, + uint32_t *peak) +{ + *avg = 250000; + *peak = 300000; + + return true; +} + +static bool dm_test_get_backlight_level_nits_fail(struct dc_link *link, + uint32_t *avg, + uint32_t *peak) +{ + return false; +} + +/* Tests for amdgpu_dm_backlight_get_level()/get_brightness() */ + +/** + * dm_test_backlight_get_level_pwm_success - Test PWM brightness readback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_pwm_success(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + u32 hw_level = dm_test_get_backlight_level_mid(link); + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_mid; + dm->backlight_link[0] = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), + convert_brightness_to_user(&dm->backlight_caps[0], hw_level)); +} + +/** + * dm_test_backlight_get_level_pwm_error - Test PWM readback fallback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_pwm_error(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error; + dm->brightness[0] = 4321; + dm->backlight_link[0] = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), 4321U); +} + +/** + * dm_test_backlight_get_level_aux_success - Test AUX brightness readback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_aux_success(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level_nits = dm_test_get_backlight_level_nits; + dm->backlight_link[0] = link; + caps->caps_valid = true; + caps->aux_support = true; + caps->aux_min_input_signal = 1; + caps->aux_max_input_signal = 512; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), + convert_brightness_to_user(caps, 250000)); +} + +/** + * dm_test_backlight_get_level_aux_error - Test AUX readback fallback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_aux_error(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level_nits = dm_test_get_backlight_level_nits_fail; + dm->brightness[0] = 6789; + dm->backlight_link[0] = link; + caps->caps_valid = true; + caps->aux_support = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), 6789U); +} + +/** + * dm_test_backlight_get_brightness_uses_device_index - Test get_brightness wrapper + * @test: The KUnit test context + */ +static void dm_test_backlight_get_brightness_uses_device_index(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct backlight_device *bd; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error; + bd = kunit_kzalloc(test, sizeof(*bd), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bd); + dev_set_drvdata(&bd->dev, dm); + dm->num_of_edps = 2; + dm->backlight_dev[1] = bd; + dm->brightness[1] = 2468; + dm->backlight_link[1] = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_brightness(bd), 2468); +} + /* Tests for amdgpu_dm_backlight_get_device_index() */ /** @@ -1460,6 +1610,12 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_backlight_set_level_pwm_programs_power_module), KUNIT_CASE(dm_test_backlight_set_level_reallows_idle), KUNIT_CASE(dm_test_backlight_update_status_no_stream), + /* amdgpu_dm_backlight_get_level / get_brightness */ + KUNIT_CASE(dm_test_backlight_get_level_pwm_success), + KUNIT_CASE(dm_test_backlight_get_level_pwm_error), + KUNIT_CASE(dm_test_backlight_get_level_aux_success), + KUNIT_CASE(dm_test_backlight_get_level_aux_error), + KUNIT_CASE(dm_test_backlight_get_brightness_uses_device_index), /* amdgpu_dm_backlight_get_device_index */ KUNIT_CASE(dm_test_backlight_device_index_matches_second), KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), From 0198bf2a7c707bf1a73cda80df7200f7317520f5 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 29 Jun 2026 18:40:26 -0600 Subject: [PATCH 083/155] drm/amd/display: Test backlight device registration [WHAT] Add KUnit test to amdgpu_dm_register_backlight_device() and cover its bl_idx == -1 early return, which must leave the backlight device slot untouched. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 1 + .../tests/amdgpu_dm_backlight_test.c | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 452e772ef136..859cbce2cdaf 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -494,6 +494,7 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); } } +EXPORT_IF_KUNIT(amdgpu_dm_register_backlight_device); void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 10965306e29d..5a2c32d53487 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -440,6 +440,24 @@ static void dm_test_backlight_get_brightness_uses_device_index(struct kunit *tes KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_brightness(bd), 2468); } +/* Tests for amdgpu_dm_register_backlight_device() */ + +/** + * dm_test_register_backlight_device_negative_index - Test invalid index no-op + * @test: The KUnit test context + */ +static void dm_test_register_backlight_device_negative_index(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->bl_idx = -1; + + amdgpu_dm_register_backlight_device(aconnector); + KUNIT_EXPECT_NULL(test, adev->dm.backlight_dev[0]); +} + /* Tests for amdgpu_dm_backlight_get_device_index() */ /** @@ -1616,6 +1634,8 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_backlight_get_level_aux_success), KUNIT_CASE(dm_test_backlight_get_level_aux_error), KUNIT_CASE(dm_test_backlight_get_brightness_uses_device_index), + /* amdgpu_dm_register_backlight_device */ + KUNIT_CASE(dm_test_register_backlight_device_negative_index), /* amdgpu_dm_backlight_get_device_index */ KUNIT_CASE(dm_test_backlight_device_index_matches_second), KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), From db546ee97153c95ea719da015cf002f970175605 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 29 Jun 2026 18:43:57 -0600 Subject: [PATCH 084/155] drm/amd/display: Test panel power savings sysfs [WHAT] Add KUnit coverage using a DRM-managed connector fixture. Tests cover show() mapping immediate-disable to 0 and reporting an active ABM level, and store() handling the disable mapping, the forbidden update, invalid text, and out-of-range input. show() writes through a page-backed buffer because sysfs_emit() requires one. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 16 +- .../display/amdgpu_dm/amdgpu_dm_backlight.h | 8 + .../tests/amdgpu_dm_backlight_test.c | 177 ++++++++++++++++++ 3 files changed, 195 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 859cbce2cdaf..373ef4d217f1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -611,9 +611,10 @@ EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device); * carefully. */ -static ssize_t panel_power_savings_show(struct device *device, - struct device_attribute *attr, - char *buf) +STATIC_IFN_KUNIT +ssize_t panel_power_savings_show(struct device *device, + struct device_attribute *attr, + char *buf) { struct drm_connector *connector = dev_get_drvdata(device); struct drm_device *dev = connector->dev; @@ -627,10 +628,12 @@ static ssize_t panel_power_savings_show(struct device *device, return sysfs_emit(buf, "%u\n", val); } +EXPORT_IF_KUNIT(panel_power_savings_show); -static ssize_t panel_power_savings_store(struct device *device, - struct device_attribute *attr, - const char *buf, size_t count) +STATIC_IFN_KUNIT +ssize_t panel_power_savings_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) { struct drm_connector *connector = dev_get_drvdata(device); struct drm_device *dev = connector->dev; @@ -660,6 +663,7 @@ static ssize_t panel_power_savings_store(struct device *device, return count; } +EXPORT_IF_KUNIT(panel_power_savings_store); static DEVICE_ATTR_RW(panel_power_savings); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 7b417c79980f..07b75064847c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -30,6 +30,8 @@ struct backlight_device; struct backlight_properties; struct dc_link; struct dc_stream_state; +struct device; +struct device_attribute; struct drm_connector; struct attribute_group; @@ -56,6 +58,12 @@ struct dc_stream_state *dm_find_stream_with_link(struct amdgpu_display_manager * int amdgpu_dm_backlight_update_status(struct backlight_device *bd); u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx); int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd); +ssize_t panel_power_savings_show(struct device *device, + struct device_attribute *attr, + char *buf); +ssize_t panel_power_savings_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count); int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, unsigned int *min, unsigned int *max); void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 5a2c32d53487..b74bbbc7f56f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -8,6 +8,11 @@ #include #include +#include +#include +#include +#include + #include "dc.h" #include "dc_dmub_srv.h" #include "amdgpu.h" @@ -458,6 +463,171 @@ static void dm_test_register_backlight_device_negative_index(struct kunit *test) KUNIT_EXPECT_NULL(test, adev->dm.backlight_dev[0]); } +static struct drm_connector *setup_panel_power_savings_connector(struct kunit *test, + struct device **device_out, + struct dm_connector_state **state_out) +{ + struct dm_connector_state *state; + struct drm_connector *connector; + struct amdgpu_device *adev; + struct device *device; + int ret; + + adev = dm_kunit_alloc_adev(test); + ret = drmm_mode_config_init(&adev->ddev); + KUNIT_ASSERT_EQ(test, ret, 0); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, device); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + connector->dev = &adev->ddev; + connector->state = &state->base; + dev_set_drvdata(device, connector); + *device_out = device; + *state_out = state; + + return connector; +} + +static void dm_test_free_sysfs_buf(void *data) +{ + free_page((unsigned long)data); +} + +static char *dm_test_alloc_sysfs_buf(struct kunit *test) +{ + char *buf; + + buf = (char *)get_zeroed_page(GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, buf); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_sysfs_buf, buf), 0); + + return buf; +} + +/* Tests for panel_power_savings_show()/panel_power_savings_store() */ + +/** + * dm_test_panel_power_savings_show_maps_disable_to_zero - Test show output + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_show_maps_disable_to_zero(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + char *buf; + + setup_panel_power_savings_connector(test, &device, &state); + buf = dm_test_alloc_sysfs_buf(test); + state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + + KUNIT_EXPECT_EQ(test, panel_power_savings_show(device, NULL, buf), 2); + KUNIT_EXPECT_STREQ(test, buf, "0\n"); +} + +/** + * dm_test_panel_power_savings_show_reports_level - Test show output for active level + * @test: The KUnit test context + * + * When abm_level is not the immediate-disable sentinel, show() reports the + * raw level value. + */ +static void dm_test_panel_power_savings_show_reports_level(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + char *buf; + + setup_panel_power_savings_connector(test, &device, &state); + buf = dm_test_alloc_sysfs_buf(test); + state->abm_level = 3; + + KUNIT_EXPECT_EQ(test, panel_power_savings_show(device, NULL, buf), 2); + KUNIT_EXPECT_STREQ(test, buf, "3\n"); +} + +/** + * dm_test_panel_power_savings_store_sets_disable - Test zero maps to disable + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_sets_disable(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + size_t count = strlen("0"); + + setup_panel_power_savings_connector(test, &device, &state); + + KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "0", count), + (ssize_t)count); + KUNIT_EXPECT_EQ(test, state->abm_level, ABM_LEVEL_IMMEDIATE_DISABLE); +} + +/** + * dm_test_panel_power_savings_store_forbidden - Test forbidden update + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_forbidden(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + + setup_panel_power_savings_connector(test, &device, &state); + state->abm_sysfs_forbidden = true; + + KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "1", 1), -EBUSY); +} + +/** + * dm_test_panel_power_savings_store_rejects_invalid_text - Test parse failure + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_rejects_invalid_text(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_device *drm; + struct device *device; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + drm = kunit_kzalloc(test, sizeof(*drm), GFP_KERNEL); + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, device); + + connector->dev = drm; + dev_set_drvdata(device, connector); + + KUNIT_EXPECT_LT(test, panel_power_savings_store(device, NULL, "bad", 3), 0); +} + +/** + * dm_test_panel_power_savings_store_rejects_out_of_range - Test range failure + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_rejects_out_of_range(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_device *drm; + struct device *device; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + drm = kunit_kzalloc(test, sizeof(*drm), GFP_KERNEL); + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, device); + + connector->dev = drm; + dev_set_drvdata(device, connector); + + KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "5", 1), -EINVAL); +} + /* Tests for amdgpu_dm_backlight_get_device_index() */ /** @@ -1636,6 +1806,13 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_backlight_get_brightness_uses_device_index), /* amdgpu_dm_register_backlight_device */ KUNIT_CASE(dm_test_register_backlight_device_negative_index), + /* panel_power_savings_show / store */ + KUNIT_CASE(dm_test_panel_power_savings_show_maps_disable_to_zero), + KUNIT_CASE(dm_test_panel_power_savings_show_reports_level), + KUNIT_CASE(dm_test_panel_power_savings_store_sets_disable), + KUNIT_CASE(dm_test_panel_power_savings_store_forbidden), + KUNIT_CASE(dm_test_panel_power_savings_store_rejects_invalid_text), + KUNIT_CASE(dm_test_panel_power_savings_store_rejects_out_of_range), /* amdgpu_dm_backlight_get_device_index */ KUNIT_CASE(dm_test_backlight_device_index_matches_second), KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), From 9e8d7ca4cfde3ec44bb9e661534ea08e6cb89dc6 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 29 Jun 2026 18:45:19 -0600 Subject: [PATCH 085/155] drm/amd/display: Test backlight ABM property attach [WHAT] Cover the eDP success path of amdgpu_dm_setup_backlight_device() where the ABM level property is attached to the connector. A DRM-managed connector fixture (drmm_mode_config_init + drmm_connector_init) lets the test verify that one property is added and that its value is ABM_SYSFS_CONTROL. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../tests/amdgpu_dm_backlight_test.c | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index b74bbbc7f56f..f55f93747df7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -16,6 +16,7 @@ #include "dc.h" #include "dc_dmub_srv.h" #include "amdgpu.h" +#include "amdgpu_display.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_backlight.h" @@ -30,6 +31,12 @@ struct dm_backlight_connector_fixture { struct dc_link *link; }; +static const struct drm_connector_funcs dm_backlight_test_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + static void setup_test_connector(struct kunit *test, struct dm_backlight_connector_fixture *fixture, int bl_idx, enum signal_type signal) @@ -1787,6 +1794,61 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) amdgpu_dm_set_abm_level_param(saved_abm_level); } +/** + * dm_test_setup_backlight_device_attaches_abm_property - Test ABM property path + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_attaches_abm_property(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_display_manager *dm; + struct amdgpu_device *adev; + struct drm_property *prop; + struct dc_link *link; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + int saved_backlight = amdgpu_dm_get_backlight_param(); + int old_count; + int ret; + + amdgpu_dm_set_abm_level_param(-1); + amdgpu_dm_set_backlight_param(-1); + adev = dm_kunit_alloc_adev(test); + ret = drmm_mode_config_init(&adev->ddev); + KUNIT_ASSERT_EQ(test, ret, 0); + + prop = drm_property_create_range(&adev->ddev, 0, "abm level", 0, 4); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prop); + adev->mode_info.abm_level_property = prop; + + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + ret = drmm_connector_init(&adev->ddev, &aconnector->base, + &dm_backlight_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + link = dm_kunit_alloc_link(test); + link->connector_signal = SIGNAL_TYPE_EDP; + link->type = dc_connection_single; + aconnector->dc_link = link; + aconnector->bl_idx = -1; + dm = &adev->dm; + dm->adev = adev; + dm->ddev = &adev->ddev; + old_count = aconnector->base.base.properties->count; + + amdgpu_dm_setup_backlight_device(dm, aconnector); + + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 1); + KUNIT_EXPECT_EQ(test, aconnector->bl_idx, 0); + KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->count, old_count + 1); + KUNIT_EXPECT_PTR_EQ(test, aconnector->base.base.properties->properties[old_count], prop); + KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->values[old_count], + (uint64_t)ABM_SYSFS_CONTROL); + + amdgpu_dm_set_backlight_param(saved_backlight); + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + static struct kunit_case dm_backlight_test_cases[] = { /* dm_find_stream_with_link */ KUNIT_CASE(dm_test_find_stream_with_link_returns_match), @@ -1872,6 +1934,7 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_setup_backlight_device_connection_none), KUNIT_CASE(dm_test_setup_backlight_device_max_edps), KUNIT_CASE(dm_test_setup_backlight_device_oled_success), + KUNIT_CASE(dm_test_setup_backlight_device_attaches_abm_property), {} }; From 47c9c743ecc248641be6ad03a93307108d855bf0 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Fri, 26 Jun 2026 16:31:22 -0400 Subject: [PATCH 086/155] drm/amd/display: enable hdmistreamclk_rcg by default for dcn42 [why] enable hdmistreamclk_rcg by default Reviewed-by: Leo Chen Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c | 7 +++++-- drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c index 617f504c8e6b..5c9ef30b5416 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c @@ -1680,8 +1680,11 @@ void dccg35_set_hdmistreamclk_root_clock_gating(struct dccg *dccg, bool enable) { struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); - if (dccg->ctx->dc->debug.root_clock_optimization.bits.hdmistream) - REG_UPDATE(DCCG_GATE_DISABLE_CNTL6, HDMISTREAMCLK0_ROOT_GATE_DISABLE, enable ? 1 : 0); + if (!dccg->ctx->dc->debug.root_clock_optimization.bits.hdmistream && !enable) { + DC_LOG_DEBUG("%s: HDMISTREAMCLK0_ROOT_GATE DISABLE = 0 bypassed", __func__); + return; + } + REG_UPDATE(DCCG_GATE_DISABLE_CNTL6, HDMISTREAMCLK0_ROOT_GATE_DISABLE, enable ? 1 : 0); DC_LOG_DEBUG("%s: HDMISTREAMCLK0_ROOT_GATE_DISABLE = %d\n", __func__, enable ? 1 : 0); } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c index 01027d120cb0..f14c39a643da 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c @@ -487,7 +487,7 @@ void dcn35_update_odm(struct dc *dc, struct dc_state *context, struct pipe_ctx * void dcn35_dpp_root_clock_control(struct dce_hwseq *hws, unsigned int dpp_inst, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpp) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpp && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->dpp_root_clock_control) { @@ -498,7 +498,7 @@ void dcn35_dpp_root_clock_control(struct dce_hwseq *hws, unsigned int dpp_inst, void dcn35_dpstream_root_clock_control(struct dce_hwseq *hws, unsigned int dp_hpo_inst, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpstream) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpstream && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->set_dpstreamclk_root_clock_gating) { @@ -509,7 +509,7 @@ void dcn35_dpstream_root_clock_control(struct dce_hwseq *hws, unsigned int dp_hp void dcn35_hdmistream_root_clock_control(struct dce_hwseq *hws, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.hdmistream) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.hdmistream && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->set_hdmistreamclk_root_clock_gating) { @@ -520,7 +520,7 @@ void dcn35_hdmistream_root_clock_control(struct dce_hwseq *hws, bool clock_on) void dcn35_physymclk_root_clock_control(struct dce_hwseq *hws, unsigned int phy_inst, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.physymclk) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.physymclk && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->set_physymclk_root_clock_gating) { diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 7620da96ffc1..640a5a422f90 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -762,7 +762,7 @@ static const struct dc_debug_options debug_defaults_drv = { .bits = { .dpp = true, .dsc = true,/*dscclk and dsc pg*/ - .hdmistream = false, + .hdmistream = true, .hdmichar = true, .dpstream = true, .symclk32_se = true, From b23470a026afe64de5c1df4ad181587b49c22923 Mon Sep 17 00:00:00 2001 From: Nicholas Kazlauskas Date: Tue, 30 Jun 2026 11:07:49 -0400 Subject: [PATCH 087/155] drm/amd/display: Exit LS for ISHARP before coefficient write [Why] The hardware programming guide indicates that we must exit LS before any coefficient writes. This is true across all ASIC for this block. [How] Drop the ips_v2 checks and unconditionall perform the update. It is not based on IPS V2 support, which is set for DCN42, but rather the presence of the block itself. Reviewed-by: Charlene Liu Signed-off-by: Nicholas Kazlauskas Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/dc/dpp/dcn401/dcn401_dpp_dscl.c | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c index 62994aecf499..e0ee00c955bc 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c @@ -963,14 +963,13 @@ static void dpp401_dscl_program_isharp(struct dpp *dpp_base, PERF_TRACE(); /*power on isharp_delta_mem first*/ - if (dpp_base->ctx->dc->caps.ips_v2_support) { - /*HW default is LS, need to wake up*/ - REG_UPDATE_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, - ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, - ISHARP_DELTA_LUT_MEM_PWR_DIS, 1); - REG_WAIT(ISHARP_DELTA_LUT_MEM_PWR_CTRL, - ISHARP_DELTA_LUT_MEM_PWR_STATE, 0, 1, 100); - } + REG_UPDATE_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, + ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, + ISHARP_DELTA_LUT_MEM_PWR_DIS, 1); + + REG_WAIT(ISHARP_DELTA_LUT_MEM_PWR_CTRL, + ISHARP_DELTA_LUT_MEM_PWR_STATE, 0, 1, 100); + /* ISHARP_MODE */ REG_SET_6(ISHARP_MODE, 0, ISHARP_EN, scl_data->dscl_prog_data.isharp_en, @@ -1049,12 +1048,10 @@ static void dpp401_dscl_program_isharp(struct dpp *dpp_base, } /*power on isharp_delta_mem first*/ - if (dpp_base->ctx->dc->caps.ips_v2_support) { - /*HW default is LS, need to wake up*/ - REG_UPDATE_SEQ_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, - ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, - ISHARP_DELTA_LUT_MEM_PWR_DIS, 0); - } + REG_UPDATE_SEQ_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, + ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, + ISHARP_DELTA_LUT_MEM_PWR_DIS, 0); + PERF_TRACE(); } // dpp401_dscl_program_isharp /** From eff243192f707a079ceee35fa1b06cd3e1c0b0de Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 23 Jun 2026 14:41:26 -0400 Subject: [PATCH 088/155] drm/amd/display: Add MST link detection tests for connector Add KUnit coverage for amdgpu_dm_detect_mst_link_for_all_connectors() to the amdgpu_dm_connector tests: - No connectors registered: iteration body never runs and the call succeeds. - Writeback connector: hit by the early continue - Non-MST link - MST branch without aux: NULL mst_mgr.aux Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 1 + .../tests/amdgpu_dm_connector_test.c | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 82eb8b0bcc4b..6ef18b37b634 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -331,6 +331,7 @@ int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_detect_mst_link_for_all_connectors); static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index aa274f5e4b84..0dcc13e7f0af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -2772,6 +2772,130 @@ static void dm_test_fbc_init_no_modes(struct kunit *test) KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); } +/* Tests for amdgpu_dm_detect_mst_link_for_all_connectors() */ + +/* Allocate a bare drm_device suitable for registering connectors against. */ +static struct drm_device *dm_test_alloc_drm(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + return drm; +} + +/* + * Allocate an amdgpu_dm_connector and register its embedded drm_connector with + * @drm so that drm_for_each_connector_iter() and to_amdgpu_dm_connector() both + * resolve to it. + */ +static struct amdgpu_dm_connector *dm_test_add_connector(struct kunit *test, + struct drm_device *drm, int connector_type) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_ASSERT_EQ(test, + drmm_connector_init(drm, &aconnector->base, + &dm_test_connector_funcs, connector_type, + NULL), 0); + + return aconnector; +} + +/** + * dm_test_detect_mst_no_connectors - Test the no-op path on an empty device + * @test: The KUnit test context + */ +static void dm_test_detect_mst_no_connectors(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + /* No connectors registered → iteration body never runs */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/** + * dm_test_detect_mst_skips_writeback - Test writeback connectors are skipped + * @test: The KUnit test context + * + * A writeback connector is hit by the early ``continue`` before its dc_link is + * ever dereferenced, so leaving dc_link NULL must not crash. + */ +static void dm_test_detect_mst_skips_writeback(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_test_add_connector(test, drm, + DRM_MODE_CONNECTOR_WRITEBACK); + /* dc_link intentionally left NULL: it must not be touched */ + aconnector->dc_link = NULL; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/** + * dm_test_detect_mst_non_mst_link - Test a non-MST link starts no topology + * @test: The KUnit test context + */ +static void dm_test_detect_mst_non_mst_link(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + aconnector = dm_test_add_connector(test, drm, + DRM_MODE_CONNECTOR_DisplayPort); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Not an MST branch → the topology manager is never started */ + link->type = dc_connection_single; + aconnector->dc_link = link; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/** + * dm_test_detect_mst_branch_without_aux - Test an MST branch with no aux is + * skipped + * @test: The KUnit test context + * + * The condition short-circuits on a NULL mst_mgr.aux, so the real + * drm_dp_mst_topology_mgr_set_mst() path is never reached. + */ +static void dm_test_detect_mst_branch_without_aux(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + aconnector = dm_test_add_connector(test, drm, + DRM_MODE_CONNECTOR_DisplayPort); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + link->type = dc_connection_mst_branch; + aconnector->dc_link = link; + /* mst_mgr.aux is NULL (kzalloc) → second half of the && is false */ + KUNIT_ASSERT_NULL(test, aconnector->mst_mgr.aux); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2934,6 +3058,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_fbc_init_non_edp), KUNIT_CASE(dm_test_fbc_init_already_allocated), KUNIT_CASE(dm_test_fbc_init_no_modes), + /* amdgpu_dm_detect_mst_link_for_all_connectors */ + KUNIT_CASE(dm_test_detect_mst_no_connectors), + KUNIT_CASE(dm_test_detect_mst_skips_writeback), + KUNIT_CASE(dm_test_detect_mst_non_mst_link), + KUNIT_CASE(dm_test_detect_mst_branch_without_aux), {} }; From 1803ad56ab8f1055eabfa584f1d4a8d1dcc57dce Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 11:47:52 -0400 Subject: [PATCH 089/155] drm/amd/display: Add crtc matching and panel type tests for connector Add KUnit coverage for amdgpu_dm_find_first_crtc_matching_connector(): match, no match, empty state, skipping NULL connector slots, and returning the first match when several target the same crtc. Also add coverage for amdgpu_dm_set_panel_type(): VSDB OLED/miniLED, DPCD OLED/miniLED, Samsung miniLED above and below threshold, and the default LCD fallback. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 1 + .../display/amdgpu_dm/amdgpu_dm_connector.h | 1 + .../tests/amdgpu_dm_connector_test.c | 313 ++++++++++++++++++ 3 files changed, 315 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 6ef18b37b634..bee6eb19959f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -394,6 +394,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_find_first_crtc_matching_connector); STATIC_IFN_KUNIT void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 7d351587c42a..01f35bb55c93 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -149,6 +149,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); void update_subconnector_property(struct amdgpu_dm_connector *aconnector); void amdgpu_dm_fbc_init(struct drm_connector *connector); +void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 0dcc13e7f0af..cc41efaf5298 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -6,10 +6,14 @@ */ #include +#include #include #include +#include #include #include +#include +#include #include #include "dc.h" @@ -2896,6 +2900,305 @@ static void dm_test_detect_mst_branch_without_aux(struct kunit *test) amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); } +/* Tests for amdgpu_dm_find_first_crtc_matching_connector() */ + +/* + * Build a minimal drm_atomic_commit holding @count connector slots. The + * function under test only reads num_connector, connectors[i].ptr and + * connectors[i].new_state, so a hand-rolled state is sufficient. + */ +static struct drm_atomic_commit * +dm_test_alloc_atomic_state(struct kunit *test, int count) +{ + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->num_connector = count; + if (count) { + state->connectors = kunit_kcalloc(test, count, + sizeof(*state->connectors), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + } + + return state; +} + +/** + * dm_test_find_first_crtc_match - Test find_first_crtc returns matching connector + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_match(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 1); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + con_state->crtc = crtc; + state->connectors[0].ptr = connector; + state->connectors[0].new_state = con_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + connector); +} + +/** + * dm_test_find_first_crtc_no_match - Test find_first_crtc returns NULL when no crtc matches + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_no_match(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 1); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + struct drm_crtc *other_crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + other_crtc = kunit_kzalloc(test, sizeof(*other_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other_crtc); + + con_state->crtc = other_crtc; + state->connectors[0].ptr = connector; + state->connectors[0].new_state = con_state; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc)); +} + +/** + * dm_test_find_first_crtc_empty_state - Test find_first_crtc returns NULL with no connectors + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_empty_state(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 0); + struct drm_crtc *crtc; + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc)); +} + +/** + * dm_test_find_first_crtc_skips_null_ptr - Test find_first_crtc skips empty connector slots + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_skips_null_ptr(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 2); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + /* Slot 0 has no connector (ptr == NULL) and must be skipped. */ + con_state->crtc = crtc; + state->connectors[1].ptr = connector; + state->connectors[1].new_state = con_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + connector); +} + +/** + * dm_test_find_first_crtc_returns_first - Test find_first_crtc returns the first match + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_returns_first(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 2); + struct drm_connector *first; + struct drm_connector *second; + struct drm_connector_state *first_state; + struct drm_connector_state *second_state; + struct drm_crtc *crtc; + + first = kunit_kzalloc(test, sizeof(*first), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, first); + second = kunit_kzalloc(test, sizeof(*second), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, second); + first_state = kunit_kzalloc(test, sizeof(*first_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, first_state); + second_state = kunit_kzalloc(test, sizeof(*second_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, second_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + /* Both connectors target the same crtc; the first one must win. */ + first_state->crtc = crtc; + second_state->crtc = crtc; + state->connectors[0].ptr = first; + state->connectors[0].new_state = first_state; + state->connectors[1].ptr = second; + state->connectors[1].new_state = second_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + first); +} + +/* Tests for amdgpu_dm_set_panel_type() */ + +/* + * Build an amdgpu_dm_connector registered against a real kunit drm_device that + * is embedded in an amdgpu_device, so drm_to_adev()/adev_to_drm() resolve and + * the panel_type property can be created, attached and updated for real. + */ +struct dm_test_panel_ctx { + struct amdgpu_device *adev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct drm_display_info *display_info; +}; + +static struct dm_test_panel_ctx *dm_test_panel_ctx_alloc(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx; + struct drm_property *prop; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + ctx->adev = drm_to_adev(ctx->drm); + + /* The function under test writes through this property. */ + prop = drm_property_create_range(ctx->drm, DRM_MODE_PROP_IMMUTABLE, + "panel_type", 0, 0xff); + KUNIT_ASSERT_NOT_NULL(test, prop); + ctx->drm->mode_config.panel_type_property = prop; + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL), 0); + drm_object_attach_property(&ctx->aconnector->base.base, prop, + DRM_MODE_PANEL_TYPE_UNKNOWN); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->aconnector->dc_link = ctx->link; + + ctx->display_info = &ctx->aconnector->base.display_info; + + return ctx; +} + +static uint64_t dm_test_panel_prop_value(struct kunit *test, + struct dm_test_panel_ctx *ctx) +{ + uint64_t val = ~0ULL; + + KUNIT_EXPECT_EQ(test, + drm_object_property_get_value(&ctx->aconnector->base.base, + ctx->drm->mode_config.panel_type_property, &val), 0); + return val; +} + +/** + * dm_test_set_panel_type_samsung_miniled - Test Samsung luminance heuristic + * @test: The KUnit test context + * + * When no VSDB or DPCD hint is present, a Samsung sink whose first luminance + * range is at least 1.5x the second is treated as mini-LED. + */ +static void dm_test_set_panel_type_samsung_miniled(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + ctx->link->local_sink = sink; + + ctx->display_info->amd_vsdb.version = 1; + ctx->display_info->amd_vsdb.luminance_range1.max_luminance = 1500; + ctx->display_info->amd_vsdb.luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_samsung_below_threshold - Test Samsung sink below the + * mini-LED luminance threshold falls back to LCD + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_samsung_below_threshold(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + ctx->link->local_sink = sink; + + ctx->display_info->amd_vsdb.version = 1; + ctx->display_info->amd_vsdb.luminance_range1.max_luminance = 1000; + ctx->display_info->amd_vsdb.luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_LCD); +} + +/** + * dm_test_set_panel_type_default_lcd - Test default fallback is LCD + * @test: The KUnit test context + * + * With no VSDB, DPCD or DID hints the panel type defaults to LCD. + */ +static void dm_test_set_panel_type_default_lcd(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_LCD); + KUNIT_EXPECT_EQ(test, dm_test_panel_prop_value(test, ctx), + (uint64_t)DRM_MODE_PANEL_TYPE_LCD); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -3063,6 +3366,16 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_detect_mst_skips_writeback), KUNIT_CASE(dm_test_detect_mst_non_mst_link), KUNIT_CASE(dm_test_detect_mst_branch_without_aux), + /* amdgpu_dm_find_first_crtc_matching_connector */ + KUNIT_CASE(dm_test_find_first_crtc_match), + KUNIT_CASE(dm_test_find_first_crtc_no_match), + KUNIT_CASE(dm_test_find_first_crtc_empty_state), + KUNIT_CASE(dm_test_find_first_crtc_skips_null_ptr), + KUNIT_CASE(dm_test_find_first_crtc_returns_first), + /* amdgpu_dm_set_panel_type */ + KUNIT_CASE(dm_test_set_panel_type_samsung_miniled), + KUNIT_CASE(dm_test_set_panel_type_samsung_below_threshold), + KUNIT_CASE(dm_test_set_panel_type_default_lcd), {} }; From 0eec0ebf820db3c1a160ce41c29a29f224e4ec75 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 14:12:05 -0400 Subject: [PATCH 090/155] drm/amd/display: Add CACP caps tests for connector Add KUnit coverage for amdgpu_dm_update_cacp_caps(): eDP and LVDS supported, old/3.1.6 IP versions unsupported, non-eDP/LVDS and LCD panels unsupported. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.h | 1 + .../tests/amdgpu_dm_connector_test.c | 149 ++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 01f35bb55c93..c5f0bda5f16e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -150,6 +150,7 @@ enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); void update_subconnector_property(struct amdgpu_dm_connector *aconnector); void amdgpu_dm_fbc_init(struct drm_connector *connector); void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index cc41efaf5298..69949f76d64f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -3199,6 +3199,148 @@ static void dm_test_set_panel_type_default_lcd(struct kunit *test) (uint64_t)DRM_MODE_PANEL_TYPE_LCD); } +/* Tests for amdgpu_dm_update_cacp_caps() */ + +/* + * Build an amdgpu_dm_connector wired to a real kunit drm_device embedded in an + * amdgpu_device, so drm_to_adev() resolves and drm_dbg_kms() has a valid + * device. Defaults are seeded to the fully-supported configuration so each + * test only flips a single knob. + */ +struct dm_test_cacp_ctx { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static struct dm_test_cacp_ctx *dm_test_cacp_ctx_alloc(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx; + struct drm_device *drm; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + ctx->adev = drm_to_adev(drm); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->aconnector->base.dev = drm; + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->aconnector->dc_link = ctx->link; + + /* Fully-supported defaults: new enough DCE, eDP, non-LCD panel. */ + ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + ctx->link->connector_signal = SIGNAL_TYPE_EDP; + ctx->link->panel_type = PANEL_TYPE_OLED; + + return ctx; +} + +/** + * dm_test_cacp_caps_edp_supported - Test CACP supported on a new eDP panel + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_edp_supported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_TRUE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lvds_supported - Test CACP supported on an LVDS panel + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_lvds_supported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->link->connector_signal = SIGNAL_TYPE_LVDS; + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_TRUE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_old_ip_unsupported - Test CACP unsupported on old DCE + * @test: The KUnit test context + * + * DCE versions older than 3.1.4 do not support CACP. + */ +static void dm_test_cacp_caps_old_ip_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 3); + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_ip_3_1_6_unsupported - Test CACP unsupported on DCE 3.1.6 + * @test: The KUnit test context + * + * DCE 3.1.6 is explicitly excluded even though it is newer than 3.1.4. + */ +static void dm_test_cacp_caps_ip_3_1_6_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 6); + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_non_edp_lvds_unsupported - Test CACP unsupported on a + * non-eDP/LVDS signal + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_non_edp_lvds_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lcd_unsupported - Test CACP unsupported on an LCD panel + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_lcd_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->link->panel_type = PANEL_TYPE_LCD; + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -3376,6 +3518,13 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_set_panel_type_samsung_miniled), KUNIT_CASE(dm_test_set_panel_type_samsung_below_threshold), KUNIT_CASE(dm_test_set_panel_type_default_lcd), + /* amdgpu_dm_update_cacp_caps */ + KUNIT_CASE(dm_test_cacp_caps_edp_supported), + KUNIT_CASE(dm_test_cacp_caps_lvds_supported), + KUNIT_CASE(dm_test_cacp_caps_old_ip_unsupported), + KUNIT_CASE(dm_test_cacp_caps_ip_3_1_6_unsupported), + KUNIT_CASE(dm_test_cacp_caps_non_edp_lvds_unsupported), + KUNIT_CASE(dm_test_cacp_caps_lcd_unsupported), {} }; From 66251e0d45dafe5c119d79523c07d849a9444dc7 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 14:36:05 -0400 Subject: [PATCH 091/155] drm/amd/display: Add stream property tests for connector Add KUnit coverage for fill_stream_properties_from_drm_display_mode(): zeroed borders, RGB defaults, sync polarity handling, inheriting from an old stream, timing from the crtc, requested bpc colour depth, content type and aspect ratio. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 3 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 7 + .../tests/amdgpu_dm_connector_test.c | 273 ++++++++++++++++++ 3 files changed, 282 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index bee6eb19959f..173793419da2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -835,7 +835,7 @@ STATIC_IFN_KUNIT bool adjust_colour_depth_from_display_info( } EXPORT_IF_KUNIT(adjust_colour_depth_from_display_info); -static void fill_stream_properties_from_drm_display_mode( +STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode( struct dc_stream_state *stream, const struct drm_display_mode *mode_in, const struct drm_connector *connector, @@ -975,6 +975,7 @@ static void fill_stream_properties_from_drm_display_mode( stream->output_color_space = amdgpu_dm_get_output_color_space(timing_out, connector_state); stream->content_type = get_output_content_type(connector_state); } +EXPORT_IF_KUNIT(fill_stream_properties_from_drm_display_mode); STATIC_IFN_KUNIT void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index c5f0bda5f16e..60f7ab63b782 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -151,6 +151,13 @@ void update_subconnector_property(struct amdgpu_dm_connector *aconnector); void amdgpu_dm_fbc_init(struct drm_connector *connector); void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector); +void fill_stream_properties_from_drm_display_mode( + struct dc_stream_state *stream, + const struct drm_display_mode *mode_in, + const struct drm_connector *connector, + const struct drm_connector_state *connector_state, + const struct dc_stream_state *old_stream, + int requested_bpc); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 69949f76d64f..0a5d439f66c3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -3341,6 +3341,269 @@ static void dm_test_cacp_caps_lcd_unsupported(struct kunit *test) KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); } +/* Tests for fill_stream_properties_from_drm_display_mode() */ + +/* + * Build the inputs for fill_stream_properties_from_drm_display_mode(). The + * connector is registered against a real kunit drm_device so that + * to_amdgpu_dm_connector(), connector->display_info and the drm debug helpers + * all resolve. Large structs are heap-allocated to keep the stack small. + * + * The stream signal defaults to DisplayPort so the HDMI infoframe paths are + * skipped, keeping the exercised behaviour deterministic. + */ +struct dm_test_fill_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *conn_state; + struct dc_stream_state *stream; + struct drm_display_mode *mode; +}; + +static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL), 0); + + ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->stream); + ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->mode); + + ctx->stream->signal = SIGNAL_TYPE_DISPLAY_PORT; + + return ctx; +} + +/** + * dm_test_fill_stream_borders_zeroed - Test the timing borders are cleared + * @test: The KUnit test context + */ +static void dm_test_fill_stream_borders_zeroed(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + /* Pre-seed nonzero borders to prove they get reset. */ + timing->h_border_left = 5; + timing->h_border_right = 6; + timing->v_border_top = 7; + timing->v_border_bottom = 8; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0); + KUNIT_EXPECT_EQ(test, (int)timing->h_border_right, 0); + KUNIT_EXPECT_EQ(test, (int)timing->v_border_top, 0); + KUNIT_EXPECT_EQ(test, (int)timing->v_border_bottom, 0); +} + +/** + * dm_test_fill_stream_rgb_defaults - Test the default RGB/sRGB output + * @test: The KUnit test context + * + * A plain DisplayPort sink with no YCbCr color formats produces RGB encoding + * and the sRGB color space, with a predefined sRGB transfer function. + */ +static void dm_test_fill_stream_rgb_defaults(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB); + KUNIT_EXPECT_EQ(test, (int)timing->timing_3d_format, + (int)TIMING_3D_FORMAT_NONE); + KUNIT_EXPECT_EQ(test, (int)timing->scan_type, (int)SCANNING_TYPE_NODATA); + KUNIT_EXPECT_EQ(test, (int)ctx->stream->output_color_space, + (int)COLOR_SPACE_SRGB); + KUNIT_EXPECT_EQ(test, (int)ctx->stream->out_transfer_func.type, + (int)TF_TYPE_PREDEFINED); + KUNIT_EXPECT_EQ(test, (int)ctx->stream->out_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_fill_stream_sync_polarity_positive - Test sync polarity from mode flags + * @test: The KUnit test context + */ +static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1); + KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 1); +} + +/** + * dm_test_fill_stream_sync_polarity_negative - Test negative sync polarity default + * @test: The KUnit test context + */ +static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + /* No sync flags set on the mode → polarity stays negative (0). */ + ctx->mode->flags = 0; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0); + KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0); +} + +/** + * dm_test_fill_stream_inherits_old_stream - Test vic/polarity copied from old stream + * @test: The KUnit test context + * + * When an old stream is supplied its vic and sync polarities are reused instead + * of being derived from the mode. + */ +static void dm_test_fill_stream_inherits_old_stream(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + struct dc_stream_state *old_stream; + + old_stream = kunit_kzalloc(test, sizeof(*old_stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_stream); + old_stream->timing.vic = 16; + old_stream->timing.flags.HSYNC_POSITIVE_POLARITY = 1; + old_stream->timing.flags.VSYNC_POSITIVE_POLARITY = 0; + + /* Mode flags would force positive polarity if the old stream were ignored. */ + ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, old_stream, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->vic, 16); + KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1); + KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0); +} + +/** + * dm_test_fill_stream_timing_from_crtc - Test timing taken from crtc_* fields + * @test: The KUnit test context + * + * Without a freesync video match the function uses the mode's crtc_* timing + * fields and scales the pixel clock to 100Hz units. + */ +static void dm_test_fill_stream_timing_from_crtc(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->mode->crtc_hdisplay = 1920; + ctx->mode->crtc_htotal = 2200; + ctx->mode->crtc_hsync_start = 2008; + ctx->mode->crtc_hsync_end = 2052; + ctx->mode->crtc_vdisplay = 1080; + ctx->mode->crtc_vtotal = 1125; + ctx->mode->crtc_vsync_start = 1084; + ctx->mode->crtc_vsync_end = 1089; + ctx->mode->crtc_clock = 148500; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920); + KUNIT_EXPECT_EQ(test, (int)timing->h_total, 2200); + KUNIT_EXPECT_EQ(test, (int)timing->h_sync_width, 44); + KUNIT_EXPECT_EQ(test, (int)timing->h_front_porch, 88); + KUNIT_EXPECT_EQ(test, (int)timing->v_addressable, 1080); + KUNIT_EXPECT_EQ(test, (int)timing->v_total, 1125); + KUNIT_EXPECT_EQ(test, (int)timing->v_sync_width, 5); + KUNIT_EXPECT_EQ(test, (int)timing->v_front_porch, 4); + KUNIT_EXPECT_EQ(test, (int)timing->pix_clk_100hz, 1485000); +} + +/** + * dm_test_fill_stream_color_depth_requested_bpc - Test bpc capping + * @test: The KUnit test context + * + * The requested bpc caps the display bpc and is rounded down to an even value. + */ +static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->aconnector->base.display_info.bpc = 12; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 10); + + KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth, + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_fill_stream_content_type - Test content type forwarded from state + * @test: The KUnit test context + */ +static void dm_test_fill_stream_content_type(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + + ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type, + (int)DISPLAY_CONTENT_TYPE_GRAPHICS); +} + +/** + * dm_test_fill_stream_aspect_ratio - Test aspect ratio mapped from the mode + * @test: The KUnit test context + */ +static void dm_test_fill_stream_aspect_ratio(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio, + (int)ASPECT_RATIO_16_9); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -3525,6 +3788,16 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_cacp_caps_ip_3_1_6_unsupported), KUNIT_CASE(dm_test_cacp_caps_non_edp_lvds_unsupported), KUNIT_CASE(dm_test_cacp_caps_lcd_unsupported), + /* fill_stream_properties_from_drm_display_mode */ + KUNIT_CASE(dm_test_fill_stream_borders_zeroed), + KUNIT_CASE(dm_test_fill_stream_rgb_defaults), + KUNIT_CASE(dm_test_fill_stream_sync_polarity_positive), + KUNIT_CASE(dm_test_fill_stream_sync_polarity_negative), + KUNIT_CASE(dm_test_fill_stream_inherits_old_stream), + KUNIT_CASE(dm_test_fill_stream_timing_from_crtc), + KUNIT_CASE(dm_test_fill_stream_color_depth_requested_bpc), + KUNIT_CASE(dm_test_fill_stream_content_type), + KUNIT_CASE(dm_test_fill_stream_aspect_ratio), {} }; From 1f07e9fddc4a29a71a935fc20b7f894767327314 Mon Sep 17 00:00:00 2001 From: Karthi Kandasamy Date: Thu, 28 May 2026 12:49:44 +0200 Subject: [PATCH 092/155] drm/amd/display: Fix DMSS not triggering for HDR to SDR transition Why: DMSS feature was not getting triggered during HDR to SDR transitions, and no DPCD write was observed. Root cause analysis revealed that incorrect panel capabilities were being reported for PSR SU panels. Due to the wrong capabilities, the OS was not invoking the DMSS API, resulting in no DPCD communication and also gate eDP teardown across DMSS hold How: Fixed by setting the correct power panel capabilities for PSR SU panels. This ensures the OS receives accurate panel capability information and triggers the DMSS API as expected, restoring proper DPCD writes during HDR to SDR transitions. The DC commit sequence,was tearing the eDP down anyway -- backlight off, ABM disable, DPMS off, PSR/Replay feature_enabled cleared, PHY TX off, OTG/OPTC off all these actions are blocked now with the skip_implict_edp_power_control Reviewed-by: Aric Cyr Signed-off-by: Karthi Kandasamy Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/hwss/dce110/dce110_hwseq.c | 12 +++++++--- .../amd/display/dc/hwss/dcn31/dcn31_hwseq.c | 21 ++++++++++++----- .../amd/display/dc/hwss/dcn35/dcn35_hwseq.c | 7 ++++-- .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 23 ++++++++++++++----- .../drm/amd/display/modules/power/power_psr.c | 7 ++++++ .../amd/display/modules/power/power_replay.c | 7 ++++++ 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index c9691974bf72..cce4f3065575 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -1287,7 +1287,9 @@ void dce110_blank_stream(struct pipe_ctx *pipe_ctx) return; if (link->local_sink && link->local_sink->sink_signal == SIGNAL_TYPE_EDP) { - if (!link->skip_implict_edp_power_control && hws) + if (link->skip_implict_edp_power_control) + return; + if (hws) hws->funcs.edp_backlight_control(link, false); link->dc->hwss.set_abm_immediate_disable(pipe_ctx); } @@ -1793,7 +1795,9 @@ enum dc_status dce110_apply_single_controller_ctx_to_hw( dc->link_srv->set_dsc_enable(pipe_ctx, true); } - if (!stream->dpms_off) + if (!stream->dpms_off && + !(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) dc->link_srv->set_dpms_on(context, pipe_ctx); /* DCN3.1 FPGA Workaround @@ -1812,7 +1816,9 @@ enum dc_status dce110_apply_single_controller_ctx_to_hw( * is constructed with the same sink). Make sure not to override * and link programming on the main. */ - if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM) { + if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM && + !(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { pipe_ctx->stream->link->psr_settings.psr_feature_enabled = false; pipe_ctx->stream->link->replay_settings.replay_feature_enabled = false; } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index b6f3c0480ab6..6f1ad651ed2c 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c @@ -537,10 +537,12 @@ static void dcn31_reset_back_end_for_pipe( } ASSERT(!pipe_ctx->top_pipe); - dc->hwss.set_abm_immediate_disable(pipe_ctx); - link = pipe_ctx->stream->link; + if (!(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) + dc->hwss.set_abm_immediate_disable(pipe_ctx); + if (dc->hwseq) dc->hwseq->wa_state.skip_blank_stream = false; @@ -555,9 +557,11 @@ static void dcn31_reset_back_end_for_pipe( pipe_ctx->stream_res.tg, OPTC_DSC_DISABLED, 0, 0); - pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); - - pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + if (!(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { + pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); + pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + } if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass) pipe_ctx->stream_res.tg->funcs->set_odm_bypass( pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing); @@ -586,7 +590,12 @@ static void dcn31_reset_back_end_for_pipe( * screen only, the dpms_off would be true but * VBIOS lit up eDP, so check link status too. */ - if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) + if (link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control) { + /* DMSS is holding the panel across the commit; skip dpms-off. */ + if (pipe_ctx->stream_res.audio) + dc->hwss.disable_audio_stream(pipe_ctx); + } else if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) dc->link_srv->set_dpms_off(pipe_ctx); else if (pipe_ctx->stream_res.audio) dc->hwss.disable_audio_stream(pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c index f14c39a643da..4ed7480d1efa 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c @@ -1818,8 +1818,11 @@ void dcn35_disable_link_output(struct dc_link *link, disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING); link->phy_state.symclk_state = SYMCLK_ON_TX_OFF; } else { - link_hwss->disable_link_output(link, link_res, signal); - link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + if (!(signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { + link_hwss->disable_link_output(link, link_res, signal); + link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + } } /* * Add the logic to extract BOTH power up and power down sequences diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 0336d118e77e..d8d3208fc1af 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -1101,8 +1101,11 @@ void dcn401_disable_link_output(struct dc_link *link, disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING); link->phy_state.symclk_state = SYMCLK_ON_TX_OFF; } else { - link_hwss->disable_link_output(link, link_res, signal); - link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + if (!(signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { + link_hwss->disable_link_output(link, link_res, signal); + link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + } } if (signal == SIGNAL_TYPE_EDP && @@ -2150,7 +2153,12 @@ void dcn401_reset_back_end_for_pipe( * screen only, the dpms_off would be true but * VBIOS lit up eDP, so check link status too. */ - if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) + if (link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control) { + /* DMSS is holding the panel across the commit; skip dpms-off. */ + if (pipe_ctx->stream_res.audio) + dc->hwss.disable_audio_stream(pipe_ctx); + } else if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) dc->link_srv->set_dpms_off(pipe_ctx); else if (pipe_ctx->stream_res.audio) dc->hwss.disable_audio_stream(pipe_ctx); @@ -2175,12 +2183,15 @@ void dcn401_reset_back_end_for_pipe( * parent pipe. */ if (pipe_ctx->top_pipe == NULL) { + if (!(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { - dc->hwss.set_abm_immediate_disable(pipe_ctx); + dc->hwss.set_abm_immediate_disable(pipe_ctx); - pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); + pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); - pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + } if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass) pipe_ctx->stream_res.tg->funcs->set_odm_bypass( pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing); diff --git a/drivers/gpu/drm/amd/display/modules/power/power_psr.c b/drivers/gpu/drm/amd/display/modules/power/power_psr.c index 5ecb570c204e..0ad4c4924696 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_psr.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_psr.c @@ -58,6 +58,13 @@ bool mod_power_psr_notify_mode_change(struct mod_power *mod_power, // stream_index is passed as validated parameter active_psr_events = core_power->map[stream_index].psr_events; + /* DMSS holds the panel in a forced PSR freeze (e.g. during HDR/SDR toggle). + * Re-running edp_setup_psr would reprogram DPCD 0x170 and disturb the freeze, + * so skip the PSR re-setup until DMSS releases the override. + */ + if (active_psr_events & psr_event_os_override_hold) + return false; + /* Calculate PSR configurations */ mod_power_calc_psr_configs(&psr_config, link, stream); diff --git a/drivers/gpu/drm/amd/display/modules/power/power_replay.c b/drivers/gpu/drm/amd/display/modules/power/power_replay.c index e782501442c4..1ad2ee01d560 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_replay.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_replay.c @@ -805,6 +805,13 @@ void mod_power_replay_notify_mode_change(struct mod_power *mod_power, core_power = MOD_POWER_TO_CORE(mod_power); active_replay_events = core_power->map[stream_index].replay_events; + /* DMSS holds the panel in a forced freeze (e.g. during HDR/SDR toggle). + * Re-running dp_setup_replay would reprogram DPCD 0x37B and disturb the + * freeze, so skip the replay re-setup until DMSS releases the override. + */ + if (active_replay_events & replay_event_os_override_hold) + return; + link->replay_settings.replay_smu_opt_enable = (link->replay_settings.config.replay_smu_opt_supported && mod_power_only_edp(dc->current_state, stream)); From 198663d035cc439eb48844a2da66f6ae1b0de303 Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Fri, 3 Jul 2026 10:08:37 -0400 Subject: [PATCH 093/155] drm/amd/display: fix dcn42 det allocation order set_pipe_unlock_order needs to be set to true for the pipes to be unlocked in correct order to avoid det overallocation Reviewed-by: Taimur Hassan Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 640a5a422f90..e40e246bcc82 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -2145,6 +2145,7 @@ static bool dcn42_resource_construct( dc->config.use_pipe_ctx_sync_logic = true; dc->config.dc_mode_clk_limit_support = false; dc->config.enable_windowed_mpo_odm = true; + dc->config.set_pipe_unlock_order = true; /* Need to ensure DET gets freed before allocating */ /* Use psp mailbox to enable assr */ dc->config.use_assr_psp_message = true; /* dcn42 and afterward always support external panel replay */ From 4fb6f596f94c0959ff6a566b99be1ea7f6443985 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 29 Jun 2026 15:02:40 -0400 Subject: [PATCH 094/155] drm/amd/display: Enable HUBP/DPP Driver PG for DCN42 [Why & How] The related corruption issue is now resolved, HUBP/DPP driver PG can be enabled for DCN42. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Leo Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index e40e246bcc82..547a0b816539 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -729,8 +729,8 @@ static const struct dc_debug_options debug_defaults_drv = { .clock_trace = true, .disable_pplib_clock_request = false, .ignore_pg = false, - .disable_dpp_power_gate = true, - .disable_hubp_power_gate = true, + .disable_dpp_power_gate = false, + .disable_hubp_power_gate = false, .disable_optc_power_gate = true, .disable_dsc_power_gate = false, .disable_dio_power_gate = true, From 183bbded999a70c5996e8f399fa8790568d71112 Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Fri, 3 Jul 2026 13:01:12 -0400 Subject: [PATCH 095/155] drm/amd/display: fix dcn42b det allocation order set_pipe_unlock_order needs to be set to true for the pipes to be unlocked in correct order to avoid det overallocation Reviewed-by: Charlene Liu Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index c555be6e541d..343e10b4e096 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -2119,6 +2119,7 @@ static bool dcn42b_resource_construct( dc->config.use_pipe_ctx_sync_logic = true; dc->config.dc_mode_clk_limit_support = false; dc->config.enable_windowed_mpo_odm = true; + dc->config.set_pipe_unlock_order = true; /* Need to ensure DET gets freed before allocating */ /* Use psp mailbox to enable assr */ dc->config.use_assr_psp_message = true; /* dcn42 and afterward always support external panel replay */ From 1bf0b71e989f8f67a91ba593621c6862eccb67d4 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Thu, 25 Jun 2026 15:06:44 -0400 Subject: [PATCH 096/155] drm/amd/display: add DST_Y_DELTA_DRQ_LIMIT reg macro for later use [why] DST_Y_DELTA_DRQ_LIMIT from dml2.1 Reviewed-by: Leo Chen Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h | 3 ++- drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h | 2 ++ drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h b/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h index 80be394a8852..2595c6441f00 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h @@ -286,7 +286,8 @@ type MCACHEID_MALL_PREF_2H_P0;\ type MCACHEID_MALL_PREF_1H_P1;\ type MCACHEID_MALL_PREF_2H_P1;\ - type HUBP_FGCG_REP_DIS + type HUBP_FGCG_REP_DIS;\ + type DST_Y_DELTA_DRQ_LIMIT #define DCN42_HUBP_REG_FIELD_VARIABLE_LIST(type) \ type HUBP_3DLUT_CROSSBAR_SEL_G;\ diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h index fc30afd4c912..27528f82058c 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h @@ -35,6 +35,7 @@ HUBP_SF(HUBP0_3DLUT_FL_CONFIG, HUBP0_3DLUT_FL_FORMAT, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_BIAS, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_SCALE, mask_sh),\ + HUBP_SF(HUBPREQ0_DST_Y_DELTA_DRQ_LIMIT, DST_Y_DELTA_DRQ_LIMIT, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ENABLE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_DONE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ADDRESSING_MODE, mask_sh),\ @@ -54,6 +55,7 @@ HUBP_SF(HUBP0_3DLUT_FL_CONFIG, HUBP0_3DLUT_FL_FORMAT, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_BIAS, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_SCALE, mask_sh),\ + HUBP_SF(HUBPREQ0_DST_Y_DELTA_DRQ_LIMIT, DST_Y_DELTA_DRQ_LIMIT, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ENABLE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_DONE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ADDRESSING_MODE, mask_sh),\ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h index 3e4d9b188b26..f2b98f1574ed 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h @@ -581,6 +581,7 @@ SRI_ARR(DCHUBP_VMPG_CONFIG, HUBP, id), \ SRI_ARR(UCLK_PSTATE_FORCE, HUBPREQ, id), \ SRI_ARR(HUBP_3DLUT_DLG_PARAM, CURSOR0_, id), \ + SRI_ARR(DST_Y_DELTA_DRQ_LIMIT, HUBPREQ, id), \ HUBP_3DLUT_FL_REG_LIST_DCN401(id) struct dcn42_resource_pool { struct resource_pool base; From f2a400978d589456ad0134656bfa0e8110e5e49a Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Fri, 3 Jul 2026 13:45:47 -0400 Subject: [PATCH 097/155] drm/amd/display: add DST_Y_DELTA_DRQ_LIMIT hw programming [why] dchubp needs to program DST_Y_DELTA_DRQ_LIMIT based on dml2.x Reviewed-by: Aric Cyr Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/hubp/dcn42/dcn42_hubp.c | 51 ++++++++++++++++++- .../amd/display/dc/hubp/dcn42/dcn42_hubp.h | 3 ++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c index 57de98444f6c..94730f3cff42 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c @@ -580,7 +580,56 @@ static bool hubp42_program_surface_flip_and_addr( return true; } +void hubp42_setup_interdependent( + struct hubp *hubp, + struct dml2_dchub_per_pipe_register_set *pipe_regs) +{ + struct dcn20_hubp *hubp2 = TO_DCN20_HUBP(hubp); + REG_SET_2(PREFETCH_SETTINGS, 0, + DST_Y_PREFETCH, pipe_regs->dlg_regs.dst_y_prefetch, + VRATIO_PREFETCH, pipe_regs->dlg_regs.vratio_prefetch); + + REG_SET(PREFETCH_SETTINGS_C, 0, + VRATIO_PREFETCH_C, pipe_regs->dlg_regs.vratio_prefetch_c); + + REG_SET_2(VBLANK_PARAMETERS_0, 0, + DST_Y_PER_VM_VBLANK, pipe_regs->dlg_regs.dst_y_per_vm_vblank, + DST_Y_PER_ROW_VBLANK, pipe_regs->dlg_regs.dst_y_per_row_vblank); + + REG_SET_2(FLIP_PARAMETERS_0, 0, + DST_Y_PER_VM_FLIP, pipe_regs->dlg_regs.dst_y_per_vm_flip, + DST_Y_PER_ROW_FLIP, pipe_regs->dlg_regs.dst_y_per_row_flip); + + REG_SET(VBLANK_PARAMETERS_3, 0, + REFCYC_PER_META_CHUNK_VBLANK_L, pipe_regs->dlg_regs.refcyc_per_meta_chunk_vblank_l); + + REG_SET(VBLANK_PARAMETERS_4, 0, + REFCYC_PER_META_CHUNK_VBLANK_C, pipe_regs->dlg_regs.refcyc_per_meta_chunk_vblank_c); + + REG_SET(FLIP_PARAMETERS_2, 0, + REFCYC_PER_META_CHUNK_FLIP_L, pipe_regs->dlg_regs.refcyc_per_meta_chunk_flip_l); + + REG_SET_2(PER_LINE_DELIVERY_PRE, 0, + REFCYC_PER_LINE_DELIVERY_PRE_L, pipe_regs->dlg_regs.refcyc_per_line_delivery_pre_l, + REFCYC_PER_LINE_DELIVERY_PRE_C, pipe_regs->dlg_regs.refcyc_per_line_delivery_pre_c); + + REG_SET(DCN_SURF0_TTU_CNTL1, 0, + REFCYC_PER_REQ_DELIVERY_PRE, + pipe_regs->ttu_regs.refcyc_per_req_delivery_pre_l); + REG_SET(DCN_SURF1_TTU_CNTL1, 0, + REFCYC_PER_REQ_DELIVERY_PRE, + pipe_regs->ttu_regs.refcyc_per_req_delivery_pre_c); + REG_SET(DCN_CUR0_TTU_CNTL1, 0, + REFCYC_PER_REQ_DELIVERY_PRE, pipe_regs->ttu_regs.refcyc_per_req_delivery_pre_cur0); + + REG_SET_2(DCN_GLOBAL_TTU_CNTL, 0, + MIN_TTU_VBLANK, pipe_regs->ttu_regs.min_ttu_vblank, + QoS_LEVEL_FLIP, pipe_regs->ttu_regs.qos_level_flip); + + REG_SET(DST_Y_DELTA_DRQ_LIMIT, 0, + DST_Y_DELTA_DRQ_LIMIT, pipe_regs->dlg_regs.dst_y_delta_drq_limit); +} struct hubp_funcs dcn42_hubp_funcs = { .hubp_enable_tripleBuffer = hubp2_enable_triplebuffer, .hubp_is_triplebuffer_enabled = hubp2_is_triplebuffer_enabled, @@ -588,7 +637,7 @@ struct hubp_funcs dcn42_hubp_funcs = { .hubp_program_surface_config = hubp42_program_surface_config, .hubp_is_flip_pending = hubp2_is_flip_pending, .hubp_setup2 = hubp42_setup, - .hubp_setup_interdependent2 = hubp401_setup_interdependent, + .hubp_setup_interdependent2 = hubp42_setup_interdependent, .hubp_set_vm_system_aperture_settings = hubp3_set_vm_system_aperture_settings, .set_blank = hubp2_set_blank, .set_blank_regs = hubp2_set_blank_regs, diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h index 27528f82058c..ba4a58c353dd 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h @@ -97,4 +97,7 @@ void hubp42_setup( union dml2_global_sync_programming *pipe_global_sync, struct dc_crtc_timing *timing); +void hubp42_setup_interdependent( + struct hubp *hubp, + struct dml2_dchub_per_pipe_register_set *pipe_regs); #endif /* __DC_HUBP_DCN42_H__ */ From 163b0b32bfbeed40390c03e2a192b802aefd9f54 Mon Sep 17 00:00:00 2001 From: James Lin Date: Tue, 7 Jul 2026 14:04:10 +0800 Subject: [PATCH 098/155] drm/amd/display: don't re-evaluate cursor mode on cursor plane movement [Why] The cursor-mode re-evaluation added to dm_crtc_get_cursor_mode() also checks the cursor plane's own destination rectangle. The crtc_x/y/w/h check lives in the per-plane loop, which only flags the cursor plane via cursor_changed and does not skip it, so the check fires whenever the cursor itself moves. flip-vs-cursor-legacy (kms_cursor_legacy) issues a stream of legacy cursor moves and requires each to land on the fast path within a single vblank. With the position check, every cursor move now sets consider_mode_change and defeats the early return, forcing the full cursor-mode evaluation. A legacy cursor move is a cursor-only commit: the underlying planes are not in the atomic state, so the coverage loop (for_each_oldnew_plane_in_descending_zpos walks only planes in the state) sees nothing covering the CRTC, evaluates entire_crtc_covered as false, and misclassifies the cursor mode as OVERLAY on a fully-covered screen. That spurious NATIVE->OVERLAY transition makes should_reset_plane() return true (lock_and_validation_needed), pulls all CRTC planes into the commit, and - because amdgpu_dm_plane_atomic_async_check() rejects async updates in overlay mode - permanently knocks later cursor updates off the async fast path. Each cursor move becomes a full atomic commit with DC global validation, serialized against the page flip, so cursor updates no longer fit in one vblank and flip-vs-cursor-legacy fails / times out [How] The cursor mode is a function of the underlying planes' coverage and properties, not of the cursor's position, so cursor movement can never change the correct mode. Restrict the destination-rectangle check to non-cursor planes. A move/resize of an underlying plane (the case the original change targets, e.g. amd_cursor_overlay@non-full) still re-evaluates the mode correctly, while pure cursor movement returns to the fast path. Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 65e7d49a5f93..a5961e19bf8c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6670,15 +6670,26 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, } /* - * A plane moving or resizing (without a scale change) changes how - * much of the CRTC it covers. This can create/remove holes under - * the cursor and thus flip the required cursor mode (native vs - * overlay), so the destination rect must be re-evaluated too. + * A non-cursor plane moving or resizing (without a scale change) + * changes how much of the CRTC it covers. This can create or + * remove a hole under the cursor and thus flip the required + * cursor mode (native vs overlay), so its destination rect must + * be re-evaluated too. + * + * The cursor plane itself is deliberately excluded: the cursor + * mode depends on the underlying planes' coverage, not on the + * cursor's position (see the entire_crtc_covered logic below). + * Triggering on cursor movement would force every legacy cursor + * update off its fast path, and in a cursor-only commit - where + * the underlying planes are not part of the state - the coverage + * loop would see no covering plane and misevaluate the mode as + * overlay, regressing flip-vs-cursor-legacy. */ - if (old_plane_state->crtc_x != plane_state->crtc_x || - old_plane_state->crtc_y != plane_state->crtc_y || - old_plane_state->crtc_w != plane_state->crtc_w || - old_plane_state->crtc_h != plane_state->crtc_h) { + if (plane->type != DRM_PLANE_TYPE_CURSOR && + (old_plane_state->crtc_x != plane_state->crtc_x || + old_plane_state->crtc_y != plane_state->crtc_y || + old_plane_state->crtc_w != plane_state->crtc_w || + old_plane_state->crtc_h != plane_state->crtc_h)) { consider_mode_change = true; break; } From a8657c82a6dbda34c918e13d93c4005a103eb00b Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Sat, 4 Jul 2026 21:39:33 -0500 Subject: [PATCH 099/155] drm/amd/display: Promote DC to 3.2.389 This DC patchset brings improvements in multiple areas. In summary, we have: * Fixes on EDID 8K parsing, cursor mode, backlight, OLED/VABC, and HDR to SDR * Improvements on DCN42 clocking, det allocation, and power gating * Greatly enhanced KUnit coverage across amdgpu_dm * Code cleanup Acked-by: George Zhang Reviewed-by: Aurabindo Pillai Signed-off-by: Taimur Hassan Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 0987258df40c..a3e1eeff6e78 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -65,7 +65,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.388" +#define DC_VER "3.2.389" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC From a5cd2fbe7b45fbd4e5c01d1411742df17395a043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Thu, 2 Jul 2026 18:24:39 +0200 Subject: [PATCH 100/155] drm/amd/display/amdgpu_dm: show error names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert printk() args that log error numbers to log the names. Acked-by: George Zhang Signed-off-by: Michał Mirosław Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 40 +++++++++---------- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 20 +++++----- .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 12 +++--- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index a5961e19bf8c..eefd52714533 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6849,7 +6849,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = drm_atomic_helper_check_modeset(dev, state); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_helper_check_modeset() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_helper_check_modeset() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6864,7 +6864,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, new_crtc_state = drm_atomic_get_crtc_state(state, new_con_state->crtc); if (IS_ERR(new_crtc_state)) { - drm_dbg_atomic(dev, "drm_atomic_get_crtc_state() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_get_crtc_state() failed: %pe\n", new_crtc_state); ret = PTR_ERR(new_crtc_state); goto fail; } @@ -6884,7 +6884,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (drm_atomic_crtc_needs_modeset(new_crtc_state)) { ret = add_affected_mst_dsc_crtcs(state, crtc); if (ret) { - drm_dbg_atomic(dev, "add_affected_mst_dsc_crtcs() failed\n"); + drm_dbg_atomic(dev, "add_affected_mst_dsc_crtcs() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -6901,7 +6901,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = amdgpu_dm_verify_lut_sizes(new_crtc_state); if (ret) { - drm_dbg_atomic(dev, "amdgpu_dm_verify_lut_sizes() failed\n"); + drm_dbg_atomic(dev, "amdgpu_dm_verify_lut_sizes() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6910,13 +6910,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = drm_atomic_add_affected_connectors(state, crtc); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_add_affected_connectors() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_add_affected_connectors() failed: %pe\n", ERR_PTR(ret)); goto fail; } ret = drm_atomic_add_affected_planes(state, crtc); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_add_affected_planes() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_add_affected_planes() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6955,7 +6955,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (IS_ERR(new_plane_state)) { ret = PTR_ERR(new_plane_state); - drm_dbg_atomic(dev, "new_plane_state is BAD\n"); + drm_dbg_atomic(dev, "new_plane_state is BAD: %pe\n", new_plane_state); goto fail; } } @@ -6969,7 +6969,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, */ ret = drm_atomic_normalize_zpos(dev, state); if (ret) { - drm_dbg(dev, "drm_atomic_normalize_zpos() failed\n"); + drm_dbg(dev, "drm_atomic_normalize_zpos() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6983,7 +6983,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, &dm_new_crtc_state->cursor_mode); if (ret) { - drm_dbg(dev, "Failed to determine cursor mode\n"); + drm_dbg(dev, "Failed to determine cursor mode: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7015,7 +7015,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, &lock_and_validation_needed, &is_top_most_overlay); if (ret) { - drm_dbg_atomic(dev, "dm_update_plane_state() failed\n"); + drm_dbg_atomic(dev, "dm_update_plane_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -7028,7 +7028,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, false, &lock_and_validation_needed); if (ret) { - drm_dbg_atomic(dev, "DISABLE: dm_update_crtc_state() failed\n"); + drm_dbg_atomic(dev, "DISABLE: dm_update_crtc_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -7041,7 +7041,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, true, &lock_and_validation_needed); if (ret) { - drm_dbg_atomic(dev, "ENABLE: dm_update_crtc_state() failed\n"); + drm_dbg_atomic(dev, "ENABLE: dm_update_crtc_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -7055,7 +7055,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, &lock_and_validation_needed, &is_top_most_overlay); if (ret) { - drm_dbg_atomic(dev, "dm_update_plane_state() failed\n"); + drm_dbg_atomic(dev, "dm_update_plane_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -7071,7 +7071,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, /* Run this here since we want to validate the streams we created */ ret = drm_atomic_helper_check_planes(dev, state); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_helper_check_planes() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_helper_check_planes() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7209,13 +7209,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (lock_and_validation_needed) { ret = dm_atomic_get_state(state, &dm_state); if (ret) { - drm_dbg_atomic(dev, "dm_atomic_get_state() failed\n"); + drm_dbg_atomic(dev, "dm_atomic_get_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } ret = do_aquire_global_lock(dev, state); if (ret) { - drm_dbg_atomic(dev, "do_aquire_global_lock() failed\n"); + drm_dbg_atomic(dev, "do_aquire_global_lock() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7223,7 +7223,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (dc_resource_is_dsc_encoding_supported(dc)) { ret = compute_mst_dsc_configs_for_state(state, dm_state->context, vars); if (ret) { - drm_dbg_atomic(dev, "MST_DSC compute_mst_dsc_configs_for_state() failed\n"); + drm_dbg_atomic(dev, "MST_DSC compute_mst_dsc_configs_for_state() failed: %pe\n", ERR_PTR(ret)); ret = -EINVAL; goto fail; } @@ -7232,7 +7232,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = dm_update_mst_vcpi_slots_for_dsc(state, dm_state->context, vars); if (ret) { - drm_dbg_atomic(dev, "dm_update_mst_vcpi_slots_for_dsc() failed\n"); + drm_dbg_atomic(dev, "dm_update_mst_vcpi_slots_for_dsc() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7244,7 +7244,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, */ ret = drm_dp_mst_atomic_check(state); if (ret) { - drm_dbg_atomic(dev, "MST drm_dp_mst_atomic_check() failed\n"); + drm_dbg_atomic(dev, "MST drm_dp_mst_atomic_check() failed: %pe\n", ERR_PTR(ret)); goto fail; } status = dc_validate_global_state(dc, dm_state->context, DC_VALIDATE_MODE_ONLY); @@ -7333,7 +7333,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, else if (ret == -EINTR || ret == -EAGAIN || ret == -ERESTARTSYS) drm_dbg_atomic(dev, "Atomic check stopped due to signal.\n"); else - drm_dbg_atomic(dev, "Atomic check failed with err: %d\n", ret); + drm_dbg_atomic(dev, "Atomic check failed: %pe\n", ERR_PTR(ret)); trace_amdgpu_dm_atomic_check_finish(state, ret); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index fa05734047e0..9a34e0014084 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -374,7 +374,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(s ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, tiling_info, dcc, address, plane_size); if (ret) - drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned error: %d\n", ret); + drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned error: %pe\n", ERR_PTR(ret)); return ret; } @@ -415,7 +415,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( /* TODO: This seems wrong because there is no DCC plane on GFX12. */ ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, tiling_info, dcc, address, plane_size); if (ret) - drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned error: %d\n", ret); + drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned: %pe\n", ERR_PTR(ret)); return ret; } @@ -975,13 +975,15 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, adev = amdgpu_ttm_adev(rbo->tbo.bdev); r = amdgpu_bo_reserve(rbo, true); if (r) { - drm_err(adev_to_drm(adev), "fail to reserve bo (%d)\n", r); + drm_err(adev_to_drm(adev), "fail to reserve bo: %pe\n", ERR_PTR(r)); return r; } r = dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES); - if (r) + if (r) { + drm_err(adev_to_drm(adev), "reserving fence slot failed: %pe\n", ERR_PTR(r)); goto error_unlock; + } if (plane->type != DRM_PLANE_TYPE_CURSOR) domain = amdgpu_display_supported_domains(adev, rbo->flags); @@ -992,13 +994,13 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, r = amdgpu_bo_pin(rbo, domain); if (unlikely(r != 0)) { if (r != -ERESTARTSYS) - DRM_ERROR("Failed to pin framebuffer with error %d\n", r); + DRM_ERROR("Failed to pin framebuffer: %pe\n", ERR_PTR(r)); goto error_unlock; } r = amdgpu_ttm_alloc_gart(&rbo->tbo); if (unlikely(r != 0)) { - DRM_ERROR("%p bind failed\n", rbo); + DRM_ERROR("%p bind failed: %pe\n", rbo, ERR_PTR(r)); goto error_unpin; } @@ -1058,7 +1060,7 @@ static void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane, rbo = gem_to_amdgpu_bo(old_state->fb->obj[0]); r = amdgpu_bo_reserve(rbo, false); if (unlikely(r)) { - DRM_ERROR("failed to reserve rbo before unpin\n"); + DRM_ERROR("failed to reserve rbo before unpin: %pe\n", ERR_PTR(r)); return; } @@ -1868,8 +1870,8 @@ dm_plane_init_colorops(struct drm_plane *plane) if (dc->ctx->dce_version >= DCN_VERSION_3_0) { ret = amdgpu_dm_initialize_default_pipeline(plane, &pipelines[len]); if (ret) { - drm_err(plane->dev, "Failed to create color pipeline for plane %d: %d\n", - plane->base.id, ret); + drm_err(plane->dev, "Failed to create color pipeline for plane %d: %pe\n", + plane->base.id, ERR_PTR(ret)); goto out; } len++; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index 1fbb568a5c80..9e7bad4d6ed0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -107,13 +107,15 @@ STATIC_IFN_KUNIT int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb r = amdgpu_bo_reserve(rbo, true); if (r) { - drm_err(adev_to_drm(adev), "fail to reserve bo (%d)\n", r); + drm_err(adev_to_drm(adev), "fail to reserve bo: %pe\n", ERR_PTR(r)); return r; } r = dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES); - if (r) + if (r) { + drm_err(adev_to_drm(adev), "reserving fence slot failed: %pe\n", ERR_PTR(r)); goto error_unlock; + } domain = amdgpu_display_supported_domains(adev, rbo->flags); @@ -121,13 +123,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb r = amdgpu_bo_pin(rbo, domain); if (unlikely(r != 0)) { if (r != -ERESTARTSYS) - DRM_ERROR("Failed to pin framebuffer with error %d\n", r); + DRM_ERROR("Failed to pin framebuffer: %pe\n", ERR_PTR(r)); goto error_unlock; } r = amdgpu_ttm_alloc_gart(&rbo->tbo); if (unlikely(r != 0)) { - DRM_ERROR("%p bind failed\n", rbo); + DRM_ERROR("%p bind failed: %pe\n", rbo, ERR_PTR(r)); goto error_unpin; } @@ -160,7 +162,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *c rbo = gem_to_amdgpu_bo(job->fb->obj[0]); r = amdgpu_bo_reserve(rbo, false); if (unlikely(r)) { - DRM_ERROR("failed to reserve rbo before unpin\n"); + DRM_ERROR("failed to reserve rbo before unpin: %pe\n", ERR_PTR(r)); return; } From 56f7ea845e7d42db1fed48a0ec58fa09b95fabd7 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Mon, 29 Jun 2026 14:47:23 +0800 Subject: [PATCH 101/155] drm/amdgpu: add RS64 local memory context array init/fini Add amdgpu_mes_rs64mem_init() and amdgpu_mes_rs64mem_fini() to manage the RS64 local memory context arrays used by the MES scheduler. Signed-off-by: Prike Liang Reviewed-by: Michael Chen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 44 +++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 7 ++++ 2 files changed, 51 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index c66e69b62361..8ba1c941c03d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -921,6 +921,50 @@ int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev) return r; } +/** + * amdgpu_mes_rs64mem_init - initialize RS64 local memory context arrays + * + * @mes: MES instance + * + * Returns 0 on success, negative errno on failure. + */ +int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes) +{ + struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes); + int r; + + if (!mes->use_rs64mem) + return 0; + + r = amdgpu_bo_create_kernel(adev, PAGE_SIZE, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, + &mes->ctx_array_size_bo, + &mes->ctx_array_size_gpu_addr, + (void **)&mes->ctx_array_size_cpu_ptr); + if (r) { + dev_err(adev->dev, + "Failed to allocate ctx array size BO, r=%d\n", r); + return r; + } + + memset(mes->ctx_array_size_cpu_ptr, 0, PAGE_SIZE); + + return 0; +} + +/** + * amdgpu_mes_rs64mem_fini - tear down RS64 local memory management + */ +void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes) +{ + if (mes->ctx_array_size_bo) { + amdgpu_bo_free_kernel(&mes->ctx_array_size_bo, + &mes->ctx_array_size_gpu_addr, + (void **)&mes->ctx_array_size_cpu_ptr); + } + mes->use_rs64mem = false; +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 389e3324caea..520bcd8ee202 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -171,6 +171,11 @@ struct amdgpu_mes { bool compute_pipe_reset_enabled; bool gfx_pipe_reset_enabled; + + bool use_rs64mem; + struct amdgpu_bo *ctx_array_size_bo; + uint64_t ctx_array_size_gpu_addr; + uint32_t *ctx_array_size_cpu_ptr; }; struct amdgpu_mes_hung_queue_hqd_info { @@ -617,4 +622,6 @@ bool amdgpu_mes_queue_reset_by_mes_supported(struct amdgpu_device *adev); int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev); +int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes); +void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes); #endif /* __AMDGPU_MES_H__ */ From 0d6453fd6e17aa091da6ca24ce8b1a6dbc94b7ad Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Tue, 30 Jun 2026 01:27:37 +1000 Subject: [PATCH 102/155] drm/amd/display: Fix DM IRQ teardown races MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DM IRQ teardown can race with interrupt handling and low-context work. The IRQ handler can still walk the DM IRQ handler tables while the teardown path removes and frees entries. Low-context work can also remain queued after its handler has been removed, leading to a possible use-after-free when the work item later runs. Rework amdgpu_dm_irq_fini() to detach all low and high context handlers from the IRQ tables under the table lock, then cancel pending low-context work outside the lock before freeing the handlers. Also cancel low-context work in remove_irq_handler() before freeing an individual handler. Fix the suspend path by disabling HPD and HPD RX hardware interrupts under the IRQ table lock before flushing pending low-context work, avoiding a TOCTOU window where new work could be queued after the list check. Finally, call amdgpu_dm_irq_fini() from amdgpu_dm_fini() before DC is destroyed, so IRQ teardown happens while the display core state is still valid. Signed-off-by: Geoffrey McRae Reviewed-by: Leo Li Cc: Harry Wentland Cc: Leo Li Cc: Alex Deucher Cc: Christian König Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 8 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 137 +++++++++--------- 2 files changed, 68 insertions(+), 77 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index eefd52714533..57681bb1d053 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1018,14 +1018,11 @@ static void amdgpu_dm_fini(struct amdgpu_device *adev) adev->dm.hpd_rx_offload_wq = NULL; } + amdgpu_dm_irq_fini(adev); + /* DC Destroy TODO: Replace destroy DAL */ if (adev->dm.dc) dc_destroy(&adev->dm.dc); - /* - * TODO: pageflip, vlank interrupt - * - * amdgpu_dm_irq_fini(adev); - */ if (adev->dm.cgs_device) { amdgpu_cgs_destroy_device(adev->dm.cgs_device); @@ -1543,7 +1540,6 @@ static int dm_hw_fini(struct amdgpu_ip_block *ip_block) amdgpu_dm_hpd_fini(adev); - amdgpu_dm_irq_fini(adev); amdgpu_dm_fini(adev); return 0; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 7398987ee8a7..7e375ad137d8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -195,6 +195,9 @@ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, return NULL; } + if (int_params->int_context == INTERRUPT_LOW_IRQ_CONTEXT) + cancel_work_sync(&handler->work); + kfree(handler); DRM_DEBUG_KMS( @@ -204,55 +207,6 @@ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, return hnd_list; } -/** - * unregister_all_irq_handlers() - Cleans up handlers from the DM IRQ table - * @adev: The base driver device containing the DM device - * - * Go through low and high context IRQ tables and deallocate handlers. - */ -static void unregister_all_irq_handlers(struct amdgpu_device *adev) -{ - struct list_head *hnd_list_low; - struct list_head *hnd_list_high; - struct list_head *entry, *tmp; - struct amdgpu_dm_irq_handler_data *handler; - unsigned long irq_table_flags; - int i; - - DM_IRQ_TABLE_LOCK(adev, irq_table_flags); - - for (i = 0; i < DAL_IRQ_SOURCES_NUMBER; i++) { - hnd_list_low = &adev->dm.irq_handler_list_low_tab[i]; - hnd_list_high = &adev->dm.irq_handler_list_high_tab[i]; - - list_for_each_safe(entry, tmp, hnd_list_low) { - - handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, - list); - - if (handler == NULL || handler->handler == NULL) - continue; - - list_del(&handler->list); - kfree(handler); - } - - list_for_each_safe(entry, tmp, hnd_list_high) { - - handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, - list); - - if (handler == NULL || handler->handler == NULL) - continue; - - list_del(&handler->list); - kfree(handler); - } - } - - DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); -} - static bool validate_irq_registration_params(struct dc_interrupt_params *int_params, void (*ih)(void *)) @@ -459,38 +413,71 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_init); * amdgpu_dm_irq_fini() - Tear down DM IRQ management * @adev: The base driver device containing the DM device * - * Flush all work within the low context IRQ table. + * Removes all handlers from the IRQ tables under the spinlock, cancels + * pending work items, and deallocates all handler data. */ void amdgpu_dm_irq_fini(struct amdgpu_device *adev) { int src; - struct list_head *lh; + LIST_HEAD(low_handlers); + LIST_HEAD(high_handlers); struct list_head *entry, *tmp; struct amdgpu_dm_irq_handler_data *handler; unsigned long irq_table_flags; DRM_DEBUG_KMS("DM_IRQ: releasing resources.\n"); + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { DM_IRQ_TABLE_LOCK(adev, irq_table_flags); - /* The handler was removed from the table, - * it means it is safe to flush all the 'work' - * (because no code can schedule a new one). + + /* + * Move all handlers from the low and high context tables to + * temporary lists under the lock. This prevents the ISR from + * finding them while we process them outside the lock. */ - lh = &adev->dm.irq_handler_list_low_tab[src]; + list_splice_init(&adev->dm.irq_handler_list_low_tab[src], + &low_handlers); + list_splice_init(&adev->dm.irq_handler_list_high_tab[src], + &high_handlers); + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); - if (!list_empty(lh)) { - list_for_each_safe(entry, tmp, lh) { - handler = list_entry( - entry, - struct amdgpu_dm_irq_handler_data, - list); - flush_work(&handler->work); - } + /* + * Cancel all pending work for the low-context handlers + * outside the lock. cancel_work_sync() may sleep and waits + * until any running work completes, preventing UAF. + */ + list_for_each_safe(entry, tmp, &low_handlers) { + handler = list_entry(entry, + struct amdgpu_dm_irq_handler_data, + list); + cancel_work_sync(&handler->work); } + + /* + * High-context handlers are executed synchronously within ISR + * context (see amdgpu_dm_irq_immediate_work()) and have no + * work_struct, so there is no pending work to cancel here. + * They will be freed along with low_handlers after the loop. + */ + } + + /* Deallocate all handlers. */ + list_for_each_safe(entry, tmp, &low_handlers) { + handler = list_entry(entry, + struct amdgpu_dm_irq_handler_data, + list); + list_del(&handler->list); + kfree(handler); + } + + list_for_each_safe(entry, tmp, &high_handlers) { + handler = list_entry(entry, + struct amdgpu_dm_irq_handler_data, + list); + list_del(&handler->list); + kfree(handler); } - /* Deallocate handlers from the table. */ - unregister_all_irq_handlers(adev); } EXPORT_IF_KUNIT(amdgpu_dm_irq_fini); @@ -498,7 +485,6 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) { struct drm_device *dev = adev_to_drm(adev); int src; - struct list_head *hnd_list_h; struct list_head *hnd_list_l; unsigned long irq_table_flags; struct list_head *entry, *tmp; @@ -511,12 +497,15 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) /** * Disable HW interrupt for HPD and HPDRX only since FLIP and VBLANK * will be disabled from manage_dm_interrupts on disable CRTC. + * + * Disable the HW interrupt first, then flush any pending work. Since + * the HW interrupt is disabled under the lock, no new IRQ can be + * generated after the disable completes. Any work already queued by an + * in-flight ISR will be flushed below. */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; - hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; - if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, false); + dc_interrupt_set(adev->dm.dc, src, false); DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -600,9 +589,12 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, struct list_head *handler_list = &adev->dm.irq_handler_list_low_tab[irq_source]; struct amdgpu_dm_irq_handler_data *handler_data; bool work_queued = false; + unsigned long irq_table_flags; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); if (list_empty(handler_list)) - return; + goto out_unlock; list_for_each_entry(handler_data, handler_list, list) { if (queue_work(system_highpri_wq, &handler_data->work)) { @@ -620,7 +612,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, handler_data_add = kzalloc_obj(*handler_data, GFP_ATOMIC); if (!handler_data_add) { DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); - return; + goto out_unlock; } /*copy new amdgpu_dm_irq_handler_data members from handler_data*/ @@ -642,6 +634,9 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, "from display for IRQ source %d\n", irq_source); } + +out_unlock: + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } EXPORT_IF_KUNIT(amdgpu_dm_irq_schedule_work); From 9102b39fa924dcc3dc75a3137bfa9633c40b88c0 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Fri, 10 Jul 2026 16:45:42 +0800 Subject: [PATCH 103/155] drm/amdgpu/userq: fix indefinite fence wait during GPU reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pre_reset only force-completes fences of MAPPED queues. A queue in any other state (e.g. mid-eviction) keeps its last_fence pending; after a GPU reset that fence never signals, so the eviction/suspend worker and process teardown (amdgpu_evf_mgr_flush_suspend) wait on it forever and wedge the machine: INFO: task kworker/6:28 blocked for more than 120 seconds. Workqueue: events amdgpu_eviction_fence_suspend_worker [amdgpu] Call Trace: dma_fence_wait_timeout+0x7e/0x130 amdgpu_userq_evict+0x67/0x140 [amdgpu] amdgpu_eviction_fence_suspend_worker+0xd8/0x160 [amdgpu] process_scheduled_works+0xa6/0x420 Force-complete every queue's fence regardless of state. The unmap and mark-hung step stays gated on MAPPED, since unmapping a queue that is not mapped is invalid. Fixes: 290f46cf5726 ("drm/amdgpu: Implement user queue reset functionality") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 9cc71a3a5362..babaabe2d891 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1422,18 +1422,21 @@ void amdgpu_userq_pre_reset(struct amdgpu_device *adev) /* TODO: We probably need a new lock for the queue state */ xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { - if (queue->state != AMDGPU_USERQ_STATE_MAPPED) - continue; - - trace_amdgpu_userq_state_start(queue); - userq_funcs = adev->userq_funcs[queue->queue_type]; - userq_funcs->unmap(queue); - /* just mark all queues as hung at this point. - * if unmap succeeds, we could map again - * in amdgpu_userq_post_reset() if vram is not lost + if (queue->state == AMDGPU_USERQ_STATE_MAPPED) { + trace_amdgpu_userq_state_start(queue); + userq_funcs = adev->userq_funcs[queue->queue_type]; + userq_funcs->unmap(queue); + /* just mark all queues as hung at this point. + * if unmap succeeds, we could map again + * in amdgpu_userq_post_reset() if vram is not lost + */ + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); + queue->state = AMDGPU_USERQ_STATE_HUNG; + } + /* Force-complete any pending fence regardless of queue state so + * that eviction/suspend and queue teardown waiters don't block + * forever on a fence that will never signal after the reset. */ - trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); - queue->state = AMDGPU_USERQ_STATE_HUNG; amdgpu_userq_fence_driver_force_completion(queue); } } From 4a209ac6749138ec0ca926b1a04d8e786df23697 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Tue, 30 Jun 2026 02:14:49 +1000 Subject: [PATCH 104/155] drm/amd/display: Use unbound workqueues for deferred DM work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DM currently queues some deferred display work on system workqueues. Low-context IRQ handlers are queued on system_highpri_wq, while deferred vmin/vmax updates are queued on system_percpu_wq. Both paths can execute long-running display work. HPD and HPD RX handling may involve link detection, AUX transactions, connector state updates, and hotplug notification. The vmin/vmax update path calls into DC under dc_lock to adjust stream timing. These paths can therefore trigger the workqueue CPU hog detector when run from per-CPU workers: workqueue: dm_irq_work_func [amdgpu] hogged CPU for >10000us workqueue: dm_handle_vmin_vmax_update [amdgpu] hogged CPU for >10000us Move the deferred low-context IRQ work to a dedicated high-priority unbound workqueue, preserving the priority of the previous system_highpri_wq usage while avoiding long-running work on per-CPU workers. Move deferred vmin/vmax updates to a separate normal-priority unbound workqueue. High-context IRQ handlers remain unchanged and continue to run directly from the IRQ path. Signed-off-by: Geoffrey McRae Reviewed-by: Leo Li Cc: Harry Wentland Cc: Leo Li Cc: Alex Deucher Cc: Christian König Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 4 +++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 31 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) 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 a04c7f26672b..f396803d1485 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -324,6 +324,8 @@ struct hpd_rx_irq_offload_work { * @ddev: DRM base driver structure * @display_indexes_num: Max number of display streams supported * @irq_handler_list_table_lock: Synchronizes access to IRQ tables + * @irq_wq: Dedicated high-priority unbound workqueue for deferred IRQ work + * @vmin_vmax_wq: Dedicated unbound workqueue for deferred vmin/vmax updates * @backlight_dev: Backlight control device * @backlight_link: Link on which to control backlight * @backlight_caps: Capabilities of the backlight device @@ -563,6 +565,8 @@ struct amdgpu_display_manager { dmub_outbox_params[1]; spinlock_t irq_handler_list_table_lock; + struct workqueue_struct *irq_wq; + struct workqueue_struct *vmin_vmax_wq; struct backlight_device *backlight_dev[AMDGPU_DM_MAX_NUM_EDP]; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 7e375ad137d8..ad30cae2af42 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -397,6 +397,21 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) spin_lock_init(&adev->dm.irq_handler_list_table_lock); + adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", + WQ_UNBOUND | WQ_HIGHPRI, 0); + + if (!adev->dm.irq_wq) + return -ENOMEM; + + adev->dm.vmin_vmax_wq = alloc_workqueue("amdgpu_dm_vmin_vmax", + WQ_UNBOUND, 0); + + if (!adev->dm.vmin_vmax_wq) { + destroy_workqueue(adev->dm.irq_wq); + adev->dm.irq_wq = NULL; + return -ENOMEM; + } + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { /* low context handler list init */ lh = &adev->dm.irq_handler_list_low_tab[src]; @@ -478,6 +493,16 @@ void amdgpu_dm_irq_fini(struct amdgpu_device *adev) list_del(&handler->list); kfree(handler); } + + if (adev->dm.vmin_vmax_wq) { + destroy_workqueue(adev->dm.vmin_vmax_wq); + adev->dm.vmin_vmax_wq = NULL; + } + + if (adev->dm.irq_wq) { + destroy_workqueue(adev->dm.irq_wq); + adev->dm.irq_wq = NULL; + } } EXPORT_IF_KUNIT(amdgpu_dm_irq_fini); @@ -597,7 +622,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, goto out_unlock; list_for_each_entry(handler_data, handler_list, list) { - if (queue_work(system_highpri_wq, &handler_data->work)) { + if (queue_work(adev->dm.irq_wq, &handler_data->work)) { work_queued = true; break; } @@ -625,7 +650,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, INIT_WORK(&handler_data_add->work, dm_irq_work_func); - if (queue_work(system_highpri_wq, &handler_data_add->work)) + if (queue_work(adev->dm.irq_wq, &handler_data_add->work)) DRM_DEBUG("Queued work for handling interrupt from " "display for IRQ source %d\n", irq_source); @@ -1913,7 +1938,7 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, offload_work->stream = stream; offload_work->adjust = adjust_copy; - queue_work(system_percpu_wq, &offload_work->work); + queue_work(adev->dm.vmin_vmax_wq, &offload_work->work); } STATIC_IFN_KUNIT void dm_vupdate_high_irq(void *interrupt_params) From 3ddc0ae76202c447b6aec61e907b852bc94671cf Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Wed, 1 Jul 2026 18:53:21 +0800 Subject: [PATCH 105/155] drm/amdgpu: fix bo->pin leaking in amdgpu_bo_create_reserved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_bo_create_reserved() only allocates a new BO when *bo_ptr (struct amdgpu_bo **bo_ptr as input parameter) is NULL, it simply skips creation when *bo_ptr is non-NULL. But it unconditionally reserves, pins, gart allocates and maps the BO afterwards. When the same non-NULL BO pointer is passed in again, for example firmware buffers that live in adev and are re-loaded on every resume / cp_resume / start under AMDGPU_FW_LOAD_DIRECT, amdgpu_bo_pin() just increases pin_count unconditionally, however the matching teardown only unpins once, so pin_count never drops to zero, so TTM is not able to move, swap or evict a BO, causing BO leaks. This commit fixes this issue by only pinning the bo once at creation, and repeated calls no longer take additional pin references. Signed-off-by: Zhu Lingshan Reviewed-by: Alex Deucher Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index f98bfba59a2c..718937777e53 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -276,10 +276,12 @@ int amdgpu_bo_create_reserved(struct amdgpu_device *adev, goto error_free; } - r = amdgpu_bo_pin(*bo_ptr, domain); - if (r) { - dev_err(adev->dev, "(%d) kernel bo pin failed\n", r); - goto error_unreserve; + if (free) { + r = amdgpu_bo_pin(*bo_ptr, domain); + if (r) { + dev_err(adev->dev, "(%d) kernel bo pin failed\n", r); + goto error_unreserve; + } } r = amdgpu_ttm_alloc_gart(&(*bo_ptr)->tbo); @@ -302,7 +304,8 @@ int amdgpu_bo_create_reserved(struct amdgpu_device *adev, return 0; error_unpin: - amdgpu_bo_unpin(*bo_ptr); + if (free) + amdgpu_bo_unpin(*bo_ptr); error_unreserve: amdgpu_bo_unreserve(*bo_ptr); From 91d50ffb37dfa2f36893f5b306e72006e638dbe7 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Tue, 30 Jun 2026 16:47:25 +0800 Subject: [PATCH 106/155] drm/amdgpu/mes: add MES process/gang context size and bitmap helper Allocating the MES context bitmap to track the process/gang index usage. Signed-off-by: Prike Liang Reviewed-by: Michael Chen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 69 +++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 9 +++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 8ba1c941c03d..56791e9e345a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -962,9 +962,78 @@ void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes) &mes->ctx_array_size_gpu_addr, (void **)&mes->ctx_array_size_cpu_ptr); } + bitmap_free(mes->proc_ctx_bitmap); + bitmap_free(mes->gang_ctx_bitmap); mes->use_rs64mem = false; } +/** + * amdgpu_mes_rs64mem_setup_bitmaps - allocate bitmaps after querying MES + * + * Called after QUERY_SCHEDULER_STATUS returns and MES has written + * the array sizes to the GPU buffer. Reads the sizes and allocates + * the tracking bitmaps. + * + * @mes: MES instance + * + * Returns 0 on success, negative errno on failure. + */ +int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes) +{ + struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes); + + if (!mes->use_rs64mem || !mes->ctx_array_size_cpu_ptr) + return 0; + + /* + * MES FW wrote the sizes to the GPU buffer: + * ctx_array_size_cpu_ptr[0] = proc_ctx_array_size (N) + * ctx_array_size_cpu_ptr[1] = gang_ctx_array_size (M) + */ + mes->proc_ctx_array_size = mes->ctx_array_size_cpu_ptr[0]; + mes->gang_ctx_array_size = mes->ctx_array_size_cpu_ptr[1]; + + /* Sanity check - MES FW typically returns N=50, M=300 */ + if (mes->proc_ctx_array_size == 0 || mes->gang_ctx_array_size == 0) { + dev_warn(adev->dev, + "MES returned zero ctx array sizes (proc=%u, gang=%u), " + "disabling RS64 local memory optimization\n", + mes->proc_ctx_array_size, mes->gang_ctx_array_size); + mes->use_rs64mem = false; + return 0; + } + + /* Cap to safety limits */ + if (mes->proc_ctx_array_size > AMDGPU_MES_PROC_CTX_ARRAY_MAX) + mes->proc_ctx_array_size = AMDGPU_MES_PROC_CTX_ARRAY_MAX; + if (mes->gang_ctx_array_size > AMDGPU_MES_GANG_CTX_ARRAY_MAX) + mes->gang_ctx_array_size = AMDGPU_MES_GANG_CTX_ARRAY_MAX; + + dev_info(adev->dev, + "MES RS64 local memory: proc_ctx_array_size:%u, " + "gang_ctx_array_size:%u\n", + mes->proc_ctx_array_size, mes->gang_ctx_array_size); + + /* Allocate bitmaps */ + mes->proc_ctx_bitmap = bitmap_zalloc(mes->proc_ctx_array_size, + GFP_KERNEL); + if (!mes->proc_ctx_bitmap) { + mes->use_rs64mem = false; + return -ENOMEM; + } + + mes->gang_ctx_bitmap = bitmap_zalloc(mes->gang_ctx_array_size, + GFP_KERNEL); + if (!mes->gang_ctx_bitmap) { + bitmap_free(mes->proc_ctx_bitmap); + mes->proc_ctx_bitmap = NULL; + mes->use_rs64mem = false; + return -ENOMEM; + } + + return 0; +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 520bcd8ee202..5c50bc9616b7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -54,7 +54,8 @@ enum amdgpu_mes_priority_level { #define AMDGPU_MES_PROC_CTX_SIZE 0x1000 /* one page area */ #define AMDGPU_MES_GANG_CTX_SIZE 0x1000 /* one page area */ - +#define AMDGPU_MES_PROC_CTX_ARRAY_MAX 128 +#define AMDGPU_MES_GANG_CTX_ARRAY_MAX 512 struct amdgpu_mes_funcs; enum amdgpu_mes_pipe { @@ -176,6 +177,11 @@ struct amdgpu_mes { struct amdgpu_bo *ctx_array_size_bo; uint64_t ctx_array_size_gpu_addr; uint32_t *ctx_array_size_cpu_ptr; + + uint32_t proc_ctx_array_size; + unsigned long *proc_ctx_bitmap; + uint32_t gang_ctx_array_size; + unsigned long *gang_ctx_bitmap; }; struct amdgpu_mes_hung_queue_hqd_info { @@ -624,4 +630,5 @@ int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev); int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes); void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes); +int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes); #endif /* __AMDGPU_MES_H__ */ From 11c141672045ffc0187aa604f2c0f597bc334fb2 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:14 -0500 Subject: [PATCH 107/155] drm/amdgpu: Fix VFCT bus number matching with soft filter On systems where PCI bus renumbering occurs (e.g. pci=realloc, resource conflicts), the runtime bus number may differ from the BIOS POST bus number recorded in the VFCT table. This causes amdgpu_acpi_vfct_bios() to fail finding the VBIOS even though the correct device entry exists. Introduce amdgpu_acpi_vfct_match() which treats the bus number as a soft filter: vendor/device/function identity is the hard requirement, while exact bus match is the preferred path. When bus numbers disagree but device identity matches, accept the VFCT entry and log a dev_notice for diagnostics. Reported-by: Oz Tiram Closes: https://lore.kernel.org/amd-gfx/20260621173211.28443-1-oz@shift-computing.de/ Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-2-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 45 +++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c index 3ebdd792feec..bede0e67b3f1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -378,6 +378,45 @@ static bool amdgpu_read_disabled_bios(struct amdgpu_device *adev) } #ifdef CONFIG_ACPI +/** + * amdgpu_acpi_vfct_match() - Check if a VFCT entry matches the device + * @adev: AMDGPU device + * @vhdr: VFCT image header to check + * + * VFCT entries contain the PCI bus number as recorded during BIOS POST. + * On systems where the kernel renumbers PCI buses (e.g. pci=realloc or + * resource conflicts), the runtime bus number may differ from the POST + * value. Match by device identity (vendor + device + function) and use + * the bus number as a preference: exact bus match is preferred, but when + * the bus numbers disagree we accept the entry if the device identity + * matches. + * + * Returns: 0 on match, -ENODEV on no match + */ +static int amdgpu_acpi_vfct_match(struct amdgpu_device *adev, + VFCT_IMAGE_HEADER *vhdr) +{ + /* Vendor and device IDs must always match */ + if (vhdr->VendorID != adev->pdev->vendor || + vhdr->DeviceID != adev->pdev->device) + return -ENODEV; + + if (vhdr->PCIDevice != PCI_SLOT(adev->pdev->devfn) || + vhdr->PCIFunction != PCI_FUNC(adev->pdev->devfn)) + return -ENODEV; + + /* Exact bus number match - preferred */ + if (vhdr->PCIBus == adev->pdev->bus->number) + return 0; + + /* Bus mismatch but device identity matches (PCI renumbering case) */ + dev_notice(adev->dev, + "VFCT bus number mismatch: table %u != runtime %u, matching by device identity (vendor 0x%04x device 0x%04x)\n", + vhdr->PCIBus, adev->pdev->bus->number, + adev->pdev->vendor, adev->pdev->device); + return 0; +} + static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) { struct acpi_table_header *hdr; @@ -413,11 +452,7 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) } if (vhdr->ImageLength && - vhdr->PCIBus == adev->pdev->bus->number && - vhdr->PCIDevice == PCI_SLOT(adev->pdev->devfn) && - vhdr->PCIFunction == PCI_FUNC(adev->pdev->devfn) && - vhdr->VendorID == adev->pdev->vendor && - vhdr->DeviceID == adev->pdev->device) { + !amdgpu_acpi_vfct_match(adev, vhdr)) { adev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); From ca5988682b4cba4cd125a0fa99b2de1239164ae4 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:15 -0500 Subject: [PATCH 108/155] drm/amdgpu: Release VFCT ACPI table reference amdgpu_acpi_vfct_bios() fetches the VFCT table with acpi_get_table() but never releases it. acpi_get_table() takes a reference on the table (incrementing its validation_count and mapping it on the 0->1 transition); without a paired acpi_put_table() the mapping is leaked on every call, whether or not a matching VBIOS image is found. Route all exit paths after the table is acquired through a common acpi_put_table(). The VBIOS image is copied out with kmemdup() before the table is released, so it remains valid for the caller. Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-3-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c index bede0e67b3f1..08ed1eb69558 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -423,13 +423,14 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) acpi_size tbl_size; UEFI_ACPI_VFCT *vfct; unsigned int offset; + bool r = false; if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr))) return false; tbl_size = hdr->length; if (tbl_size < sizeof(UEFI_ACPI_VFCT)) { dev_info(adev->dev, "ACPI VFCT table present but broken (too short #1),skipping\n"); - return false; + goto out; } vfct = (UEFI_ACPI_VFCT *)hdr; @@ -442,13 +443,13 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) offset += sizeof(VFCT_IMAGE_HEADER); if (offset > tbl_size) { dev_info(adev->dev, "ACPI VFCT image header truncated,skipping\n"); - return false; + goto out; } offset += vhdr->ImageLength; if (offset > tbl_size) { dev_info(adev->dev, "ACPI VFCT image truncated,skipping\n"); - return false; + goto out; } if (vhdr->ImageLength && @@ -459,15 +460,19 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) if (!check_atom_bios(adev, vhdr->ImageLength)) { amdgpu_bios_release(adev); - return false; + goto out; } adev->bios_size = vhdr->ImageLength; - return true; + r = true; + goto out; } } dev_info(adev->dev, "ACPI VFCT table present but broken (too short #2),skipping\n"); - return false; + +out: + acpi_put_table(hdr); + return r; } #else static inline bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) From cf5f98609a03d89a32cd0922b092eea66bdb2e77 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 3 Jul 2026 10:25:58 +0800 Subject: [PATCH 109/155] drm/amdgpu: add mes process context alloc/free Those helpers allocates/frees slots from bitmaps for process_context_array_index processed by MES firmware. Signed-off-by: Prike Liang Reviewed-by: Michael Chen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 46 ++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 5 +++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 2 + drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 9 ++++- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 1 + 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 56791e9e345a..699c6db4ea36 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -1034,6 +1034,52 @@ int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes) return 0; } +/** + * amdgpu_mes_alloc_proc_ctx_index - allocate a process context slot + * + * @mes: MES instance + * + * Returns 0 on success, -ENOSPC if all slots are used (caller should + * fall back to system memory path). + */ +int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + unsigned long bit; + + if (!mes->use_rs64mem || !mes->proc_ctx_bitmap) + return -EOPNOTSUPP; + + amdgpu_mes_lock(mes); + bit = find_first_zero_bit(mes->proc_ctx_bitmap, + mes->proc_ctx_array_size); + if (bit >= mes->proc_ctx_array_size) { + amdgpu_mes_unlock(mes); + return -ENOSPC; + } + set_bit(bit, mes->proc_ctx_bitmap); + queue->proc_ctx_array_index = (uint32_t)bit; + amdgpu_mes_unlock(mes); + + return 0; +} + +/** + * amdgpu_mes_free_proc_ctx_index - free a process context slot + */ +void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + if (!mes->use_rs64mem || !mes->proc_ctx_bitmap) + return; + if (queue->proc_ctx_array_index >= mes->proc_ctx_array_size) + return; + + amdgpu_mes_lock(mes); + clear_bit(queue->proc_ctx_array_index, mes->proc_ctx_bitmap); + amdgpu_mes_unlock(mes); +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 5c50bc9616b7..33a426f58e72 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -279,6 +279,7 @@ struct mes_add_queue_input { uint32_t exclusively_scheduled; uint32_t sh_mem_config_data; uint32_t vm_cntx_cntl; + uint32_t process_context_array_index; }; struct mes_remove_queue_input { @@ -631,4 +632,8 @@ int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev); int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes); void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes); int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes); +int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); +void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); #endif /* __AMDGPU_MES_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 61e5f8a06eb2..8bb0d3213abf 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -100,6 +100,8 @@ struct amdgpu_usermode_queue { } va; u64 va_array[6]; } userq_vas; + + uint32_t proc_ctx_array_index; }; struct amdgpu_userq_funcs { diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index defa050fd871..c5b01a552592 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -121,6 +121,7 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) struct amdgpu_userq_obj *ctx = &queue->fw_obj; struct amdgpu_mqd_prop *userq_props = queue->userq_prop; struct mes_add_queue_input queue_input; + struct amdgpu_mes *mes = &adev->mes; int r; memset(&queue_input, 0x0, sizeof(struct mes_add_queue_input)); @@ -146,7 +147,10 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) queue_input.doorbell_offset = userq_props->doorbell_index; queue_input.page_table_base_addr = amdgpu_gmc_pd_addr(queue->vm->root.bo); queue_input.wptr_mc_addr = queue->wptr_obj.gpu_addr; - + if (mes->use_rs64mem) { + amdgpu_mes_alloc_proc_ctx_index(mes, queue); + queue_input.process_context_array_index = queue->proc_ctx_array_index; + } amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->add_hw_queue(&adev->mes, &queue_input); amdgpu_mes_unlock(&adev->mes); @@ -165,6 +169,7 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) struct amdgpu_device *adev = uq_mgr->adev; struct mes_remove_queue_input queue_input; struct amdgpu_userq_obj *ctx = &queue->fw_obj; + struct amdgpu_mes *mes = &adev->mes; int r; memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); @@ -175,6 +180,8 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->remove_hw_queue(&adev->mes, &queue_input); amdgpu_mes_unlock(&adev->mes); + if (mes->use_rs64mem) + amdgpu_mes_free_proc_ctx_index(mes, queue); if (r) DRM_ERROR("Failed to unmap queue in HW, err (%d)\n", r); return r; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 72ca7302bbfb..e119e743f489 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -335,6 +335,7 @@ static int mes_v11_0_add_hw_queue(struct amdgpu_mes *mes, mes_add_queue_pkt.process_va_end = input->process_va_end; mes_add_queue_pkt.process_quantum = input->process_quantum; mes_add_queue_pkt.process_context_addr = input->process_context_addr; + mes_add_queue_pkt.process_context_array_index = input->process_context_array_index; mes_add_queue_pkt.gang_quantum = input->gang_quantum; mes_add_queue_pkt.gang_context_addr = input->gang_context_addr; mes_add_queue_pkt.inprocess_gang_priority = From c87e6635d2db02c88ae8d09529362da672d34770 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Fri, 12 Jun 2026 13:29:31 -0400 Subject: [PATCH 110/155] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock [Why] On DCN, vblank events were delivered from VSTARTUP/VUPDATE (dm_crtc_high_irq/dm_vupdate_high_irq) and pageflip completion from GRPH_PFLIP (dm_pflip_high_irq). These signals can be masked by hardware by a few things: * DPG - DCN can Dynamically Power Gate parts of the display pipe when a self-refresh capable eDP is connected. DPG is engaged when there's enough static frames (detected through drm_vblank_off). Once gated, even though the OTG (output timing generator) is still enabled, VSTARTUP and GRPH_FLIP are masked. * GSL - Driver can use the Global Sync Lock to block HW from latching onto double-buffered registers during programming, to prevent HW from latching onto a partially programmed state. This will mask VSTARTUP, GRPH_FLIP, and VUPDATE. See dcn20_pipe_control_lock(). * MALL - A DCN accessible cache introduced in DCN32+ DGPUs that can store fb data to allow for longer DRAM sleep. When scanning out from MALL, VSTARTUP is masked. When masked, events are never delivered, which can show up as flip_done timeouts in the wild. However, there is an interrupt source on DCN that is never masked: VUPDATE_NO_LOCK. It's simply an unmasked variant of VUPDATE, which fires while the OTG is active, at the exact point hardware latches double-buffered registers. It is therefore the natural single signal for delivering both vblank and flip-completion events on DCN, and the correct point to timestamp both VRR and non-VRR vblanks. DCE's interrupt sources are different, it does not have an unmaskable VUPDATE_NO_LOCK. The only unmaskable DCE interrupt is VLINE0, but it can only be programmed as a vline offset from vsync_start, making it unsuitable for VRR. Thus, we keep DCE untouched and use the existing mix of interrupt sources. [How] For DCN1 and newer only: * Factor the body of dm_crtc_high_irq() into dm_crtc_high_irq_handler() and drive it from dm_vupdate_high_irq() (VUPDATE_NO_LOCK). DCE keeps using dm_crtc_high_irq() (VSTARTUP) and dm_pflip_high_irq() (GRPH_PFLIP) unchanged. * Stop registering VSTARTUP (crtc_irq) and GRPH_PFLIP (pageflip_irq) on DCN, and stop enabling them in amdgpu_dm_crtc_set_vblank() / manage_dm_interrupts(). Enable VUPDATE whenever vblank is enabled on DCN (previously only in VRR mode). The secure-display vline0 interrupt is left untouched. * VUPDATE_NO_LOCK does not early-fire on an immediate (tearing / async) flip, since HW latches the new address right away. Deliver the flip completion event immediately after programming such flips in amdgpu_dm_commit_planes(), and clear pflip_status so the next vupdate handler does not double-send. v2: Do not gate VUPDATE_NO_LOCK on DCN in dm_handle_vrr_transition() Also toggle VUPDATE_NO_LOCK on DCN in dm_gpureset_toggle_interrupts() Re-cook vblank event count and timestamp for immediate flips Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations") Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787 Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141 Assisted-by: Copilot:claude-opus-4.8 Co-developed-by: Matthew Schwartz Signed-off-by: Matthew Schwartz Tested-by: Mario Limonciello (AMD) Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 68 ++++- .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 70 +++-- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 283 ++++++++---------- .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 16 +- 4 files changed, 237 insertions(+), 200 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 57681bb1d053..53f18ea65644 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1557,7 +1557,8 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, acrtc = amdgpu_dm_get_crtc_by_otg_inst( adev, state->stream_status[i].primary_otg_inst); - if (acrtc && state->stream_status[i].plane_count != 0) { + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; if (rc) @@ -1585,6 +1586,13 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, */ if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { + /* DCN only needs to toggle VUPDATE_NO_LOCK */ + rc = amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, enable); + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %sable vupdate interrupt\n", + enable ? "en" : "dis"); } } @@ -3571,14 +3579,22 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, drm_crtc_vblank_on_config(&acrtc->base, &config); - /* Allow RX6xxx, RX7700, RX7800 GPUs to call amdgpu_irq_get.*/ + /* + * Since pflip_high_irq is no longer registered for DCN, grab an + * extra reference to vupdate irq instead to workaround this + * issue: + * https://gitlab.freedesktop.org/drm/amd/-/work_items/3936 + * + * The callbacks to drm_vblank_on/off should really take care of + * this though. + */ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 0, 0): case IP_VERSION(3, 0, 2): case IP_VERSION(3, 0, 3): case IP_VERSION(3, 2, 0): - if (amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type)) - drm_err(dev, "DM_IRQ: Cannot get pageflip irq!\n"); + if (amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type)) + drm_err(dev, "DM_IRQ: Cannot get vupdate irq!\n"); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) if (amdgpu_irq_get(adev, &adev->vline0_irq, irq_type)) drm_err(dev, "DM_IRQ: Cannot get vline0 irq!\n"); @@ -3596,8 +3612,8 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, if (amdgpu_irq_put(adev, &adev->vline0_irq, irq_type)) drm_err(dev, "DM_IRQ: Cannot put vline0 irq!\n"); #endif - if (amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type)) - drm_err(dev, "DM_IRQ: Cannot put pageflip irq!\n"); + if (amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type)) + drm_err(dev, "DM_IRQ: Cannot put vupdate irq!\n"); } drm_crtc_vblank_off(&acrtc->base); @@ -3610,6 +3626,10 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev, int irq_type = amdgpu_display_crtc_idx_to_irq_type(adev, acrtc->crtc_id); + /* GRPH_PFLIP is not used on DCN; nothing to reapply. */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) + return; + /** * This reads the current state for the IRQ and force reapplies * the setting to hardware. @@ -3943,9 +3963,13 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, struct dm_crtc_state *old_state, struct dm_crtc_state *new_state) { + struct amdgpu_device *adev = dm->adev; bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state); bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state); + /* Only DCE gates vupdate on VRR, keep it enabled for DCN */ + bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0; + if (!old_vrr_active && new_vrr_active) { /* Transition VRR inactive -> active: * While VRR is active, we must not disable vblank irq, as a @@ -3955,7 +3979,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, * We also need vupdate irq for the actual core vblank handling * at end of vblank. */ - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0); drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n", __func__, new_state->base.crtc->base.id); @@ -3971,7 +3996,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, /* Transition VRR active -> inactive: * Allow vblank irq disable again for fixed refresh rate. */ - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); drm_crtc_vblank_put(new_state->base.crtc); drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n", __func__, new_state->base.crtc->base.id); @@ -4142,6 +4168,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state); bool cursor_update = false; bool pflip_present = false; + bool immediate_flip = false; bool dirty_rects_changed = false; bool updated_planes_and_streams = false; struct { @@ -4304,6 +4331,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, acrtc_state->update_type == UPDATE_TYPE_FAST && get_mem_type(old_plane_state->fb) == get_mem_type(fb); + immediate_flip |= bundle->flip_addrs[planes_count].flip_immediate; + timestamp_ns = ktime_get_ns(); bundle->flip_addrs[planes_count].flip_timestamp_in_us = div_u64(timestamp_ns, 1000); bundle->surface_updates[planes_count].flip_addr = &bundle->flip_addrs[planes_count]; @@ -4496,6 +4525,29 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, acrtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE) amdgpu_dm_commit_cursors(state); + /* + * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK. + * However, an immediate (tearing / async) flip is latched by HW right + * away and does not wait for the next vupdate, so deliver its + * completion event here after programming. + * + * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so + * this is DCN-only. + */ + if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + spin_lock_irqsave(&pcrtc->dev->event_lock, flags); + if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc_attach->event) { + drm_crtc_accurate_vblank_count(&acrtc_attach->base); + drm_crtc_send_vblank_event(&acrtc_attach->base, + acrtc_attach->event); + acrtc_attach->event = NULL; + drm_crtc_vblank_put(&acrtc_attach->base); + acrtc_attach->pflip_status = AMDGPU_FLIP_NONE; + } + spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); + } + cleanup: kfree(bundle); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 0ad7704800d9..d63688b9d93d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -281,7 +281,14 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) drm_crtc_vblank_restore(crtc); } - if (dc_supports_vrr(dm->dc->ctx->dce_version)) { + /* + * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver + * vblank and pageflip completion events, so enable it whenever vblank + * is enabled. On DCE, vupdate is only needed in VRR mode. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { + rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable); + } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) { if (enable) { /* vblank irq on -> Only need vupdate irq in vrr mode */ if (amdgpu_dm_crtc_vrr_active(acrtc_state)) @@ -292,39 +299,46 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) } } - if (rc) - return rc; - - /* crtc vblank or vstartup interrupt */ - if (enable) { - rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc); - } else { - rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc); - } - if (rc) return rc; /* - * hubp surface flip interrupt - * - * We have no guarantee that the frontend index maps to the same - * backend index - some even map to more than one. - * - * TODO: Use a different interrupt or check DC itself for the mapping. + * VLINE0 (crtc_irq) and GRPH_PFLIP (pageflip_irq) are only used on + * DCE. On DCN, vblank and pageflip completion are delivered from + * VUPDATE_NO_LOCK (enabled above), so don't touch them here. */ - if (enable) { - rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc); - } else { - rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc); - } + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + /* crtc vblank or vstartup interrupt */ + if (enable) { + rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc); + } - if (rc) - return rc; + if (rc) + return rc; + + /* + * hubp surface flip interrupt + * + * We have no guarantee that the frontend index maps to the same + * backend index - some even map to more than one. + * + * TODO: Use a different interrupt or check DC itself for the mapping. + */ + if (enable) { + rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc); + } + + if (rc) + return rc; + } #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) /* crtc vline0 interrupt, only available on DCN+ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index ad30cae2af42..33347a5ba6fd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1941,89 +1941,23 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, queue_work(adev->dm.vmin_vmax_wq, &offload_work->work); } -STATIC_IFN_KUNIT void dm_vupdate_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - struct drm_device *drm_dev; - struct drm_vblank_crtc *vblank; - ktime_t frame_duration_ns, previous_timestamp; - unsigned long flags; - int vrr_active; - - acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); - - if (acrtc) { - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - drm_dev = acrtc->base.dev; - vblank = drm_crtc_vblank_crtc(&acrtc->base); - previous_timestamp = atomic64_read(&irq_params->previous_timestamp); - frame_duration_ns = vblank->time - previous_timestamp; - - if (frame_duration_ns > 0) { - trace_amdgpu_refresh_rate_track(acrtc->base.index, - frame_duration_ns, - ktime_divns(NSEC_PER_SEC, frame_duration_ns)); - atomic64_set(&irq_params->previous_timestamp, vblank->time); - } - - drm_dbg_vbl(drm_dev, - "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, - vrr_active); - - /* Core vblank handling is done here after end of front-porch in - * vrr mode, as vblank timestamping will give valid results - * while now done after front-porch. This will also deliver - * page-flip completion events that have been queued to us - * if a pageflip happened inside front-porch. - */ - if (vrr_active && acrtc->dm_irq_params.stream) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state - == VRR_STATE_ACTIVE_VARIABLE; - - amdgpu_dm_crtc_handle_vblank(acrtc); - - /* BTR processing for pre-DCE12 ASICs */ - if (adev->family < AMDGPU_FAMILY_AI) { - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - mod_freesync_handle_v_update( - adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - } - } - } -} -EXPORT_IF_KUNIT(dm_vupdate_high_irq); - /** - * dm_crtc_high_irq() - Handles CRTC interrupt - * @interrupt_params: used for determining the CRTC instance + * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling + * @adev: amdgpu device + * @acrtc: the CRTC to service + * Performs writeback completion, vblank event handling, CRC processing, VRR BTR + * updates and pageflip completion delivery. * - * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK - * event handler. + * On DCN this is driven by VUPDATE_NO_LOCK (the register latch point) from + * dm_vupdate_high_irq(); on DCE it is driven by VLINE0 at the start of vblank + * from dm_crtc_high_irq(). */ -STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) +static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) { - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; unsigned long flags; int vrr_active; - - acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); - if (!acrtc) - return; + bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0; if (acrtc->wb_conn && acrtc->wb_pending) { struct dc_stream_state *stream = acrtc->dm_irq_params.stream; @@ -2052,12 +1986,17 @@ STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) vrr_active, acrtc->dm_irq_params.active_planes); /** - * Core vblank handling at start of front-porch is only possible - * in non-vrr mode, as only there vblank timestamping will give - * valid results while done in front-porch. Otherwise defer it - * to dm_vupdate_high_irq after end of front-porch. + * Core vblank handling. + * + * On DCN this handler runs at VUPDATE_NO_LOCK, the register latch + * point, which is the correct place to timestamp both VRR and non-VRR + * vblanks. + * + * On DCE this handler runs at the start of front-porch, where only + * non-VRR timestamping is valid; VRR vblank is deferred to + * dm_vupdate_high_irq() after end of front-porch. */ - if (!vrr_active) + if (is_dcn || !vrr_active) amdgpu_dm_crtc_handle_vblank(acrtc); /** @@ -2090,18 +2029,16 @@ STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) } /* - * If there aren't any active_planes then DCH HUBP may be clock-gated. - * In that case, pageflip completion interrupts won't fire and pageflip - * completion events won't get delivered. Prevent this by sending - * pending pageflip events from here if a flip is still pending. + * Deliver pageflip completion events (DCN only). * - * If any planes are enabled, use dm_pflip_high_irq() instead, to - * avoid race conditions between flip programming and completion, - * which could cause too early flip completion events. + * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch + * point. Deliver any pending pageflip completion event from here. + * + * NOTE: This can deliver an event for a flip that was armed but not yet + * programmed into HW; that race is closed in a follow-up change by + * checking the programmed flip status. */ - if (adev->family >= AMDGPU_FAMILY_RV && - acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && - acrtc->dm_irq_params.active_planes == 0) { + if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) { if (acrtc->event) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; @@ -2112,6 +2049,105 @@ STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); } + +STATIC_IFN_KUNIT void dm_vupdate_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + struct drm_device *drm_dev; + struct drm_vblank_crtc *vblank; + ktime_t frame_duration_ns, previous_timestamp; + unsigned long flags; + int vrr_active; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); + if (!acrtc) + return; + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + drm_dev = acrtc->base.dev; + vblank = drm_crtc_vblank_crtc(&acrtc->base); + previous_timestamp = atomic64_read(&irq_params->previous_timestamp); + frame_duration_ns = vblank->time - previous_timestamp; + + if (frame_duration_ns > 0) { + trace_amdgpu_refresh_rate_track(acrtc->base.index, + frame_duration_ns, + ktime_divns(NSEC_PER_SEC, frame_duration_ns)); + atomic64_set(&irq_params->previous_timestamp, vblank->time); + } + + drm_dbg_vbl(drm_dev, + "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, + vrr_active); + + /* + * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver + * vblank and pageflip completion events; VSTARTUP and GRPH_PFLIP are + * not used. Run the full handler here. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { + dm_crtc_high_irq_handler(adev, acrtc); + return; + } + + /* DCE only below. */ + + /* Core vblank handling is done here after end of front-porch in + * vrr mode, as vblank timestamping will give valid results + * while now done after front-porch. This will also deliver + * page-flip completion events that have been queued to us + * if a pageflip happened inside front-porch. + */ + if (vrr_active && acrtc->dm_irq_params.stream) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state + == VRR_STATE_ACTIVE_VARIABLE; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + /* BTR processing for pre-DCE12 ASICs */ + if (adev->family < AMDGPU_FAMILY_AI) { + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + mod_freesync_handle_v_update( + adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + } + } +} +EXPORT_IF_KUNIT(dm_vupdate_high_irq); + +/** + * dm_crtc_high_irq() - Handles CRTC interrupt + * @interrupt_params: used for determining the CRTC instance + * + * Handles the CRTC/VSYNC interrupt by notifying DRM's VBLANK event handler. + * + * Used on DCE (VLINE0, set to vblank start). On DCN the equivalent handling is + * driven by VUPDATE_NO_LOCK in dm_vupdate_high_irq(). + */ +STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); + if (!acrtc) + return; + + dm_crtc_high_irq_handler(adev, acrtc); +} EXPORT_IF_KUNIT(dm_crtc_high_irq); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) @@ -2431,38 +2467,6 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) * for acknowledging and handling. */ - /* Use VSTARTUP interrupt */ - for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; - i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - /* Use otg vertical line interrupt */ #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { @@ -2534,37 +2538,6 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) return -ENOMEM; } - /* Use GRPH_PFLIP interrupt */ - for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; - i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - /* HPD */ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, &adev->hpd_irq); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 5a4fecdf3135..ed20e278742d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -3702,8 +3702,8 @@ static void dm_test_dcn10_register_irq_handlers_zero_crtc(struct kunit *test) * dm_test_dcn10_register_irq_handlers_one_crtc - Test DCN10 with 1 CRTC * @test: The KUnit test context * - * Exercises the VSTARTUP, VUPDATE, and PFLIP for-loop bodies with a - * fake IRQ service. + * Exercises the VUPDATE_NO_LOCK for-loop bodies with a fake IRQ service, and + * that it's the only one populated (other than VLINE0, for secure display). */ static void dm_test_dcn10_register_irq_handlers_one_crtc(struct kunit *test) { @@ -3723,18 +3723,16 @@ static void dm_test_dcn10_register_irq_handlers_one_crtc(struct kunit *test) KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), 0); - /* Verify VBLANK params were populated */ - KUNIT_EXPECT_EQ(test, (int)adev->dm.vblank_params[0].irq_src, - (int)DC_IRQ_SOURCE_VBLANK1); - KUNIT_EXPECT_PTR_EQ(test, adev->dm.vblank_params[0].adev, adev); + /* Verify VBLANK params were *not* populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vblank_params[0].irq_src, 0); + KUNIT_EXPECT_NULL(test, adev->dm.vblank_params[0].adev); /* Verify VUPDATE params were populated */ KUNIT_EXPECT_EQ(test, (int)adev->dm.vupdate_params[0].irq_src, (int)DC_IRQ_SOURCE_VUPDATE1); - /* Verify PFLIP params were populated */ - KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[0].irq_src, - (int)DC_IRQ_SOURCE_PFLIP1); + /* Verify PFLIP params were *not* populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[0].irq_src, 0); amdgpu_dm_irq_fini(adev); } From 24071402a41a56dba3e215ca89c6b5d855c678ea Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:16 -0500 Subject: [PATCH 111/155] drm/radeon: Modernize VFCT error handling Clean up radeon_acpi_vfct_bios() logging: - Replace DRM_ERROR with dev_warn tied to the PCI device - Use unsigned int rather than bare unsigned for the offset A malformed or missing VFCT table is not fatal: radeon falls back to the other BIOS fetch methods, so warn rather than error on these paths. The goto out label is retained: acpi_get_table() takes a reference on the table (incrementing its validation_count and mapping it), which must be released with a paired acpi_put_table() on every exit path. Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-4-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_bios.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 3a8c5199a0fe..c6df799c3cf4 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -602,14 +602,14 @@ static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) struct acpi_table_header *hdr; acpi_size tbl_size; UEFI_ACPI_VFCT *vfct; - unsigned offset; + unsigned int offset; bool r = false; if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr))) return false; tbl_size = hdr->length; if (tbl_size < sizeof(UEFI_ACPI_VFCT)) { - DRM_ERROR("ACPI VFCT table present but broken (too short #1)\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT table present but broken (too short #1),skipping\n"); goto out; } @@ -622,13 +622,13 @@ static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) offset += sizeof(VFCT_IMAGE_HEADER); if (offset > tbl_size) { - DRM_ERROR("ACPI VFCT image header truncated\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT image header truncated,skipping\n"); goto out; } offset += vhdr->ImageLength; if (offset > tbl_size) { - DRM_ERROR("ACPI VFCT image truncated\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT image truncated,skipping\n"); goto out; } @@ -648,7 +648,7 @@ static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) } } - DRM_ERROR("ACPI VFCT table present but broken (too short #2)\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT table present but broken (too short #2),skipping\n"); out: acpi_put_table(hdr); From 4059e2f02c8a2a51ba78860775877d721df6a055 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:17 -0500 Subject: [PATCH 112/155] drm/radeon: Fix VFCT bus number matching with soft filter On systems where PCI bus renumbering occurs (e.g. pci=realloc, resource conflicts), the runtime bus number may differ from the BIOS POST bus number recorded in the VFCT table. This causes radeon_acpi_vfct_bios() to fail finding the VBIOS even though the correct device entry exists. Introduce radeon_acpi_vfct_match() which treats the bus number as a soft filter: vendor/device/function identity is the hard requirement, while exact bus match is the preferred path. When bus numbers disagree but device identity matches, accept the VFCT entry and log a dev_notice for diagnostics. This mirrors the equivalent amdgpu change. Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-5-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_bios.c | 45 ++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index c6df799c3cf4..cc10880af096 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -596,6 +596,45 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev) return legacy_read_disabled_bios(rdev); } +/** + * radeon_acpi_vfct_match() - Check if a VFCT entry matches the device + * @rdev: Radeon device + * @vhdr: VFCT image header to check + * + * VFCT entries contain the PCI bus number as recorded during BIOS POST. + * On systems where the kernel renumbers PCI buses (e.g. pci=realloc or + * resource conflicts), the runtime bus number may differ from the POST + * value. Match by device identity (vendor + device + function) and use + * the bus number as a preference: exact bus match is preferred, but when + * the bus numbers disagree we accept the entry if the device identity + * matches. + * + * Returns: 0 on match, -ENODEV on no match + */ +static int radeon_acpi_vfct_match(struct radeon_device *rdev, + VFCT_IMAGE_HEADER *vhdr) +{ + /* Vendor and device IDs must always match */ + if (vhdr->VendorID != rdev->pdev->vendor || + vhdr->DeviceID != rdev->pdev->device) + return -ENODEV; + + if (vhdr->PCIDevice != PCI_SLOT(rdev->pdev->devfn) || + vhdr->PCIFunction != PCI_FUNC(rdev->pdev->devfn)) + return -ENODEV; + + /* Exact bus number match - preferred */ + if (vhdr->PCIBus == rdev->pdev->bus->number) + return 0; + + /* Bus mismatch but device identity matches (PCI renumbering case) */ + dev_notice(&rdev->pdev->dev, + "VFCT bus number mismatch: table %u != runtime %u, matching by device identity (vendor 0x%04x device 0x%04x)\n", + vhdr->PCIBus, rdev->pdev->bus->number, + rdev->pdev->vendor, rdev->pdev->device); + return 0; +} + #ifdef CONFIG_ACPI static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) { @@ -633,11 +672,7 @@ static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) } if (vhdr->ImageLength && - vhdr->PCIBus == rdev->pdev->bus->number && - vhdr->PCIDevice == PCI_SLOT(rdev->pdev->devfn) && - vhdr->PCIFunction == PCI_FUNC(rdev->pdev->devfn) && - vhdr->VendorID == rdev->pdev->vendor && - vhdr->DeviceID == rdev->pdev->device) { + !radeon_acpi_vfct_match(rdev, vhdr)) { rdev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); From a9c196b3f135fe9b165c5e75b3a24eadcc22fd6a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:18 -0500 Subject: [PATCH 113/155] drm/radeon: Validate VBIOS signature in VFCT path The VFCT path accepted whatever kmemdup() returned without checking that the copied image is a valid VBIOS. Every other radeon BIOS fetch path verifies the 0x55 0xaa signature before trusting the image; the VFCT path is the odd one out. Check the signature after copying the image and reject it (freeing the buffer) if it does not match, matching the amdgpu VFCT path which validates via check_atom_bios(). Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-6-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_bios.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index cc10880af096..215e47c94d29 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -676,9 +676,14 @@ static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) rdev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); - if (rdev->bios) - r = true; + if (!rdev->bios || + rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { + kfree(rdev->bios); + rdev->bios = NULL; + goto out; + } + r = true; goto out; } } From 8b4bd60402a402cd3b451d11cd710c8d25c3acee Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Jul 2026 12:00:04 -0500 Subject: [PATCH 114/155] drm/amdgpu/display: Add amdgpu_display.c documentation Add kernel-doc references for amdgpu_display.c to the display manager documentation. This pulls in documentation for display core functions like the hotplug work handler, and the adaptive backlight modulation property. The :internal: directive automatically includes all function documentation, while the explicit :doc: directive captures the property documentation that :internal: doesn't pull in. Fixes: 1454642960b0 ("drm/amd: Re-introduce property to control adaptive backlight modulation") Reviewed-by: Leo Li Link: https://patch.msgid.link/20260701170004.465737-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- Documentation/gpu/amdgpu/display/display-manager.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/gpu/amdgpu/display/display-manager.rst b/Documentation/gpu/amdgpu/display/display-manager.rst index b269ff3f7a54..f6de9e7779e2 100644 --- a/Documentation/gpu/amdgpu/display/display-manager.rst +++ b/Documentation/gpu/amdgpu/display/display-manager.rst @@ -32,6 +32,9 @@ Interrupts .. kernel-doc:: drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c :functions: register_hpd_handlers dm_crtc_high_irq dm_pflip_high_irq +.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_display.c + :functions: amdgpu_display_hotplug_work_func + Atomic Implementation ===================== @@ -178,3 +181,9 @@ following path: 2. On DC interface, :c:type:`struct mpcc_blnd_cfg ` programs the MPCC blend configuration considering the :c:type:`dc_plane_info ` input from DPP. + +Display Properties +================== + +.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_display.c + :doc: property for adaptive backlight modulation From f64a9be5653689ff43e148cd8a6483077488c8e5 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Fri, 12 Jun 2026 15:49:03 -0400 Subject: [PATCH 115/155] drm/amd/display: check GRPH_FLIP status before sending event [Why] After unifying DCN interrupt sources under VUPDATE_NO_LOCK, we have two remaining issues to clean up: 1. On DCN, flip completion is now delivered from VUPDATE_NO_LOCK (dm_crtc_high_irq_handler) instead of GRPH_PFLIP. But VUPDATE_NO_LOCK fires every frame, regardless of whether a flip has latched. 2. There is a window during commit where a flip is armed (pflip_status = SUBMITTED) but not yet programmed into HW. If the VUPDATE_NO_LOCK fires in that window, its handler would deliver a flip event to userspace before HW has latched to it. If userspace then renders to what it believes is now the back buffer (but HW is still latched to it!), it will cause display corruption. This issue seemed to have been introduced by: commit 1159898a88db ("drm/amd/display: Handle commit plane with no FB.") Enabling replay or psr extended the duration of this window, and hence made corruption more likely to be observed. [How] * Move acrtc->event/pflip_status arming to after update_planes_and_stream_adapter() has programmed the flip into HW. This closes the window where pflip_status is SUBMITTED but the flip is not yet programmed. * Add dc_get_flip_pending_on_otg(), which reads the HUBP flip-pending status straight from HW for the pipe(s) bound to an OTG instance. It is keyed only by otg_inst and does not take or mutate a dc_plane_state, so it is safe to call from the OTG interrupt handler without racing a concurrent commit that may be modifying plane state. * Optimistically query for flip-pending after programming, in the event that HW latched to the new fb between programming start and arming event. If it latched, send the vblank event immediately, rather than wait for the next vblank IRQ. * In the VUPDATE_NO_LOCK handler, only deliver flip completion once dc_get_flip_pending_on_otg() reports the flip is no longer pending. Otherwise leave the flip armed and retry on the next vupdate. * For DCE, maintain the existing behavior of arming flips before programming, and relying on GRPH_FLIP to fire at HW latch. v2: * Drop flip_programmed completion object, instead move event/pflip_status arming after programming. * For DCN, optimistically query for flip pending immediately after programming, and if it latched, send event right away. v3: * Fix event timestamps on optimistic flip latch detection, where it's possible for it to run *before* the vupdate IRQ updates the timestamp. * Add more docstrings for DCN vblank handling. * Clean up if conditions in dm_arm_vblank_event(). * Code style cleanup on braces surrounding multi-line statements. Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations") Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787 Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141 Assisted-by: Copilot:claude-opus-4.8 Tested-by: Mario Limonciello (AMD) Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 164 ++++++++++++++---- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 27 ++- drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++++ drivers/gpu/drm/amd/display/dc/dc.h | 1 + 4 files changed, 197 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 53f18ea65644..b05e7d4d53a9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4146,6 +4146,28 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm, } } +static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, + struct dm_crtc_state *acrtc_state, + bool pflip_update, + bool cursor_update) +{ + assert_spin_locked(&acrtc->base.dev->event_lock); + + if (!acrtc->base.state->event || acrtc_state->active_planes == 0) + return; + + if (pflip_update) { + drm_crtc_vblank_get(&acrtc->base); + WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); + /* Arm flip completion handling and event delivery after programming. */ + prepare_flip_isr(acrtc); + } else if (cursor_update) { + drm_crtc_vblank_get(&acrtc->base); + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + } +} + static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, struct drm_device *dev, struct amdgpu_display_manager *dm, @@ -4169,6 +4191,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bool cursor_update = false; bool pflip_present = false; bool immediate_flip = false; + bool flip_latched_during_prog = false; bool dirty_rects_changed = false; bool updated_planes_and_streams = false; struct { @@ -4401,39 +4424,24 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, usleep_range(1000, 1100); } - /** - * Prepare the flip event for the pageflip interrupt to handle. - * - * This only works in the case where we've already turned on the - * appropriate hardware blocks (eg. HUBP) so in the transition case - * from 0 -> n planes we have to skip a hardware generated event - * and rely on sending it from software. - */ - if (acrtc_attach->base.state->event && - acrtc_state->active_planes > 0) { - drm_crtc_vblank_get(pcrtc); - - spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - - WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE); - prepare_flip_isr(acrtc_attach); - - spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); - } - if (acrtc_state->stream) { if (acrtc_state->freesync_vrr_info_changed) bundle->stream_update.vrr_infopacket = &acrtc_state->stream->vrr_infopacket; } - } else if (cursor_update && acrtc_state->active_planes > 0) { - spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - if (acrtc_attach->base.state->event) { - drm_crtc_vblank_get(pcrtc); - acrtc_attach->event = acrtc_attach->base.state->event; - acrtc_attach->base.state->event = NULL; + } + + /* + * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for + * event delivery. Only GRPH_FLIP handler can send pflip events, and it + * only fires if HW latched to the flip. Maintain legacy behavior by + * arming event before programming. + */ + if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { + scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { + dm_arm_vblank_event(acrtc_attach, acrtc_state, + pflip_present, cursor_update); } - spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } /* Update the planes if changed or disable if we don't have any. */ @@ -4526,17 +4534,103 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, amdgpu_dm_commit_cursors(state); /* - * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK. - * However, an immediate (tearing / async) flip is latched by HW right - * away and does not wait for the next vupdate, so deliver its - * completion event here after programming. + * DCN specific vblank handling + * ============================ * - * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so - * this is DCN-only. + * With the event_lock held, arm the vblank event, and determine whether + * deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch + * point) handler. Do this *after* programming so that the IRQ handler + * will not deliver the event before HW laches onto the programmed + * values: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * arm_vblank_event() + * vupdate() + * vupdate_handler() + * cook_timestamp() + * # prev flip already latched, + * # so flip_latched == true. + * if event_armed && flip_latched: + * send_vblank_event() + * # sent before latch, **BAD!** + * hw_program() + * vupdate() + * **latch** + * + * There's a consequence of arming after: it's possible for HW to latch + * between start of HW programming and acrtc->event/pflip_status arming. + * When this happens, the IRQ handler will send the event on the next + * immediate latch point, even though HW has already latched. This is + * handled by optimistically checking for HW latch after programming, + * and if latched, send the event immediately: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * hw_program() + * vupdate() + * **latch** + * vupdate_handler() + * cook_timestamp() + * # event_armed == false + * # **no event sent!** + * arm_vblank_event() + * if flip_latched: + * **send_vblank_event()** + * disarm_vblank_event() + * + * The IRQ handler is expected to cook the timestamp, but we need to + * cook the timestamp before optimistic sending as well. That's because + * the following sequence is possible: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * hw_program() + * arm_vblank_event() + * vupdate() + * **latch** + * if flip_latched: + * # Need cook before send! + * **cook_timestamp()** + * send_vblank_event() + * disarm_vblank_event() + * vupdate_handler() + * cook_timestamp() + * # event_armed == false + * # no event sent! + * + * Cooking twice is OK, since DRM scanout accurate timestamps report A) + * the previous vactive start if currently in vactive, or B) the next + * vactive start if currently in vblank (see &get_vblank_counter). 'A)' + * is what we want for the optimistic send, and for 'B)', we'll cook a + * timestamp no later than the next IRQ handler run. + * + * The more correct fix is to wrap programming and arming with the + * event_lock and thus serializing it with the IRQ handler. However, + * there are various sleep-waits within + * update_planes_and_stream_adapter() that makes spin locking illegal. + * And on full updates, it can take 1-2 frame-times to return (see + * commit_planes_for_stream). + * + * On DCE, GRPH_PFLIP IRQ is used and takes care of this. */ - if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + + if (updated_planes_and_streams) { + flip_latched_during_prog = + !dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst); + } + + dm_arm_vblank_event(acrtc_attach, acrtc_state, + pflip_present, cursor_update); + + /* + * Deliver the event immediately on immediate flip, or on a + * update that has already latched. + */ + if ((immediate_flip || flip_latched_during_prog) && + acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && acrtc_attach->event) { drm_crtc_accurate_vblank_count(&acrtc_attach->base); drm_crtc_send_vblank_event(&acrtc_attach->base, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 33347a5ba6fd..ae3f81f53074 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -2032,13 +2032,30 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, * Deliver pageflip completion events (DCN only). * * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch - * point. Deliver any pending pageflip completion event from here. + * point. Deliver any pending pageflip completion event from here, + * once HW has consumed the new address (the OTG no longer reports a + * pending flip). * - * NOTE: This can deliver an event for a flip that was armed but not yet - * programmed into HW; that race is closed in a follow-up change by - * checking the programmed flip status. + * Also handle the case here where there aren't any active planes and + * DCN HUBP may be clock-gated, so the flip-pending status may be + * undefined. */ - if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) { + if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->event) { + + if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; + drm_crtc_vblank_put(&acrtc->base); + acrtc->pflip_status = AMDGPU_FLIP_NONE; + } + /* + * If the flip is still pending, leave it armed and + * retry on the next vupdate. + */ + } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->dm_irq_params.active_planes == 0) { + if (acrtc->event) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index dab19a360b02..8d77158229f1 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6349,6 +6349,51 @@ void dc_disable_dangling_timing_generators(struct dc *dc) } } +/* + * dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG + * + * @dc: display core context @otg_inst: OTG instance to query + * + * Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst, + * returning true if any of them has not yet latched its programmed surface + * address. + * + * Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state, + * so it is safe to call from interrupt context without racing a concurrent + * commit that may be updating plane state. + * + * Return: true if a flip is still pending on the OTG, false otherwise. + */ +bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst) +{ + bool flip_pending = false; + unsigned int i; + + if (!dc || !dc->current_state) + return false; + + dc_exit_ips_for_hw_access(dc); + + for (i = 0; i < dc->res_pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; + struct hubp *hubp = pipe_ctx->plane_res.hubp; + + if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg) + continue; + + if (pipe_ctx->stream_res.tg->inst != otg_inst) + continue; + + if (hubp && hubp->funcs->hubp_is_flip_pending && + hubp->funcs->hubp_is_flip_pending(hubp)) { + flip_pending = true; + break; + } + } + + return flip_pending; +} + void dc_power_down_on_boot(struct dc *dc) { if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index a3e1eeff6e78..e1b7d359d917 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -2998,6 +2998,7 @@ enum dc_irq_source dc_interrupt_to_irq_source( uint32_t ext_id); bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable); void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src); +bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst); enum dc_irq_source dc_get_hpd_irq_source_at_index( struct dc *dc, uint32_t link_index); From ee186ae23ab0f7f9103b21cc04c4ef6155b4e0f5 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 4 May 2026 14:09:49 -0400 Subject: [PATCH 116/155] Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs" Now that proper fixes have been found, let's revert this workaround. This reverts commit a1fc7bf6677eb547167cb72b3bcafdc34b976692. Tested-by: Mario Limonciello (AMD) Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index b05e7d4d53a9..656708596c18 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3546,21 +3546,9 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, if (acrtc_state) { timing = &acrtc_state->stream->timing; - if (amdgpu_ip_version(adev, DCE_HWIP, 0) >= - IP_VERSION(3, 2, 0) && - !(adev->flags & AMD_IS_APU)) { - /* - * DGPUs NV3x and newer that support idle optimizations - * experience intermittent flip-done timeouts on cursor - * updates. Restore 5s offdelay behavior for now. - * - * Discussion on the issue: - * https://lore.kernel.org/amd-gfx/20260217191632.1243826-1-sysdadmin@m1k.cloud/ - */ - config.offdelay_ms = 5000; - config.disable_immediate = false; - } else if (amdgpu_ip_version(adev, DCE_HWIP, 0) < - IP_VERSION(3, 5, 0)) { + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < + IP_VERSION(3, 5, 0) || + !(adev->flags & AMD_IS_APU)) { /* * Older HW and DGPU have issues with instant off; * use a 2 frame offdelay. From fed7aa36d79802c3e02acd05aeae8b0a877e47c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:21:07 +0200 Subject: [PATCH 117/155] drm/amdgpu: Print vmid, pasid and more task info in devcoredump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are in the dmesg logs but are missing from devcoredumps. Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 7 ++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h | 1 + 2 files changed, 7 insertions(+), 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 4fd0df3aa70d..aaf091b7f9d0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -356,10 +356,14 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf drm_printf(&p, "kernel: %s\n", init_utsname()->release); drm_printf(&p, "module: " KBUILD_MODNAME "\n"); drm_printf(&p, "time: %ptSp\n", &coredump->reset_time); + drm_printf(&p, "pasid: %u\n", coredump->pasid); + drm_printf(&p, "vmid: %u\n", coredump->vmid); if (coredump->reset_task_info.task.pid) - drm_printf(&p, "process_name: %s PID: %d\n", + drm_printf(&p, "process_name: %s TGID: %d thread: %s PID: %d\n", coredump->reset_task_info.process_name, + coredump->reset_task_info.tgid, + coredump->reset_task_info.task.comm, coredump->reset_task_info.task.pid); /* SOC Information */ @@ -563,6 +567,7 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, amdgpu_vm_put_task_info(ti); } coredump->pasid = job->pasid; + coredump->vmid = job->vmid; coredump->num_ibs = job->num_ibs; for (i = 0; i < job->num_ibs; ++i) { coredump->ibs[i].gpu_addr = job->ibs[i].gpu_addr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h index 2371e20fc68b..63f27337c09a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h @@ -63,6 +63,7 @@ struct amdgpu_coredump_info { char *formatted; unsigned int pasid; + unsigned int vmid; int num_ibs; struct amdgpu_coredump_ib_info ibs[] __counted_by(num_ibs); }; From 4e2c0821509fed754e8c31d5053d152fbb3484a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:21:08 +0200 Subject: [PATCH 118/155] drm/amdgpu: Reserve space for IB contents in devcoredumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the contents of IBs are abruptly cut off and don't show the full contents. This patch makes sure to reserve space for those contents too so they may be printed. Signed-off-by: Timur Kristóf Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index aaf091b7f9d0..39b2a4c0e011 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -235,6 +235,9 @@ amdgpu_devcoredump_print_ibs(struct drm_printer *p, drm_printf(p, "\nIB #%d 0x%llx %d dw\n", i, coredump->ibs[i].gpu_addr, coredump->ibs[i].ib_size_dw); + + for (int j = 0; j < coredump->ibs[i].ib_size_dw; j++) + drm_printf(p, "0xffffffff\n"); } return; } From 0421fc6ab3a8514e99156ff3c2cee13ee9af3fa7 Mon Sep 17 00:00:00 2001 From: yanglinlin Date: Mon, 13 Jul 2026 11:12:28 +0800 Subject: [PATCH 119/155] drm/amd/display: fix __udivdi3 link error When compiling the AMDGPU display driver for 32-bit architectures, the linker reports undefined reference to `__udivdi3` in functions get_dp_dto_frequency_100hz() and dcn401_get_dp_dto_frequency_100hz(). This is because the code uses 64-bit division (/) on 32-bit systems, which GCC cannot handle directly and instead tries to call the missing __udivdi3 helper function. Replace the raw division with div_u64(), the kernel's standard 64-bit division helper, to avoid the link error. Signed-off-by: Linlin Yang Reported-by: k2ci Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c index 7c293917e6fd..ecb8493ec523 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c @@ -1229,9 +1229,9 @@ static bool get_dp_dto_frequency_100hz( */ modulo_hz = REG_READ(MODULO[inst]); if (modulo_hz) { - temp = div_u64((uint64_t)clock_hz * dp_dto_ref_khz * 10, modulo_hz); - ASSERT(temp / 100 <= 0xFFFFFFFFUL); - *pixel_clk_100hz = (unsigned int)(temp / 100); + temp = clock_hz * dp_dto_ref_khz * 10; + ASSERT(temp <= UINT_MAX * modulo_hz * 100ULL); + *pixel_clk_100hz = div_u64(temp, modulo_hz * 100); } else *pixel_clk_100hz = 0; } else { @@ -1285,13 +1285,12 @@ static bool dcn401_get_dp_dto_frequency_100hz(const struct clock_source *clock_s * - target pix_clk_hz = (DPDTO INTEGER * DPDTO MODULO + DPDTO PHASE) */ temp = (unsigned long long)dp_dto_integer * modulo_hz + phase_hz; - - if (temp / 100 > 0xFFFFFFFFUL) { + if (temp > (UINT_MAX * 100ULL)) { /* pixel rate 100hz should never be this high, if it is, throw an assert and return 0 */ BREAK_TO_DEBUGGER(); *pixel_clk_100hz = 0; } else { - *pixel_clk_100hz = (unsigned int)(temp / 100); + *pixel_clk_100hz = div_u64(temp, 100); } return true; From d34acad064ee7d82bd18f5d87592c422d4d323ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Mon, 13 Jul 2026 08:14:43 +0200 Subject: [PATCH 120/155] drm/amd/pm/ci: Don't disable MCLK DPM on Bonaire 0x6658 (R7 260X) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old radeon driver has a documented workaround in ci_dpm.c which claims that Bonaire 0x6658 with old memory controller firmware is unstable with MCLK DPM, so as a precaution I disabled MCLK DPM on this ASIC in amdgpu. Note that the old MC firmware is not actually used with amdgpu, but in theory it's possible that the VBIOS sets up the ASIC with an old MC firmware that is already running when amdgpu initializes (in which case amdgpu doesn't load its own firmware). What I expected to happen is that the GPU would simply use its maximum memory clock, and indeed this is what seemed to happen according to amdgpu_pm_info which reads the current MCLK value from the SMU. However, some users reported a huge perf regression and upon a closer look it seems that the GPU seems to not actually use the highest MCLK value, despite the SMU reporting that it does. Let's not disable MCLK DPM on Bonaire 0x6658 (R7 260X). Keep MCLK DPM disabled on R9 M380 in the 2015 iMac because that still hangs if we enable it. Fixes: 9851f29cb06c ("drm/amd/pm/ci: Disable MCLK DPM on problematic CI ASICs") Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c index 1d6e30269d56..4d553be56396 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c @@ -106,11 +106,8 @@ int hwmgr_early_init(struct pp_hwmgr *hwmgr) hwmgr->od_enabled = false; switch (hwmgr->chip_id) { case CHIP_BONAIRE: - /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM - * R7 260X cards with old MC ucode: MCLK DPM is unstable - */ - if (adev->pdev->subsystem_vendor == 0x106B || - adev->pdev->device == 0x6658) { + /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM */ + if (adev->pdev->subsystem_vendor == 0x106B) { dev_info(adev->dev, "disabling MCLK DPM on quirky ASIC"); adev->pm.pp_feature &= ~PP_MCLK_DPM_MASK; hwmgr->feature_mask &= ~PP_MCLK_DPM_MASK; From 2f79f0130f828cf26fe2dcf45291821616af7b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:34:35 +0200 Subject: [PATCH 121/155] drm/amd/display: Set native cursor mode for disabled CRTCs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always set native cursor mode when the CRTC is disabled, to make sure it doesn't cause atomic commits to fail when they are trying to disable the CRTC. Fixes: 41af6215cdbc ("drm/amd/display: Reject cursor plane on DCE when scaled differently than primary") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5432 Cc: Leo Li Cc: Michel Dänzer Signed-off-by: Timur Kristóf Tested-by: Viktor Jägersküpper Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 656708596c18..0e76a3799b85 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6760,10 +6760,15 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, /* Overlay cursor not supported on HW before DCN * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions * as previous DCN generations, so enable native mode on DCN401/420 + * + * Always set native cursor mode when the CRTC is disabled, + * to make sure it doesn't cause atomic commits to fail when + * they are trying to disable the CRTC. */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1)) { + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || + !dm_crtc_state->base.enable) { *cursor_mode = DM_CURSOR_NATIVE_MODE; return 0; } From 28ec64943e3ee4d9b8d30cea61e380f1429953a8 Mon Sep 17 00:00:00 2001 From: Andriy Korud Date: Fri, 10 Jul 2026 12:52:26 +0200 Subject: [PATCH 122/155] drm/amd/display: dce100: skip non-DP stream encoders for DP MST On DCE8-class ASICs (e.g. Bonaire), the resource pool contains digital DIG stream encoders plus one analog DAC encoder. When assigning a stream encoder for a second DisplayPort MST stream, if the preferred digital encoder is already acquired, dce100_find_first_free_match_stream_enc_for_link() falls back to the first free pool entry. That entry may be the analog encoder, whose funcs table lacks DP hooks such as dp_set_stream_attribute. The subsequent atomic commit then dereferences NULL function pointers in link_set_dpms_on() and crashes. Skip encoders without dp_set_stream_attribute when the stream uses a DP signal (including MST). Use dc_is_dp_signal(stream->signal) for the MST fallback path instead of checking only the link connector signal. Tested on: - GPU: AMD Radeon R7 260X (Bonaire / DCE8) - Board: Supermicro C9X299-PG300 - Setup: DP MST daisy chain, hotplug second monitor or have it connected on boot - Kernel: 7.1.3 (issue observed since 6.19) - Result: kernel oops without patch; dual monitors stable with patch Signed-off-by: Andriy Korud Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5162 Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/resource/dce100/dce100_resource.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c index b92d4f378d60..97ea22af5d2b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c @@ -992,6 +992,11 @@ struct stream_encoder *dce100_find_first_free_match_stream_enc_for_link( for (i = 0; i < pool->stream_enc_count; i++) { if (!res_ctx->is_stream_enc_acquired[i] && pool->stream_enc[i]) { + /* DP/MST needs a digital encoder; skip analog/no-DP encoders */ + if (dc_is_dp_signal(stream->signal) && + (!pool->stream_enc[i]->funcs || + !pool->stream_enc[i]->funcs->dp_set_stream_attribute)) + continue; /* Store first available for MST second display * in daisy chain use case */ @@ -1014,7 +1019,7 @@ struct stream_encoder *dce100_find_first_free_match_stream_enc_for_link( * required for non DP connectors. */ - if (j >= 0 && link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) + if (j >= 0 && dc_is_dp_signal(stream->signal)) return pool->stream_enc[j]; return NULL; From 8e75e90d3a402cd7c7d3506d9e5407a8bc92481e Mon Sep 17 00:00:00 2001 From: Venkat Rao Bagalkote Date: Sat, 11 Jul 2026 08:59:56 +0530 Subject: [PATCH 123/155] drm/amd/display: Shorten KUnit exported symbol names The KUnit exported helpers amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() exceed MODULE_NAME_LEN and cause modpost to fail with: ERROR: modpost: too long symbol "amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers" Shorten the helper names while preserving their functionality. Reported-by: Venkat Rao Bagalkote Link: https://lore.kernel.org/all/fde3656e-9e22-4e4c-937f-7e8cb918da6b@linux.ibm.com/ Signed-off-by: Venkat Rao Bagalkote Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 12 ++++++------ .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h | 4 ++-- .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 9a34e0014084..26f35434a92f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -328,7 +328,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_plane_validate_dcc); -STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -378,9 +378,9 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(s return ret; } -EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers); +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers); -STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -419,7 +419,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( return ret; } -EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers); +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers); static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *adev, uint64_t **mods, @@ -927,14 +927,14 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, } if (adev->family == AMDGPU_FAMILY_GC_12_0_0) { - ret = amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(adev, afb, format, + ret = amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(adev, afb, format, rotation, plane_size, tiling_info, dcc, address); if (ret) return ret; } else if (adev->family >= AMDGPU_FAMILY_AI) { - ret = amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(adev, afb, format, + ret = amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(adev, afb, format, rotation, plane_size, tiling_info, dcc, address); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h index ad22324a5d2e..365e306a52e7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h @@ -92,7 +92,7 @@ int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, const struct dc_plane_cap *plane_cap, uint32_t *formats, int max_formats); -int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, +int amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -100,7 +100,7 @@ int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_devi struct dc_tiling_info *tiling_info, struct dc_plane_dcc_param *dcc, struct dc_plane_address *address); -int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, +int amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index df69e2235e24..a751e67cadf1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -615,7 +615,7 @@ static void dm_test_fill_gfx12_plane_attributes_from_modifiers(struct kunit *tes plane_size.surface_size.height = 1080; KUNIT_EXPECT_EQ(test, - amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( + amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers( adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), 0); @@ -659,7 +659,7 @@ static void dm_test_fill_gfx9_plane_attributes_from_modifiers(struct kunit *test afb->base.modifier = DRM_FORMAT_MOD_LINEAR; KUNIT_EXPECT_EQ(test, - amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers( + amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers( adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), 0); @@ -1379,7 +1379,7 @@ static int dm_test_gfx9_attrs(struct amdgpu_device *adev, struct dc_plane_dcc_param *dcc, struct dc_plane_address *address) { - return amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(adev, + return amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, plane_size, tiling_info, dcc, address); } @@ -1391,7 +1391,7 @@ static int dm_test_gfx12_attrs(struct amdgpu_device *adev, struct dc_plane_dcc_param *dcc, struct dc_plane_address *address) { - return amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(adev, + return amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, plane_size, tiling_info, dcc, address); } @@ -3210,12 +3210,12 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { /* amdgpu_dm_plane_format_mod_supported() */ KUNIT_CASE(dm_test_format_mod_supported), KUNIT_CASE(dm_test_format_mod_supported_d_swizzle_reject), - /* amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() */ + /* amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block0), KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block_unconstrained), KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_validate_fails), - /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ + /* amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_from_modifiers), KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc), KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_validate_fails), From bd018d36171a695952c6d391471c279c9e05c8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 12 Jul 2026 19:39:26 +0200 Subject: [PATCH 124/155] drm/amd/pm/si: Don't schedule thermal work when queue isn't initialized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When DPM is turned off with the amdgpu.dpm=0 module parameter, the thermal work queue isn't initialized so we shouldn't schedule any work on it. Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8079da7c5335..9d837d23d3a7 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -7689,7 +7689,7 @@ static int si_dpm_process_interrupt(struct amdgpu_device *adev, break; } - if (queue_thermal) + if (queue_thermal && amdgpu_dpm) schedule_work(&adev->pm.dpm.thermal.work); return 0; From 358dd0a9ce66d898fa934887385327547d599d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 12 Jul 2026 19:39:27 +0200 Subject: [PATCH 125/155] drm/amd/pm/si: Fix AC/DC switch notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were two mistakes in the previous implementation: The check for ATOM_PP_PLATFORM_CAP_HARDWAREDC should be inverted. We recently learned that the kernel should send PPSMC_MSG_RunningOnAC when the flag is set, and not the other way around. The clocks also need to be recomputed, because the code in the si_apply_state_adjust_rules() function selects different limits on AC and DC. Fixes: 2d071f6457af ("drm/amd/pm/si: Notify the SMC when switching to AC") 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 | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 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 9d837d23d3a7..012227c70600 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -3892,13 +3892,16 @@ static void si_notify_hw_of_powersource(void *handle) { struct amdgpu_device *adev = (struct amdgpu_device *)handle; - /* 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) + /* + * Check if the platform already manages the AC/DC switch via dedicated GPIO. + * Otherwise SMU automatically notices DC, but needs to be notified of AC. + */ + if (adev->pm.ac_power && + (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC)) amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC); + + /* Recompute clocks with updated max_limits. */ + amdgpu_legacy_dpm_compute_clocks(adev); } static PPSMC_Result si_send_msg_to_smc_with_parameter(struct amdgpu_device *adev, From 12f86950a9a33eb4b0da49d0f3a76d12439bf349 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Wed, 1 Jul 2026 16:35:22 +0000 Subject: [PATCH 126/155] drm/amd/pm: add jpeg/vcn busy to smu_v15_0_8 gpu metrics Add jpeg_busy and vcn_busy population to smu_v15_0_8 gpu metrics Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index 2105a1d7bb34..227cd86e13e8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -1766,12 +1766,17 @@ static ssize_t smu_v15_0_8_get_gpu_metrics(struct smu_context *smu, void **table idx++; } - /* Per-VCN clocks */ + /* Per-VCN clocks and busy */ for (i = 0; i < adev->vcn.num_vcn_inst; ++i) { inst = GET_INST(VCN, i); if (inst >= 0) { gpu_metrics->current_vclk0[i] = SMUQ10_ROUND(metrics->VclkFrequency[inst]); gpu_metrics->current_dclk0[i] = SMUQ10_ROUND(metrics->DclkFrequency[inst]); + gpu_metrics->vcn_busy[i] = SMUQ10_ROUND(metrics->VcnBusy[inst]); + for (j = 0; j < NUM_JPEG_RINGS_FW; ++j) + gpu_metrics->jpeg_busy[(i * NUM_JPEG_RINGS_FW) + j] = + SMUQ10_ROUND(metrics->JpegBusy[(inst * NUM_JPEG_RINGS_FW) + + j]); } } From bc639a9eadc75822f7f15a4315c198a4b5513bd2 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 3 Jun 2026 15:41:28 -0400 Subject: [PATCH 127/155] drm/amdgpu: always emit the job vm fence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need the fence to reemit the gds switch or spm update after a queue reset. Fixes: a17ef941212b ("drm/amdgpu: rework ring reset backup and reemit v9") Cc: timur.kristof@gmail.com Cc: christian.koenig@amd.com Reviewed-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index f1dcf4f5bb78..157d4168907c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -855,12 +855,10 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, job->oa_size); } - if (vm_flush_needed || pasid_mapping_needed || cleaner_shader_needed) { - amdgpu_fence_emit(ring, job->hw_vm_fence, 0); - fence = &job->hw_vm_fence->base; - /* get a ref for the job */ - dma_fence_get(fence); - } + amdgpu_fence_emit(ring, job->hw_vm_fence, 0); + fence = &job->hw_vm_fence->base; + /* get a ref for the job */ + dma_fence_get(fence); if (vm_flush_needed) { mutex_lock(&id_mgr->lock); From 9ceb4e034a327a04155f32f1cd1a5031dfa5fe02 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 22:15:20 -0500 Subject: [PATCH 128/155] drm/amdgpu: Disable PCIe dynamic speed switching on Ryzen Pinnacle Ridge AMD Ryzen Pinnacle Ridge (Zen+, family 0x17 model 0x08) CPUs have PCI controllers that don't support PCIe dynamic speed switching, causing system freezes during GPU initialization when enabled. Disable dynamic speed switching when this CPU is detected. Assisted-by: Claude:sonnet Fixes: 466a7d115326 ("drm/amd: Use the first non-dGPU PCI device for BW limits") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5436 Reviewed-by: Lijo Lazar Link: https://patch.msgid.link/20260709031520.841611-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index b39ec5ed6242..fa97281f6c4c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1222,6 +1222,15 @@ static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device if (c->x86_vendor == X86_VENDOR_INTEL) return false; + + /* + * AMD Ryzen Pinnacle Ridge (Zen+, family 0x17 model 0x08) CPUs don't + * support PCIe dynamic speed switching. + * https://gitlab.freedesktop.org/drm/amd/-/work_items/5436 + */ + if (c->x86_vendor == X86_VENDOR_AMD && c->x86 == 0x17 && + c->x86_model == 0x08) + return false; #endif return true; } From 40c58b4fb5bd823d61f998b37e393af7d496485f Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Tue, 14 Jul 2026 09:31:05 +0800 Subject: [PATCH 129/155] drm/amdgpu/mes11: set remove_queue_after_reset for reset unmap path In mes_v11_0_unmap_legacy_queue(), set remove_queue_after_reset=1 for RESET_QUEUES. The queue may already be MMIO-reset. This flag tells MES to drop internal queue state directly instead of issuing another CP unmap flow, reducing timeout risk during recovery. Reviewed-by: Amber Lin Suggested-by: Shaoyun Liu Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index e119e743f489..805e662faaff 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -770,6 +770,11 @@ static int mes_v11_0_unmap_legacy_queue(struct amdgpu_mes *mes, convert_to_mes_queue_type(input->queue_type); } + if (input->action == RESET_QUEUES && + (mes->sched_version & AMDGPU_MES_VERSION_MASK) >= 0x60) + mes_remove_queue_pkt.remove_queue_after_reset = 1; + + return mes_v11_0_submit_pkt_and_poll_completion(mes, &mes_remove_queue_pkt, sizeof(mes_remove_queue_pkt), offsetof(union MESAPI__REMOVE_QUEUE, api_status)); From 4ae2e536c6438f2eae684a9485d4e76c5ba6f9c5 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 3 Jul 2026 13:38:02 +0800 Subject: [PATCH 130/155] drm/amdgpu/mes11: get MES process/gang contex size Setup the MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE MES firmware command request, and get the MES11 process/gang contex size. Signed-off-by: Prike Liang Reviewed-by: Michael Chen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 79 +++++++++++++++++++ drivers/gpu/drm/amd/include/mes_v11_api_def.h | 46 ++++++++++- 2 files changed, 121 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 805e662faaff..d8b4d3deff60 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -837,6 +837,58 @@ static int mes_v11_0_query_sched_status(struct amdgpu_mes *mes) offsetof(union MESAPI__QUERY_MES_STATUS, api_status)); } +/* + * QUERY_SCHEDULER_STATUS: get proc/gang context array sizes + */ +static int mes_v11_0_query_ctx_array_sizes(struct amdgpu_mes *mes) +{ + struct amdgpu_device *adev = mes->adev; + union MESAPI__QUERY_MES_STATUS mes_query_pkt; + int r; + + if (!mes->ctx_array_size_cpu_ptr) + return -EINVAL; + + /* Clear the output buffer */ + mes->ctx_array_size_cpu_ptr[0] = 0; /* proc_ctx_array_size */ + mes->ctx_array_size_cpu_ptr[1] = 0; /* gang_ctx_array_size */ + + memset(&mes_query_pkt, 0, sizeof(mes_query_pkt)); + + mes_query_pkt.header.type = MES_API_TYPE_SCHEDULER; + mes_query_pkt.header.opcode = MES_SCH_API_QUERY_SCHEDULER_STATUS; + mes_query_pkt.header.dwsize = API_FRAME_SIZE_IN_DWORDS; + + mes_query_pkt.subopcode = MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE; + + /* + * MES FW will write the array sizes to these GPU addresses: + * proc_ctx_array_size_addr -> uint32_t N (e.g., 50) + * gang_ctx_array_size_addr -> uint32_t M (e.g., 300) + */ + mes_query_pkt.ctx_array_size.proc_ctx_array_size_addr = + mes->ctx_array_size_gpu_addr; + mes_query_pkt.ctx_array_size.gang_ctx_array_size_addr = + mes->ctx_array_size_gpu_addr + sizeof(uint32_t); + + mes_query_pkt.api_status.api_completion_fence_addr = + mes->ring[0].fence_drv.gpu_addr; + mes_query_pkt.api_status.api_completion_fence_value = + ++mes->ring[0].fence_drv.sync_seq; + + r = mes_v11_0_submit_pkt_and_poll_completion(mes, + &mes_query_pkt, sizeof(mes_query_pkt), + offsetof(union MESAPI__QUERY_MES_STATUS, api_status)); + if (r) { + dev_err(adev->dev, + "MES QUERY_SCHEDULER_STATUS (GET_CTX_ARRAY_SIZE) failed, r=%d\n", r); + return r; + } + + /* MES has written the sizes - now set up bitmaps */ + return amdgpu_mes_rs64mem_setup_bitmaps(mes); +} + static int mes_v11_0_misc_op(struct amdgpu_mes *mes, struct mes_misc_op_input *input) { @@ -1910,10 +1962,33 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) if (r) goto failure; + /* Allocate GPU buffer for array size query results */ + r = amdgpu_mes_rs64mem_init(&adev->mes); + if (r) + dev_warn(adev->dev, + "RS64 local memory init failed (%d)," + "falling back to system memory path\n", r); + r = mes_v11_0_set_hw_resources(&adev->mes); + if (r) goto failure; + /* + * QUERY_SCHEDULER_STATUS to get array sizes (N, M). + * MES writes the sizes to the GPU buffer, then we allocate bitmaps. + */ + if (adev->mes.use_rs64mem) { + r = mes_v11_0_query_ctx_array_sizes(&adev->mes); + if (r) { + dev_warn(adev->dev, + "Failed to query ctx array sizes (%d)," + "disabling RS64 local memory\n", r); + /* Continue without optimization - not fatal */ + } + } + + if ((adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x52) { r = mes_v11_0_set_hw_resources_1(&adev->mes); if (r) { @@ -1951,6 +2026,10 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) static int mes_v11_0_hw_fini(struct amdgpu_ip_block *ip_block) { + struct amdgpu_device *adev = ip_block->adev; + + if (adev->mes.use_rs64mem) + amdgpu_mes_rs64mem_fini(&adev->mes); return 0; } diff --git a/drivers/gpu/drm/amd/include/mes_v11_api_def.h b/drivers/gpu/drm/amd/include/mes_v11_api_def.h index b06412ac8583..1fe824b41f2a 100644 --- a/drivers/gpu/drm/amd/include/mes_v11_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v11_api_def.h @@ -329,6 +329,7 @@ union MESAPI__ADD_QUEUE { uint32_t pipe_id; uint32_t queue_id; uint32_t alignment_mode_setting; + uint32_t full_sh_mem_config_data; uint64_t unmap_flag_addr; }; @@ -358,6 +359,7 @@ union MESAPI__REMOVE_QUEUE { uint32_t tf_data; enum MES_QUEUE_TYPE queue_type; + uint64_t timestamp; }; uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; @@ -516,14 +518,50 @@ union MESAPI__SET_LOGGING_BUFFER { uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; }; +enum MES_API_QUERY_MES_OPCODE { + MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE, + MES_API_QUERY_MES__GET_CAPS = MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE, + MES_API_QUERY_MES__CHECK_HEALTHY, + MES_API_QUERY_MES__MAX, +}; + +enum { QUERY_MES_MAX_SIZE_IN_DWORDS = 20 }; + +/** + * Obsolete + * to be removed once KMD stopped refering to it. + * use MES_API_QUERY_MES__CAPS instead +*/ +struct MES_API_QUERY_MES__CTX_ARRAY_SIZE { + uint64_t proc_ctx_array_size_addr; + uint64_t gang_ctx_array_size_addr; +}; + +struct MES_API_QUERY_MES__HEALTHY_CHECK { + uint64_t healthy_addr; +}; + +struct MES_API_QUERY_MES__CAPS { + uint64_t proc_ctx_array_size_addr; + uint64_t gang_ctx_array_size_addr; + uint64_t features_enablement_addr; +}; + union MESAPI__QUERY_MES_STATUS { struct { - union MES_API_HEADER header; - bool mes_healthy; /* 0 - not healthy, 1 - healthy */ - struct MES_API_STATUS api_status; + union MES_API_HEADER header; + enum MES_API_QUERY_MES_OPCODE subopcode; + struct MES_API_STATUS api_status; + uint64_t timestamp; + union { + struct MES_API_QUERY_MES__CTX_ARRAY_SIZE ctx_array_size; + struct MES_API_QUERY_MES__CAPS caps; + struct MES_API_QUERY_MES__HEALTHY_CHECK healthy_check; + uint32_t data[QUERY_MES_MAX_SIZE_IN_DWORDS]; + }; }; - uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; + uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; }; union MESAPI__PROGRAM_GDS { From f96e76c031cb8b2e30e06d33d2b6ad6f44b1c685 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 9 Jul 2026 18:10:47 +0800 Subject: [PATCH 131/155] drm/amd/pm: update smu_v13_0_6 acc gpu metrics Update two cases in the smu_v13_0_6 GPU metrics accumulated counter field definition Widen the following fields from U32 to U64 to prevent counter wrap-around and match smu_v15_0_8 field sizes: - prochot_residency_acc - ppt_residency_acc - socket_thm_residency_acc - vr_thm_residency_acc - hbm_thm_residency_acc - gfx_activity_acc - mem_activity_acc - pcie_nak_sent_count_acc - pcie_nak_rcvd_count_acc - pcie_lc_perf_other_end_recovery Correct the unit annotation from PERCENT to NONE for accumulated counter fields which are dimensionless hardware counters and carry no inherent unit: - gfx_activity_acc - mem_activity_acc - pcie_bandwidth_acc - gfx_busy_acc v2: remove percent unit (Lijo) Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- .../drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h index a66bf33dbb58..6554780afff7 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h @@ -144,15 +144,15 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_SCALAR(SMU_MATTR(ACCUMULATION_COUNTER), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), accumulation_counter); \ SMU_SCALAR(SMU_MATTR(PROCHOT_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), prochot_residency_acc); \ + SMU_MTYPE(U64), prochot_residency_acc); \ SMU_SCALAR(SMU_MATTR(PPT_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), ppt_residency_acc); \ + SMU_MTYPE(U64), ppt_residency_acc); \ SMU_SCALAR(SMU_MATTR(SOCKET_THM_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), socket_thm_residency_acc); \ + SMU_MTYPE(U64), socket_thm_residency_acc); \ SMU_SCALAR(SMU_MATTR(VR_THM_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), vr_thm_residency_acc); \ + SMU_MTYPE(U64), vr_thm_residency_acc); \ SMU_SCALAR(SMU_MATTR(HBM_THM_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), hbm_thm_residency_acc); \ + SMU_MTYPE(U64), hbm_thm_residency_acc); \ SMU_SCALAR(SMU_MATTR(GFXCLK_LOCK_STATUS), SMU_MUNIT(NONE), \ SMU_MTYPE(U32), gfxclk_lock_status); \ SMU_SCALAR(SMU_MATTR(PCIE_LINK_WIDTH), SMU_MUNIT(NONE), \ @@ -163,11 +163,11 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_MTYPE(U16), xgmi_link_width); \ SMU_SCALAR(SMU_MATTR(XGMI_LINK_SPEED), SMU_MUNIT(SPEED_1), \ SMU_MTYPE(U16), xgmi_link_speed); \ - SMU_SCALAR(SMU_MATTR(GFX_ACTIVITY_ACC), SMU_MUNIT(PERCENT), \ - SMU_MTYPE(U32), gfx_activity_acc); \ - SMU_SCALAR(SMU_MATTR(MEM_ACTIVITY_ACC), SMU_MUNIT(PERCENT), \ - SMU_MTYPE(U32), mem_activity_acc); \ - SMU_SCALAR(SMU_MATTR(PCIE_BANDWIDTH_ACC), SMU_MUNIT(PERCENT), \ + SMU_SCALAR(SMU_MATTR(GFX_ACTIVITY_ACC), SMU_MUNIT(NONE), \ + SMU_MTYPE(U64), gfx_activity_acc); \ + SMU_SCALAR(SMU_MATTR(MEM_ACTIVITY_ACC), SMU_MUNIT(NONE), \ + SMU_MTYPE(U64), mem_activity_acc); \ + SMU_SCALAR(SMU_MATTR(PCIE_BANDWIDTH_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_bandwidth_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_BANDWIDTH_INST), SMU_MUNIT(BW_1), \ SMU_MTYPE(U64), pcie_bandwidth_inst); \ @@ -178,9 +178,9 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_SCALAR(SMU_MATTR(PCIE_REPLAY_ROVER_COUNT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_replay_rover_count_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_NAK_SENT_COUNT_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), pcie_nak_sent_count_acc); \ + SMU_MTYPE(U64), pcie_nak_sent_count_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_NAK_RCVD_COUNT_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), pcie_nak_rcvd_count_acc); \ + SMU_MTYPE(U64), pcie_nak_rcvd_count_acc); \ SMU_ARRAY(SMU_MATTR(XGMI_READ_DATA_ACC), SMU_MUNIT(DATA_1), \ SMU_MTYPE(U64), xgmi_read_data_acc, \ SMU_13_0_6_NUM_XGMI_LINKS); \ @@ -203,7 +203,7 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_SCALAR(SMU_MATTR(CURRENT_UCLK), SMU_MUNIT(CLOCK_1), \ SMU_MTYPE(U16), current_uclk); \ SMU_SCALAR(SMU_MATTR(PCIE_LC_PERF_OTHER_END_RECOVERY), \ - SMU_MUNIT(NONE), SMU_MTYPE(U32), \ + SMU_MUNIT(NONE), SMU_MTYPE(U64), \ pcie_lc_perf_other_end_recovery); \ SMU_ARRAY(SMU_MATTR(GFX_BUSY_INST), SMU_MUNIT(PERCENT), \ SMU_MTYPE(U32), gfx_busy_inst, SMU_13_0_6_MAX_XCC); \ @@ -211,7 +211,7 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; jpeg_busy, SMU_13_0_6_MAX_JPEG); \ SMU_ARRAY(SMU_MATTR(VCN_BUSY), SMU_MUNIT(PERCENT), SMU_MTYPE(U16), \ vcn_busy, SMU_13_0_6_MAX_VCN); \ - SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(PERCENT), SMU_MTYPE(U64), \ + SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(NONE), SMU_MTYPE(U64), \ gfx_busy_acc, SMU_13_0_6_MAX_XCC); \ SMU_ARRAY(SMU_MATTR(GFX_BELOW_HOST_LIMIT_PPT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), gfx_below_host_limit_ppt_acc, \ @@ -256,7 +256,7 @@ void smu_v13_0_12_get_gpu_metrics(struct smu_context *smu, void **table, jpeg_busy, SMU_13_0_6_MAX_JPEG); \ SMU_ARRAY(SMU_MATTR(VCN_BUSY), SMU_MUNIT(PERCENT), SMU_MTYPE(U16), \ vcn_busy, SMU_13_0_6_MAX_VCN); \ - SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(PERCENT), SMU_MTYPE(U64), \ + SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(NONE), SMU_MTYPE(U64), \ gfx_busy_acc, SMU_13_0_6_MAX_XCC); \ SMU_ARRAY(SMU_MATTR(GFX_BELOW_HOST_LIMIT_PPT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), gfx_below_host_limit_ppt_acc, \ From fffb5cd7303e6bde7b171e53a2b844bc93a11aa0 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 9 Jul 2026 18:37:36 +0800 Subject: [PATCH 132/155] drm/amd/pm: Update smu_v15_0_8 gpu metrics Widen pcie_bandwidth_inst from U32 to U64 to be consistent with the smu_v13_0_6 definition. v2: Remove percent conversion (Lijo) Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h index 4dfc40aaffcb..46fce0a79c21 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h @@ -125,7 +125,7 @@ typedef struct { SMU_ARRAY(SMU_MATTR(PCIE_BANDWIDTH_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_bandwidth_acc, SMU_15_0_8_MAX_MID); \ SMU_ARRAY(SMU_MATTR(PCIE_BANDWIDTH_INST), SMU_MUNIT(BW_1), \ - SMU_MTYPE(U32), pcie_bandwidth_inst, SMU_15_0_8_MAX_MID); \ + SMU_MTYPE(U64), pcie_bandwidth_inst, SMU_15_0_8_MAX_MID); \ SMU_SCALAR(SMU_MATTR(PCIE_L0_TO_RECOV_COUNT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_l0_to_recov_count_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_REPLAY_COUNT_ACC), SMU_MUNIT(NONE), \ From 262d5a7dc441047a46d5b8aabd25fe8ddd6e8ae6 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Mon, 13 Jul 2026 09:00:00 +0530 Subject: [PATCH 133/155] drm/amd/pm: Fix pp_entries_max() bios check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 055a40c32f3a ("drm/amd/pm: Use uploaded size for legacy custom PPTable") changed pp_dpm_set_pp_table() to kmemdup the uploaded buffer directly and set soft_pp_table_size to the uploaded size. As a result soft_pp_table now points to an allocation completely outside adev->bios, making the pp_end > bios_end check in pp_entries_max() likely true for custom PP tables — returning 0 and breaking PP table overrides via sysfs. Fixes: c42871ba4833 ("drm/amdgpu/pm: add pp_entries_max() helper") Reported-by: John Olender Signed-off-by: Asad Kamal Reviewed-by: Yang Wang Tested-by: John Olender Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index 7ebc1344023f..e01afbc39d67 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -833,14 +833,17 @@ static inline uint32_t pp_entries_max(const struct pp_hwmgr *hwmgr, const void *sub_table, size_t hdr_size, size_t rec_size) { - struct amdgpu_device *adev = (struct amdgpu_device *)hwmgr->adev; - const char *bios_end = (const char *)adev->bios + adev->bios_size; - const char *pp_end = (const char *)hwmgr->soft_pp_table - + hwmgr->soft_pp_table_size; + const char *pp_start = hwmgr->soft_pp_table; + const char *pp_end = pp_start + hwmgr->soft_pp_table_size; const char *entries = (const char *)sub_table + hdr_size; - if (pp_end > bios_end) - return 0; + if (!hwmgr->hardcode_pp_table) { + struct amdgpu_device *adev = hwmgr->adev; + const char *bios_end = (const char *)adev->bios + adev->bios_size; + + if (pp_end > bios_end) + return 0; + } if (!rec_size || entries >= pp_end) return 0; return (uint32_t)((pp_end - entries) / rec_size); From 9546c8a9ac4dc66dde260cc9d69885aacede8b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Wed, 8 Jul 2026 19:20:21 -0300 Subject: [PATCH 134/155] drm/amdgpu: Inline drm_simple_encoder_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simple KMS helpers are deprecated because they introduce an unnecessary intermediate layer between atomic modesetting and the DRM driver. Inline the functionality of drm_simple_encoder_init() to remove dependencies on these deprecated helpers. Reviewed-by: Thomas Zimmermann Signed-off-by: Nicolás Antinori Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c index 170adaf7e76a..c835504fdf2b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -316,6 +316,10 @@ static struct drm_plane *amdgpu_vkms_plane_init(struct drm_device *dev, return plane; } +static const struct drm_encoder_funcs drm_encoder_funcs_cleanup = { + .destroy = drm_encoder_cleanup, +}; + static int amdgpu_vkms_output_init(struct drm_device *dev, struct amdgpu_vkms_output *output, int index) { @@ -342,7 +346,9 @@ static int amdgpu_vkms_output_init(struct drm_device *dev, struct drm_connector_helper_add(connector, &amdgpu_vkms_conn_helper_funcs); - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL); + ret = drm_encoder_init(dev, encoder, + &drm_encoder_funcs_cleanup, + DRM_MODE_ENCODER_VIRTUAL, NULL); if (ret) { DRM_ERROR("Failed to init encoder\n"); goto err_encoder; From 982b2a43682b55e7d9800000f2592832ff2865ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 10 Jul 2026 09:36:48 +0200 Subject: [PATCH 135/155] drm/amdgpu: Fix IP block NULL check during soft reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can't print the IP block name when the IP block is NULL. Note that it should never be NULL, the only way that can happen is when amdgpu_ip_from_ring() is missing the given ring type. The check is just there to be sure. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202607031711.yLwFhGfp-lkp@intel.com/ Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c index 33a04113ed74..922f4b15619d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c @@ -489,7 +489,13 @@ int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, ip_type = amdgpu_ip_from_ring(guilty_ring->funcs->type); ip_block = amdgpu_device_ip_get_ip_block(adev, ip_type); - if (!ip_block || !ip_block->version->funcs->soft_reset) { + if (unlikely(!ip_block)) { + dev_warn(adev->dev, "IP block not found for ring %s\n", + guilty_ring->name); + return -EOPNOTSUPP; + } + + if (!ip_block->version->funcs->soft_reset) { dev_warn(adev->dev, "IP block soft reset not supported on %s\n", ip_block->version->funcs->name); return -EOPNOTSUPP; From 6a3664b0b49bd61c3d4d29ea071f34cfe819aa51 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 8 Jul 2026 23:00:07 -0700 Subject: [PATCH 136/155] drm/amdgpu: drop kernel-doc for deleted struct fields 3 fields in struct amd_ip_funcs have been deleted, so also delete the kernel-doc comments for them to avoid warnings: WARNING: ../drivers/gpu/drm/amd/include/amd_shared.h:481 Excess struct member 'check_soft_reset' description in 'amd_ip_funcs' WARNING: ../drivers/gpu/drm/amd/include/amd_shared.h:481 Excess struct member 'pre_soft_reset' description in 'amd_ip_funcs' WARNING: ../drivers/gpu/drm/amd/include/amd_shared.h:481 Excess struct member 'post_soft_reset' description in 'amd_ip_funcs' Fixes: 947e46eb2fb9 ("drm/amdgpu: Delete check_soft_reset() from amd_ip_funcs") Fixes: b7500532e12b ("drm/amdgpu: Delete pre/post_soft_reset() from amd_ip_funcs") Signed-off-by: Randy Dunlap Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/include/amd_shared.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 10396018afb3..6d0c74ebdb58 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -439,10 +439,7 @@ enum amd_dpm_forced_level; * @complete: handles IP specific changes after resume * @is_idle: returns current IP block idle status * @wait_for_idle: poll for idle - * @check_soft_reset: check soft reset the IP block - * @pre_soft_reset: pre soft reset the IP block * @soft_reset: soft reset the IP block - * @post_soft_reset: post soft reset the IP block * @set_clockgating_state: enable/disable cg for the IP block * @set_powergating_state: enable/disable pg for the IP block * @get_clockgating_state: get current clockgating status From 516f8fc30a1b56af03f39e93c18707d13419fb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 12 Jul 2026 19:39:28 +0200 Subject: [PATCH 137/155] drm/amd/pm/smu7: Fix AC/DC switch notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were two mistakes in the previous implementation: The check for AutomaticDCTransition should be inverted. We recently learned that the kernel should send PPSMC_MSG_RunningOnAC when the flag is set, and not the other way around. The clocks also need to be recomputed, because the code in the smu7_apply_state_adjust_rules() function selects different limits on AC and DC. Fixes: 96da0d86614e ("drm/amd/pm/smu7: Notify SMU7 of DC->AC switch") Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c index 1f1bb274685a..fbb59e5a4453 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -5887,15 +5887,19 @@ static int smu7_power_off_asic(struct pp_hwmgr *hwmgr) static void smu7_notify_ac_dc(struct pp_hwmgr *hwmgr) { struct amdgpu_device *adev = (struct amdgpu_device *)(hwmgr->adev); + const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; - /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ - if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, + /* + * Check if the platform already manages the AC/DC switch via dedicated GPIO. + * Otherwise SMU automatically notices DC, but needs to be notified of AC. + */ + if (adev->pm.ac_power && + 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); + + /* Recompute clocks with updated max_limits. */ + pp_funcs->pm_compute_clocks(adev->powerplay.pp_handle); } static const struct pp_hwmgr_func smu7_hwmgr_funcs = { From ac4827324c3b6780baed39b0e80cec1de60cb077 Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Tue, 14 Jul 2026 17:51:05 +0800 Subject: [PATCH 138/155] drm/amd/ras: initialize CPER after XGMI reset on init The XGMI reset-on-init path can run while the device is still at the minimal init level, such as during an NPS memory partition switch. In that flow the normal RAS IP block hw_init is skipped, so unified RAS is not enabled when the early CPER initialization is attempted, leaving CPER disabled for the rest of the device's lifetime. Resume RAS after the XGMI reset-on-init completes. Once the RAS manager resume succeeds, the RAS resume wrapper performs deferred CPER initialization, keeping the path a no-op for devices where CPER was already initialized. Keep the deferred CPER retry and its debugfs registration together in the CPER helper. The normal debugfs ring walk skips the CPER ring until CPER is enabled, so the ring debugfs entry is created either by the deferred helper when debugfs is already available or by the normal debugfs walk. Reviewed-by: Hawking Zhang Signed-off-by: Xiang Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c | 19 +++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 2 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 10 ++++++++-- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 5 ++++- 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c index 6fb129025761..0af8b7be326e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c @@ -498,6 +498,25 @@ int amdgpu_cper_init(struct amdgpu_device *adev) return 0; } +int amdgpu_cper_deferred_init(struct amdgpu_device *adev) +{ + int r; + + if (adev->cper.enabled) + return 0; + + r = amdgpu_cper_init(adev); + if (r || !adev->cper.enabled) + return r; + +#if defined(CONFIG_DEBUG_FS) + if (adev_to_drm(adev)->primary->debugfs_root) + amdgpu_debugfs_ring_init(adev, &adev->cper.ring_buf); +#endif + + return 0; +} + int amdgpu_cper_fini(struct amdgpu_device *adev) { if (amdgpu_sriov_vf(adev)) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h index d12c98077d9d..eea30be91b47 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h @@ -92,6 +92,7 @@ int amdgpu_cper_generate_bp_threshold_record(struct amdgpu_device *adev); void amdgpu_cper_ring_write(struct amdgpu_ring *ring, void *src, int count); int amdgpu_cper_init(struct amdgpu_device *adev); +int amdgpu_cper_deferred_init(struct amdgpu_device *adev); int amdgpu_cper_fini(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 0455c2cd043f..deab64765388 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -2178,6 +2178,8 @@ int amdgpu_debugfs_init(struct amdgpu_device *adev) if (!ring) continue; + if (ring == &adev->cper.ring_buf && !adev->cper.enabled) + continue; amdgpu_debugfs_ring_init(adev, ring); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index de121539788d..24fd24e8d874 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -4990,7 +4990,13 @@ void amdgpu_ras_post_reset(struct amdgpu_device *adev, } } -void amdgpu_ras_resume_after_reset(struct amdgpu_device *adev) +int amdgpu_ras_resume_after_reset(struct amdgpu_device *adev) { - amdgpu_ras_mgr_resume_after_reset(adev); + int r; + + r = amdgpu_ras_mgr_resume_after_reset(adev); + if (r) + return r; + + return amdgpu_cper_deferred_init(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h index 23bff7a0f35b..c53f911d5729 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h @@ -980,5 +980,5 @@ void amdgpu_ras_pre_reset(struct amdgpu_device *adev, struct list_head *device_list); void amdgpu_ras_post_reset(struct amdgpu_device *adev, struct list_head *device_list); -void amdgpu_ras_resume_after_reset(struct amdgpu_device *adev); +int amdgpu_ras_resume_after_reset(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index d2c5bb50d94a..c0f581e416f9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -1393,7 +1393,10 @@ static void amdgpu_xgmi_reset_on_init_work(struct work_struct *work) * no-op for any other reset path where RAS is already * initialized, and for non-uniras devices. */ - amdgpu_ras_resume_after_reset(tmp_adev); + r = amdgpu_ras_resume_after_reset(tmp_adev); + if (r) + dev_err(tmp_adev->dev, + "failed to resume RAS after XGMI reset-on-init\n"); } } From dc6022da45b9abec5f2fc8ef7055d2b8afe1d0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Thu, 2 Jul 2026 18:24:40 +0200 Subject: [PATCH 139/155] drm/amd/pm/smu7: make SMU message reports more readable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the lines greppable by removing an extra EOL and report the status only once - after the reply is detected. Signed-off-by: Michał Mirosław Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c b/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c index 3e0068a6aeb2..2c190f599ff8 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c @@ -134,14 +134,6 @@ int smu7_send_msg_to_smc(struct pp_hwmgr *hwmgr, uint16_t msg) PHM_WAIT_FIELD_UNEQUAL(hwmgr, SMC_RESP_0, SMC_RESP, 0); - ret = PHM_READ_FIELD(hwmgr->device, SMC_RESP_0, SMC_RESP); - - if (ret == 0xFE) - dev_dbg(adev->dev, "last message was not supported\n"); - else if (ret != 1) - dev_info(adev->dev, - "\nlast message was failed ret is %d\n", ret); - cgs_write_register(hwmgr->device, mmSMC_RESP_0, 0); cgs_write_register(hwmgr->device, mmSMC_MESSAGE_0, msg); @@ -150,10 +142,9 @@ int smu7_send_msg_to_smc(struct pp_hwmgr *hwmgr, uint16_t msg) ret = PHM_READ_FIELD(hwmgr->device, SMC_RESP_0, SMC_RESP); if (ret == 0xFE) - dev_dbg(adev->dev, "message %x was not supported\n", msg); + dev_dbg(adev->dev, "SMU message %#x was not supported\n", msg); else if (ret != 1) - dev_dbg(adev->dev, - "failed to send message %x ret is %d \n", msg, ret); + dev_info(adev->dev, "SMU message %#x failed: response is %d\n", msg, ret); return 0; } From 0d2d656dfeb6d143bdfd42c6e16bb3e27f847186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Thu, 2 Jul 2026 18:24:40 +0200 Subject: [PATCH 140/155] drm/amd/pm/smu7: return error on message send failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Mirosław Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c b/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c index 2c190f599ff8..45504ac3a31d 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c @@ -141,12 +141,16 @@ int smu7_send_msg_to_smc(struct pp_hwmgr *hwmgr, uint16_t msg) ret = PHM_READ_FIELD(hwmgr->device, SMC_RESP_0, SMC_RESP); - if (ret == 0xFE) + switch (ret) { + case 1: + return 0; + case 0xFE: dev_dbg(adev->dev, "SMU message %#x was not supported\n", msg); - else if (ret != 1) + return -EOPNOTSUPP; + default: dev_info(adev->dev, "SMU message %#x failed: response is %d\n", msg, ret); - - return 0; + return ret != 0xFFFF ? -EIO : -ENXIO; + } } int smu7_send_msg_to_smc_with_parameter(struct pp_hwmgr *hwmgr, uint16_t msg, uint32_t parameter) From bd2c949e5e751e05638c7e8a64e3e9b3d3a42a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Thu, 2 Jul 2026 18:24:40 +0200 Subject: [PATCH 141/155] drm/amd/amdgpu/cgs: Avoid redundant copying of firmware filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While at it, remove redundant error message - request_firmware() will log a failure anyway. Signed-off-by: Michał Mirosław Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c | 54 ++++++++++++------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c index 9bbe60968352..d3531f0c90b4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c @@ -239,7 +239,7 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, info->fw_version = amdgpu_get_firmware_version(cgs_device, type); info->feature_version = (uint16_t)le32_to_cpu(header->ucode_feature_version); } else { - char fw_name[30] = {0}; + const char *fw_name = NULL; int err = 0; uint32_t ucode_size; uint32_t ucode_start_address; @@ -255,17 +255,17 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, (adev->pdev->revision == 0x81) || (adev->pdev->device == 0x665f)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/bonaire_k_smc.bin"); + fw_name = "bonaire_k_smc.bin"; } else { - strscpy(fw_name, "amdgpu/bonaire_smc.bin"); + fw_name = "bonaire_smc.bin"; } break; case CHIP_HAWAII: if (adev->pdev->revision == 0x80) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/hawaii_k_smc.bin"); + fw_name = "hawaii_k_smc.bin"; } else { - strscpy(fw_name, "amdgpu/hawaii_smc.bin"); + fw_name = "hawaii_smc.bin"; } break; case CHIP_TOPAZ: @@ -275,76 +275,76 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, ((adev->pdev->device == 0x6900) && (adev->pdev->revision == 0xD1)) || ((adev->pdev->device == 0x6900) && (adev->pdev->revision == 0xD3))) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/topaz_k_smc.bin"); + fw_name = "topaz_k_smc.bin"; } else - strscpy(fw_name, "amdgpu/topaz_smc.bin"); + fw_name = "topaz_smc.bin"; break; case CHIP_TONGA: if (((adev->pdev->device == 0x6939) && (adev->pdev->revision == 0xf1)) || ((adev->pdev->device == 0x6938) && (adev->pdev->revision == 0xf1))) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/tonga_k_smc.bin"); + fw_name = "tonga_k_smc.bin"; } else - strscpy(fw_name, "amdgpu/tonga_smc.bin"); + fw_name = "tonga_smc.bin"; break; case CHIP_FIJI: - strscpy(fw_name, "amdgpu/fiji_smc.bin"); + fw_name = "fiji_smc.bin"; break; case CHIP_POLARIS11: if (type == CGS_UCODE_ID_SMU) { if (ASICID_IS_P21(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris11_k_smc.bin"); + fw_name = "polaris11_k_smc.bin"; } else if (ASICID_IS_P31(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris11_k2_smc.bin"); + fw_name = "polaris11_k2_smc.bin"; } else { - strscpy(fw_name, "amdgpu/polaris11_smc.bin"); + fw_name = "polaris11_smc.bin"; } } else if (type == CGS_UCODE_ID_SMU_SK) { - strscpy(fw_name, "amdgpu/polaris11_smc_sk.bin"); + fw_name = "polaris11_smc_sk.bin"; } break; case CHIP_POLARIS10: if (type == CGS_UCODE_ID_SMU) { if (ASICID_IS_P20(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris10_k_smc.bin"); + fw_name = "polaris10_k_smc.bin"; } else if (ASICID_IS_P30(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris10_k2_smc.bin"); + fw_name = "polaris10_k2_smc.bin"; } else { - strscpy(fw_name, "amdgpu/polaris10_smc.bin"); + fw_name = "polaris10_smc.bin"; } } else if (type == CGS_UCODE_ID_SMU_SK) { - strscpy(fw_name, "amdgpu/polaris10_smc_sk.bin"); + fw_name = "polaris10_smc_sk.bin"; } break; case CHIP_POLARIS12: if (ASICID_IS_P23(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris12_k_smc.bin"); + fw_name = "polaris12_k_smc.bin"; } else { - strscpy(fw_name, "amdgpu/polaris12_smc.bin"); + fw_name = "polaris12_smc.bin"; } break; case CHIP_VEGAM: - strscpy(fw_name, "amdgpu/vegam_smc.bin"); + fw_name = "vegam_smc.bin"; break; case CHIP_VEGA10: if ((adev->pdev->device == 0x687f) && ((adev->pdev->revision == 0xc0) || (adev->pdev->revision == 0xc1) || (adev->pdev->revision == 0xc3))) - strscpy(fw_name, "amdgpu/vega10_acg_smc.bin"); + fw_name = "vega10_acg_smc.bin"; else - strscpy(fw_name, "amdgpu/vega10_smc.bin"); + fw_name = "vega10_smc.bin"; break; case CHIP_VEGA12: - strscpy(fw_name, "amdgpu/vega12_smc.bin"); + fw_name = "vega12_smc.bin"; break; case CHIP_VEGA20: - strscpy(fw_name, "amdgpu/vega20_smc.bin"); + fw_name = "vega20_smc.bin"; break; default: drm_err(adev_to_drm(adev), "SMC firmware not supported\n"); @@ -353,10 +353,8 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, err = amdgpu_ucode_request(adev, &adev->pm.fw, AMDGPU_UCODE_REQUIRED, - "%s", fw_name); + "amdgpu/%s", fw_name); if (err) { - drm_err(adev_to_drm(adev), - "Failed to load firmware \"%s\"\n", fw_name); amdgpu_ucode_release(&adev->pm.fw); return err; } From c119d05a36a884482decc67e55944648f8cba97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Thu, 2 Jul 2026 18:24:41 +0200 Subject: [PATCH 142/155] drm/amdgpu: debugfs: avoid extra EOLs in amdgpu_gem_info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Mirosław Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index 718937777e53..d4a9d5e8fb42 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -1699,8 +1699,9 @@ u64 amdgpu_bo_print_info(int id, struct amdgpu_bo *bo, struct seq_file *m) if (dma_resv_trylock(bo->tbo.base.resv)) { dma_resv_describe(bo->tbo.base.resv, m); dma_resv_unlock(bo->tbo.base.resv); + } else { + seq_puts(m, "\n"); } - seq_puts(m, "\n"); return size; } From faad8105e9a7362ef3eba1f0fc4fe446191856e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcolas=20F=2E=20R=2E=20A=2E=20Prado?= Date: Fri, 26 Jun 2026 10:18:06 -0400 Subject: [PATCH 143/155] drm/amd/display: Always notify the HDA driver on connector disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_dm_audio_eld_notify() is currently only called in atomic commits. This results in the state of the HDA driver never getting updated when an audio sink is removed and an atomic commit never comes. (eg the HDMI audio jack switch in ALSA stays 'on' after the HDMI cable is disconnected if no other display is connected to cause the compositor to submit an atomic commit) Call amdgpu_dm_audio_eld_notify() in amdgpu_dm_update_connector_after_detect() when a sink is no longer available to ensure that the HDA driver always gets notified on disconnect regardless of atomic commits. Assisted-by: Copilot:claude-sonnet-4.6 Signed-off-by: Nícolas F. R. A. Prado Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c index fd4e185bf801..e97ce5c2c0e0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -182,7 +182,7 @@ void amdgpu_dm_audio_fini(struct amdgpu_device *adev) } EXPORT_IF_KUNIT(amdgpu_dm_audio_fini); -STATIC_IFN_KUNIT void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) +void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) { struct drm_audio_component *acomp = adev->dm.audio_component; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h index 018e9919a118..e57e2ed20d56 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -40,6 +40,7 @@ void amdgpu_dm_commit_audio(struct drm_device *dev, void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, const struct dc_sink *dc_sink); +void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) struct device; @@ -48,7 +49,6 @@ int amdgpu_dm_audio_component_bind(struct device *kdev, struct device *hda_kdev, void *data); void amdgpu_dm_audio_component_unbind(struct device *kdev, struct device *hda_kdev, void *data); -void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); int amdgpu_dm_audio_get_param(void); void amdgpu_dm_audio_set_param(int val); void amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 173793419da2..5c5f9c3e6d77 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -500,6 +500,8 @@ void amdgpu_dm_update_connector_after_detect( struct drm_connector *connector = &aconnector->base; struct dc_sink *sink __free(sink_release) = NULL; struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + int inst; /* MST handled by drm_mst framework */ if (aconnector->mst_mgr.mst_state) @@ -638,6 +640,13 @@ void amdgpu_dm_update_connector_after_detect( /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + mutex_lock(&adev->dm.audio_lock); + inst = aconnector->audio_inst; + aconnector->audio_inst = -1; + mutex_unlock(&adev->dm.audio_lock); + if (inst != -1) + amdgpu_dm_audio_eld_notify(adev, inst); } update_subconnector_property(aconnector); From a8171229bc836607fbc225d323ebc4d14489cfbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:49:58 +0200 Subject: [PATCH 144/155] drm/amdgpu/ttm: Consider concurrent VM flushes for buffer entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow using multiple SDMA schedulers only on GPUs where we are allowed to do concurrent VM flushes. This consideration is necessary because all GART windows are mapped in VMID 0 (the kernel VMID) so each buffer entity would flush VMID 0 concurrently. Practically this means that we can't use multiple SDMA engines for TTM on GFX6-8 and Navi 1x. Fixes: 01c836788b37 ("drm/amdgpu: pass all the sdma scheds to amdgpu_mman") Fixes: e4029f7a9474 ("drm/amdgpu: only use working sdma schedulers for ttm") Cc: Pierre-Eric Pelloux-Prayer Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index b10b0878df37..7920675af1d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2684,12 +2684,22 @@ void amdgpu_sdma_set_buffer_funcs_scheds(struct amdgpu_device *adev, return; } - /* Navi1x's workaround requires us to limit to a single SDMA sched - * for ttm. - */ hub = &adev->vmhub[AMDGPU_GFXHUB(0)]; - adev->mman.num_buffer_funcs_scheds = hub->sdma_invalidation_workaround ? - 1 : n; + + /* + * Allow using multiple SDMA schedulers only on GPUs where + * we are allowed to do concurrent VM flushes. + * This consideration is necessary because all GART windows + * are mapped in VMID 0 (the kernel VMID) so each buffer + * entity would flush VMID 0 concurrently. + * + * Also consider the SDMA invalidation workaround on + * Navi 1x GPUs, which also prevents us from using + * multiple SDMA engines on VMID 0 at the same time. + */ + adev->mman.num_buffer_funcs_scheds = + (adev->vm_manager.concurrent_flush && + !hub->sdma_invalidation_workaround) ? n : 1; } #if defined(CONFIG_DEBUG_FS) From 1fff2e07b6670bc5b8f7344a8708c136259cb176 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 18:51:15 +0800 Subject: [PATCH 145/155] drm/amdkfd: free MQD managers on DQM init failures The change referenced by the Fixes tag releases the HIQ SDMA MQD trunk buffer when device_queue_manager_init() fails after it has been allocated. However, the same failure path can also be reached after init_mqd_managers() has succeeded. At that point dqm->mqd_mgrs[] contains per-type MQD manager objects owned by the device queue manager. The normal teardown path frees those objects from uninitialize(), but the initialization error path only frees dqm itself. Free the MQD managers from the initialization error path as well. This is safe for earlier failures because dqm is zeroed when allocated and init_mqd_managers() clears the entries it rolls back internally. Fixes: b7cccc8286bb ("drm/amdkfd: fix a memory leak in device_queue_manager_init()") Signed-off-by: Guangshuo Li Reviewed-by: Mukul Joshi Reviewed-by: Felix Kuehling Signed-off-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 4 ++++ 1 file changed, 4 insertions(+) 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 cc42feb24277..5446d89a84b3 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -3071,6 +3071,7 @@ static void deallocate_hiq_sdma_mqd(struct kfd_node *dev, struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) { struct device_queue_manager *dqm; + int i; pr_debug("Loading device queue manager\n"); @@ -3199,6 +3200,9 @@ struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) deallocate_hiq_sdma_mqd(dev, &dqm->hiq_sdma_mqd); out_free: + for (i = 0; i < KFD_MQD_TYPE_MAX; i++) + kfree(dqm->mqd_mgrs[i]); + kfree(dqm); return NULL; } From a57c68224d73901a6ebc1da8f96cb4f07bcb5bd8 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 13 Jul 2026 12:24:31 -0500 Subject: [PATCH 146/155] drm/amd: Move dynamic PCIe switching quirks to X86_MATCH_* macros Use the X86_MATCH_VENDOR_FAM() and X86_MATCH_VENDOR_FAM_MODEL() macros to make the quirks for dynamic speed switching more scalable. Acked-by: Alex Deucher Link: https://patch.msgid.link/20260713172431.1599801-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 51 ++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index fa97281f6c4c..472e96ae884e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1203,37 +1203,44 @@ bool amdgpu_device_seamless_boot_supported(struct amdgpu_device *adev) return amdgpu_ip_version(adev, DCE_HWIP, 0) >= IP_VERSION(3, 0, 0); } -/* - * Intel hosts such as Rocket Lake, Alder Lake, Raptor Lake and Sapphire Rapids - * don't support dynamic speed switching. Until we have confirmation from Intel - * that a specific host supports it, it's safer that we keep it disabled for all. - * - * https://edc.intel.com/content/www/us/en/design/products/platforms/details/raptor-lake-s/13th-generation-core-processors-datasheet-volume-1-of-2/005/pci-express-support/ - * https://gitlab.freedesktop.org/drm/amd/-/issues/2663 - */ -static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device *adev) -{ #if IS_ENABLED(CONFIG_X86) - struct cpuinfo_x86 *c = &cpu_data(0); - - /* eGPU change speeds based on USB4 fabric conditions */ - if (dev_is_removable(adev->dev)) - return true; - - if (c->x86_vendor == X86_VENDOR_INTEL) - return false; - +static const struct x86_cpu_id amdgpu_pcie_dynamic_switching_quirks[] = { + /* + * Intel hosts such as Rocket Lake, Alder Lake, Raptor Lake and Sapphire Rapids + * don't support dynamic speed switching. Until we have confirmation from Intel + * that a specific host supports it, it's safer that we keep it disabled for all. + * + * https://edc.intel.com/content/www/us/en/design/products/platforms/details/raptor-lake-s/13th-generation-core-processors-datasheet-volume-1-of-2/005/pci-express-support/ + * https://gitlab.freedesktop.org/drm/amd/-/issues/2663 + */ + X86_MATCH_VENDOR_FAM(INTEL, X86_FAMILY_ANY, NULL), /* * AMD Ryzen Pinnacle Ridge (Zen+, family 0x17 model 0x08) CPUs don't * support PCIe dynamic speed switching. * https://gitlab.freedesktop.org/drm/amd/-/work_items/5436 */ - if (c->x86_vendor == X86_VENDOR_AMD && c->x86 == 0x17 && - c->x86_model == 0x08) + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x08, NULL), + {} +}; + +static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device *adev) +{ + /* eGPU change speeds based on USB4 fabric conditions */ + if (dev_is_removable(adev->dev)) + return true; + + /* Hosts have problems with dynamic speed switching */ + if (x86_match_cpu(amdgpu_pcie_dynamic_switching_quirks)) return false; -#endif + return true; } +#else +static inline bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device *adev) +{ + return true; +} +#endif static bool amdgpu_device_aspm_support_quirk(struct amdgpu_device *adev) { From 81b39f43e7e53589491e2eef6bad5389626b4b9c Mon Sep 17 00:00:00 2001 From: Alessandro Rinaldi Date: Fri, 26 Jun 2026 16:36:00 +0200 Subject: [PATCH 147/155] drm/amd/display: Force PWM backlight on Lenovo Legion 5 15ARH05 The Lenovo Legion 5 15ARH05 (Renoir) ships a BOE 0x08DF eDP panel that advertises AUX/DPCD backlight control, so amdgpu's automatic detection (amdgpu_backlight == -1) selects AUX. On this panel the AUX backlight path has no effect: brightness writes are accepted but the panel level never changes, the display is stuck at a fixed brightness and max_brightness is reported as a bogus 511000. As a result neither the desktop brightness slider nor the brightness hotkeys do anything. Forcing PWM backlight (amdgpu.backlight=0) restores working control: max_brightness becomes 65535 and the level tracks writes. This has long been applied by users as a manual kernel-parameter workaround. Extend the generic panel backlight quirk with a force_pwm flag, add an entry for the Legion 5 15ARH05 / BOE 0x08DF panel, and have amdgpu disable AUX backlight (use PWM) when the quirk matches and the user lets the driver auto-select the backlight type. Signed-off-by: Alessandro Rinaldi Tested-by: Alessandro Rinaldi Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 7 +++++-- drivers/gpu/drm/drm_panel_backlight_quirks.c | 9 +++++++++ include/drm/drm_utils.h | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 373ef4d217f1..4eaf5e303850 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -517,6 +517,8 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; caps->aux_support = false; + panel_backlight_quirk = drm_get_panel_backlight_quirk(aconnector->drm_edid); + if (caps->ext_caps->bits.oled == 1 /* * || @@ -529,6 +531,9 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->aux_support = false; else if (amdgpu_backlight == 1) caps->aux_support = true; + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_pwm) + caps->aux_support = false; if (caps->aux_support) aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; @@ -544,8 +549,6 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) else caps->aux_min_input_signal = 1; - panel_backlight_quirk = - drm_get_panel_backlight_quirk(aconnector->drm_edid); if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { if (panel_backlight_quirk->min_brightness) { caps->min_input_signal = diff --git a/drivers/gpu/drm/drm_panel_backlight_quirks.c b/drivers/gpu/drm/drm_panel_backlight_quirks.c index f85cb293a3db..e417c7533053 100644 --- a/drivers/gpu/drm/drm_panel_backlight_quirks.c +++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c @@ -20,6 +20,15 @@ struct drm_get_panel_backlight_quirk { }; static const struct drm_get_panel_backlight_quirk drm_panel_min_backlight_quirks[] = { + /* Lenovo Legion 5 15ARH05, AUX backlight non-functional, force PWM */ + { + .dmi_match.field = DMI_SYS_VENDOR, + .dmi_match.value = "LENOVO", + .dmi_match_other.field = DMI_PRODUCT_VERSION, + .dmi_match_other.value = "Lenovo Legion 5 15ARH05", + .ident.panel_id = drm_edid_encode_panel_id('B', 'O', 'E', 0x08df), + .quirk = { .force_pwm = true, }, + }, /* 13 inch matte panel */ { .dmi_match.field = DMI_BOARD_VENDOR, diff --git a/include/drm/drm_utils.h b/include/drm/drm_utils.h index 6a46f755daba..7e077484c5bb 100644 --- a/include/drm/drm_utils.h +++ b/include/drm/drm_utils.h @@ -19,6 +19,7 @@ int drm_get_panel_orientation_quirk(int width, int height); struct drm_panel_backlight_quirk { u16 min_brightness; u32 brightness_mask; + bool force_pwm; }; const struct drm_panel_backlight_quirk * From 9835f031f08f087485d27a87e3cd2ef5841851d9 Mon Sep 17 00:00:00 2001 From: Ziran Zhang Date: Sat, 27 Jun 2026 21:13:54 +0800 Subject: [PATCH 148/155] drm/amd/display: Remove redundant NULL check before kfree() in mod_power_create() kfree() safely handles NULL pointers, so there is no need to check for NULL before calling kfree(). Remove redundant NULL check. Found by Coccinelle ifnullfree script. Signed-off-by: Ziran Zhang Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/modules/power/power.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index db101fdb11f0..ee15c14a899e 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -270,13 +270,11 @@ struct mod_power *mod_power_create(struct dc *dc, fail_bad_brightness_range: fail_alloc_backlight_array: for (inst = 0; inst < edp_num; inst++) - if (core_power->bl_prop[inst].backlight_lut) - kfree(core_power->bl_prop[inst].backlight_lut); + kfree(core_power->bl_prop[inst].backlight_lut); fail_construct: - for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) { - if (core_power->map[i].psr_context) - kfree(core_power->map[i].psr_context); - } + for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) + kfree(core_power->map[i].psr_context); + kfree(core_power->map); fail_alloc_map: @@ -295,8 +293,7 @@ void mod_power_destroy(struct mod_power *mod_power) MOD_POWER_TO_CORE(mod_power); for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) - if (core_power->map[i].psr_context) - kfree(core_power->map[i].psr_context); + kfree(core_power->map[i].psr_context); for (i = 0; i < core_power->num_entities; i++) if (core_power->map[i].stream) @@ -305,8 +302,7 @@ void mod_power_destroy(struct mod_power *mod_power) kfree(core_power->map); for (i = 0; i < MAX_NUM_EDP; i++) - if (core_power->bl_prop[i].backlight_lut) - kfree(core_power->bl_prop[i].backlight_lut); + kfree(core_power->bl_prop[i].backlight_lut); kfree(core_power); } From b736792e5bd4a62f24e8d1e310bf4a75bfbeaaaa Mon Sep 17 00:00:00 2001 From: Gleb Markov Date: Mon, 29 Jun 2026 16:09:06 +0300 Subject: [PATCH 149/155] drm/amd/display: Remove unused-but-set variable hubp from The final check of hubp for NULL covers all remaining lines of code, since the value of hubp does not change until the end of the method. This check is redundant because hubp1 is already dereferenced within the macro. If it were NULL, the program would have already failed to proceed. Remove the left part of the expression with the logical "&&". Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: be1fb44389ca ("drm/amd/display: Check null pointers before used"). Signed-off-by: Gleb Markov Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c index 7c97a774141f..d8eb5996b577 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c @@ -772,8 +772,7 @@ bool hubp1_is_flip_pending(struct hubp *hubp) if (flip_pending) return true; - if (hubp && - earliest_inuse_address.grph.addr.quad_part != hubp->request_address.grph.addr.quad_part) + if (earliest_inuse_address.grph.addr.quad_part != hubp->request_address.grph.addr.quad_part) return true; return false; From db9c882f83d3f3e08069f52d0d2ebf9d131c9ae4 Mon Sep 17 00:00:00 2001 From: Dyllan Kobal Date: Tue, 23 Jun 2026 00:58:04 +0000 Subject: [PATCH 150/155] drm/amd/display: fix wrong register field in dccg35_set_hdmistreamclk_src_new dccg35_set_hdmistreamclk_src_new() updates HDMISTREAMCLK_CNTL but passes DPSTREAMCLK0_SRC_SEL as the field identifier in the second REG_UPDATE_2 slot. The current behavior is harmless on DCN3.5 because both fields share the same bit layout, but it is still incorrect and could break on future hardware revisions. Fixes: d36771a03412 ("drm/amd/display: Add DCCG DIO, HPO, OPP, and OPTC support for FRL") Signed-off-by: Dyllan Kobal Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c index 5c9ef30b5416..71903a6c63a0 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c @@ -572,7 +572,7 @@ static void dccg35_set_hdmistreamclk_src_new( case 0: REG_UPDATE_2(HDMISTREAMCLK_CNTL, HDMISTREAMCLK0_EN, (src == HDMI_STREAM_REFCLK) ? 0 : 1, - DPSTREAMCLK0_SRC_SEL, + HDMISTREAMCLK0_SRC_SEL, (src == HDMI_STREAM_REFCLK) ? 0 : src); break; default: From dcda83715716ac828454094ed9c6e17150a0092f Mon Sep 17 00:00:00 2001 From: Anas Khan Date: Thu, 2 Jul 2026 16:58:28 +0530 Subject: [PATCH 151/155] drm/amd/display: remove duplicate link_dp_panel_replay.h include link_dp_irq_handler.c includes "link_dp_panel_replay.h" twice. Drop the redundant second include; this is a non-functional cleanup flagged by scripts/checkincludes.pl. Fixes: 1e5cd4adfc54 ("drm/amd/display: move panel replay out from edp") Signed-off-by: Anas Khan Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c index 54ce768ae6ad..da679fb7d89c 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c @@ -39,7 +39,6 @@ #include "link/link_dpms.h" #include "dm_helpers.h" #include "link_dp_dpia_bw.h" -#include "link_dp_panel_replay.h" #define DC_LOGGER \ link->ctx->logger From 99f3af19073b3ddbfd96e789124cce12c4277b28 Mon Sep 17 00:00:00 2001 From: WenTao Liang Date: Sun, 28 Jun 2026 15:27:40 +0800 Subject: [PATCH 152/155] drm/amd/display: set new_stream to NULL after release In dm_update_crtc_state(), the skip_modeset path releases new_stream via dc_stream_release() but does not set the pointer to NULL. If a later error (e.g., color management failure) triggers the fail label, the error path calls dc_stream_release() again on the same dangling pointer, causing a double release and potential use-after-free. Fix this by setting new_stream to NULL after the initial release. Fixes: 9b690ef3c704 ("drm/amd/display: Avoid full modeset when not required") Signed-off-by: WenTao Liang Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 + 1 file changed, 1 insertion(+) 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 0e76a3799b85..e7080880b221 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6046,6 +6046,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, /* Release extra reference */ if (new_stream) dc_stream_release(new_stream); + new_stream = NULL; /* * We want to do dc stream updates that do not require a From 85453fb4ff726e1ddb9984ee83dca260903c5353 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 25 Jun 2026 14:06:47 +0800 Subject: [PATCH 153/155] drm/amd/display: wire DCN42B mcache programming callback DCN42B enables DML2 and DML21 by default and defines dcn42b_prepare_mcache_programming(), but the resource function table only wires the callback when CONFIG_DRM_AMD_DC_DML21 is defined. There is no in-tree Kconfig symbol named DRM_AMD_DC_DML21, so the preprocessor always removes the callback entry. Sibling DCN42 and DCN401 resource tables wire their prepare_mcache_programming callbacks unconditionally, and the core DC code already checks whether the callback pointer is present before calling it. Remove the stale guard so DCN42B exposes the callback relation that its source and DML21 build world already provide. This is an RFC patch draft from static conditional callback legality auditing. It needs AMD display maintainer review before submission as a final fix. Signed-off-by: Pengpeng Hou Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 343e10b4e096..2334bc5b75b8 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -1915,9 +1915,7 @@ static struct resource_funcs dcn42b_res_pool_funcs = { .update_soc_for_wm_a = dcn30_update_soc_for_wm_a, .add_phantom_pipes = dcn32_add_phantom_pipes, .calculate_mall_ways_from_bytes = dcn32_calculate_mall_ways_from_bytes, -#ifdef CONFIG_DRM_AMD_DC_DML21 .prepare_mcache_programming = dcn42b_prepare_mcache_programming, -#endif .build_pipe_pix_clk_params = dcn42b_build_pipe_pix_clk_params, .get_power_profile = dcn401_get_power_profile, .get_vstartup_for_pipe = dcn401_get_vstartup_for_pipe, From 07c93d7eeb0d990bc1b8e3b1eafa464bc9feee97 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 13 Jul 2026 14:53:13 -0500 Subject: [PATCH 154/155] drm/amd: Create a device link between APU display and XHCI devices Some AMD APU multi-function devices expose an integrated USB xHCI controller. In some circumstances (such as larger VRAM), the PM core can resume can fail when the xHCI controller is resuming in parallel with the GPU/display function. On affected systems, the xHCI controller can complete pci_pm_resume and start resuming USB devices while the GPU is still in its much longer resume path. This race condition leads to USB device resume failures followed by: xhci_hcd ...: xHCI host not responding to stop endpoint command xhci_hcd ...: HC died; cleaning up Create a device link from any xHCI controller sharing the same PCIe root port as the APU display function. The link uses DL_FLAG_STATELESS and DL_FLAG_PM_RUNTIME to ensure the GPU completes its resume before the xHCI controller begins resuming USB devices. This device link is done specifically in amdgpu so that if the platform firmware has been modified such that this issue doesn't happen the version can be detected and the workaround skipped. Suggested-by: Aaron Ma Reported-by: mrh@frame.work Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221073 Acked-by: Alex Deucher Tested-by: Mark Pearson Tested-by: Alexander F Tested-by: Francis DB Link: https://patch.msgid.link/20260713195313.1739762-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 12 +++++ drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 11 +++++ .../drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c | 45 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 504fd9762a8e..f4e817885e54 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -1366,6 +1366,14 @@ static void smu_feature_cap_init(struct smu_context *smu) bitmap_zero(fea_cap->cap_map, SMU_FEATURE_CAP_ID__COUNT); } +static int smu_set_power_dep(struct smu_context *smu, bool enable) +{ + if (!smu->ppt_funcs->set_power_dep) + return 0; + + return smu->ppt_funcs->set_power_dep(smu, enable); +} + static int smu_sw_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1427,6 +1435,8 @@ static int smu_sw_init(struct amdgpu_ip_block *ip_block) if (!smu->ppt_funcs->get_fan_control_mode) smu->adev->pm.no_fan = true; + smu_set_power_dep(smu, true); + return 0; } @@ -1449,6 +1459,8 @@ static int smu_sw_fini(struct amdgpu_ip_block *ip_block) smu_fini_microcode(smu); + smu_set_power_dep(smu, false); + return 0; } diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h index f8fd93999617..7ea7c4a5279b 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -749,6 +749,9 @@ struct smu_context { bool pm_enabled; bool is_apu; + /* Power dependency link from an integrated xHCI controller to the GPU */ + struct device_link *usb_power_link; + uint32_t smc_driver_if_version; uint32_t smc_fw_if_version; uint32_t smc_fw_version; @@ -1618,6 +1621,14 @@ struct pptable_funcs { */ int (*ras_send_msg)(struct smu_context *smu, enum smu_message_type msg, uint32_t param, uint32_t *read_arg); + + /** + * @set_power_dep: Create or destroy a power dependency link + * from an integrated xHCI controller to the GPU so that the GPU is + * resumed before the USB controller during PM resume. @enable is true + * to create the link and false to tear it down. + */ + int (*set_power_dep)(struct smu_context *smu, bool enable); }; typedef enum { diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c index 75719c47a41e..3d73f2050bbe 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c @@ -1701,6 +1701,50 @@ static int smu_v14_0_0_restore_user_od_settings(struct smu_context *smu) return 0; } +/* + * Link any xHCI controller sharing the GPU's PCIe root port as a consumer + * of the GPU so the GPU resumes first, avoiding an xHCI resume race. + */ +static int smu_v14_0_0_set_power_dep(struct smu_context *smu, bool enable) +{ + struct amdgpu_device *adev = smu->adev; + struct pci_dev *gpu_pdev = adev->pdev; + struct pci_dev *root_port, *usb_pdev = NULL; + struct device_link *link; + + if (!enable) { + if (smu->usb_power_link) { + device_link_del(smu->usb_power_link); + smu->usb_power_link = NULL; + } + return 0; + } + + root_port = pcie_find_root_port(gpu_pdev); + while ((usb_pdev = pci_get_class(PCI_CLASS_SERIAL_USB_XHCI, usb_pdev))) { + struct pci_dev *usb_root; + + usb_root = pcie_find_root_port(usb_pdev); + if (usb_root != root_port) + continue; + + /* Create device link: USB (consumer) depends on GPU (supplier) */ + link = device_link_add(&usb_pdev->dev, &gpu_pdev->dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); + if (link) { + smu->usb_power_link = link; + drm_info(adev_to_drm(adev), "USB controller %s D0 power state depends on %s\n", + pci_name(usb_pdev), pci_name(gpu_pdev)); + /* Only create one link for the first USB controller found */ + break; + } + } + + pci_dev_put(usb_pdev); + + return 0; +} + static const struct pptable_funcs smu_v14_0_0_ppt_funcs = { .check_fw_status = smu_v14_0_check_fw_status, .check_fw_version = smu_cmn_check_fw_version, @@ -1734,6 +1778,7 @@ static const struct pptable_funcs smu_v14_0_0_ppt_funcs = { .dpm_set_umsch_mm_enable = smu_v14_0_0_set_umsch_mm_enable, .get_dpm_clock_table = smu_v14_0_common_get_dpm_table, .set_mall_enable = smu_v14_0_common_set_mall_enable, + .set_power_dep = smu_v14_0_0_set_power_dep, }; static void smu_v14_0_0_init_msg_ctl(struct smu_context *smu) From 5ab89b491f215f69b750130ead2ff6bbde6c0e2e Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Thu, 9 Jul 2026 10:42:55 +0800 Subject: [PATCH 155/155] drm/amdgpu: add mes gang contex alloc/free helper Implement the MES gang contex alloc and free heplers. Signed-off-by: Prike Liang Reviewed-by: Michael Chen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 41 +++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 7 ++++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 1 + drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 7 +++- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 2 + drivers/gpu/drm/amd/include/mes_v11_api_def.h | 1 + 6 files changed, 58 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 699c6db4ea36..1e3a23c81552 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -1080,6 +1080,47 @@ void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, amdgpu_mes_unlock(mes); } +/** + * amdgpu_mes_alloc_gang_ctx_index - allocate a gang context slot + */ +int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + unsigned long bit; + + if (!mes->use_rs64mem || !mes->gang_ctx_bitmap) + return -EOPNOTSUPP; + + amdgpu_mes_lock(mes); + bit = find_first_zero_bit(mes->gang_ctx_bitmap, + mes->gang_ctx_array_size); + if (bit >= mes->gang_ctx_array_size) { + amdgpu_mes_unlock(mes); + return -ENOSPC; + } + set_bit(bit, mes->gang_ctx_bitmap); + queue->gang_ctx_array_index = bit; + amdgpu_mes_unlock(mes); + + return 0; +} + +/** + * amdgpu_mes_free_gang_ctx_index - free a gang context slot + */ +void amdgpu_mes_free_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + if (!mes->use_rs64mem || !mes->gang_ctx_bitmap) + return; + if (queue->gang_ctx_array_index >= mes->gang_ctx_array_size) + return; + + amdgpu_mes_lock(mes); + clear_bit(queue->gang_ctx_array_index, mes->gang_ctx_bitmap); + amdgpu_mes_unlock(mes); +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 33a426f58e72..c67db2d6e122 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -181,6 +181,7 @@ struct amdgpu_mes { uint32_t proc_ctx_array_size; unsigned long *proc_ctx_bitmap; uint32_t gang_ctx_array_size; + uint32_t gang_ctx_array_index; unsigned long *gang_ctx_bitmap; }; @@ -280,6 +281,7 @@ struct mes_add_queue_input { uint32_t sh_mem_config_data; uint32_t vm_cntx_cntl; uint32_t process_context_array_index; + uint32_t gang_context_array_index; }; struct mes_remove_queue_input { @@ -288,6 +290,7 @@ struct mes_remove_queue_input { uint64_t gang_context_addr; uint32_t queue_type; bool remove_queue_after_reset; + uint32_t gang_context_array_index; }; struct mes_map_legacy_queue_input { @@ -636,4 +639,8 @@ int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, struct amdgpu_usermode_queue *queue); void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, struct amdgpu_usermode_queue *queue); +int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); +void amdgpu_mes_free_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); #endif /* __AMDGPU_MES_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 8bb0d3213abf..8333bab537d4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -102,6 +102,7 @@ struct amdgpu_usermode_queue { } userq_vas; uint32_t proc_ctx_array_index; + uint32_t gang_ctx_array_index; }; struct amdgpu_userq_funcs { diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index c5b01a552592..e099304b4ac4 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -150,6 +150,8 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) if (mes->use_rs64mem) { amdgpu_mes_alloc_proc_ctx_index(mes, queue); queue_input.process_context_array_index = queue->proc_ctx_array_index; + amdgpu_mes_alloc_gang_ctx_index(mes, queue); + queue_input.gang_context_array_index = queue->gang_ctx_array_index; } amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->add_hw_queue(&adev->mes, &queue_input); @@ -173,6 +175,7 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) int r; memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); + queue_input.gang_context_array_index = queue->gang_ctx_array_index; queue_input.doorbell_offset = queue->doorbell_index; queue_input.gang_context_addr = ctx->gpu_addr; queue_input.queue_type = queue->queue_type; @@ -180,8 +183,10 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->remove_hw_queue(&adev->mes, &queue_input); amdgpu_mes_unlock(&adev->mes); - if (mes->use_rs64mem) + if (mes->use_rs64mem) { amdgpu_mes_free_proc_ctx_index(mes, queue); + amdgpu_mes_free_gang_ctx_index(mes, queue); + } if (r) DRM_ERROR("Failed to unmap queue in HW, err (%d)\n", r); return r; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index d8b4d3deff60..b72da75dd851 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -338,6 +338,7 @@ static int mes_v11_0_add_hw_queue(struct amdgpu_mes *mes, mes_add_queue_pkt.process_context_array_index = input->process_context_array_index; mes_add_queue_pkt.gang_quantum = input->gang_quantum; mes_add_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_add_queue_pkt.gang_context_array_index = input->gang_context_array_index; mes_add_queue_pkt.inprocess_gang_priority = convert_to_mes_priority_level(input->inprocess_gang_priority); mes_add_queue_pkt.gang_global_priority_level = @@ -388,6 +389,7 @@ static int mes_v11_0_remove_hw_queue(struct amdgpu_mes *mes, mes_remove_queue_pkt.doorbell_offset = input->doorbell_offset; mes_remove_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_remove_queue_pkt.gang_context_array_index = input->gang_context_array_index; mes_remove_queue_pkt.queue_type = convert_to_mes_queue_type(input->queue_type); diff --git a/drivers/gpu/drm/amd/include/mes_v11_api_def.h b/drivers/gpu/drm/amd/include/mes_v11_api_def.h index 1fe824b41f2a..a8e6ccc7817c 100644 --- a/drivers/gpu/drm/amd/include/mes_v11_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v11_api_def.h @@ -360,6 +360,7 @@ union MESAPI__REMOVE_QUEUE { enum MES_QUEUE_TYPE queue_type; uint64_t timestamp; + uint32_t gang_context_array_index; }; uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS];