From: Bhawanpreet Lakha <[email protected]> [Why & How] Add KUnit coverage for the EDID CEA extension parsing helpers dm_edid_parser_send_cea(), parse_edid_cea_dmcu(), parse_edid_cea_dmub() and parse_edid_cea(). Tests exercise the oversized-chunk rejection and DMUB-unavailable paths of the send helper, the DMCU ack/VSDB branches via a mock dmcu_funcs table, the DMUB empty/send-fail branches, and both routing branches of parse_edid_cea().
Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung <[email protected]> Signed-off-by: Bhawanpreet Lakha <[email protected]> Signed-off-by: Ray Wu <[email protected]> --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 12 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 10 + .../tests/amdgpu_dm_connector_test.c | 337 ++++++++++++++++++ 3 files changed, 355 insertions(+), 4 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 1b184699e955..6a41ccd1b5b9 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 @@ -3544,7 +3544,7 @@ void dm_restore_drm_connector_state(struct drm_device *dev, } EXPORT_IF_KUNIT(dm_restore_drm_connector_state); -static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, +STATIC_IFN_KUNIT bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, unsigned int offset, unsigned int total_length, u8 *data, @@ -3601,8 +3601,9 @@ static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, return true; } +EXPORT_IF_KUNIT(dm_edid_parser_send_cea); -static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, +STATIC_IFN_KUNIT bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, u8 *edid_ext, int len, struct amdgpu_hdmi_vsdb_info *vsdb_info) { @@ -3644,8 +3645,9 @@ static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, return false; } +EXPORT_IF_KUNIT(parse_edid_cea_dmcu); -static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, +STATIC_IFN_KUNIT bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, u8 *edid_ext, int len, struct amdgpu_hdmi_vsdb_info *vsdb_info) { @@ -3660,8 +3662,9 @@ static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, return vsdb_info->freesync_supported; } +EXPORT_IF_KUNIT(parse_edid_cea_dmub); -static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, u8 *edid_ext, int len, struct amdgpu_hdmi_vsdb_info *vsdb_info) { @@ -3676,6 +3679,7 @@ static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, mutex_unlock(&adev->dm.dc_lock); return ret; } +EXPORT_IF_KUNIT(parse_edid_cea); STATIC_IFN_KUNIT void parse_edid_displayid_vrr(struct drm_connector *connector, const struct edid *edid) 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 6732e5e644cd..e42a0d53b549 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,6 +158,16 @@ int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, const struct edid *edid, struct amdgpu_hdmi_vsdb_info *vsdb_info); +bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, + unsigned int offset, unsigned int total_length, + u8 *data, unsigned int length, + struct amdgpu_hdmi_vsdb_info *vsdb); +bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, u8 *edid_ext, + int len, struct amdgpu_hdmi_vsdb_info *vsdb_info); +bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, u8 *edid_ext, + int len, struct amdgpu_hdmi_vsdb_info *vsdb_info); +bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, u8 *edid_ext, + int len, struct amdgpu_hdmi_vsdb_info *vsdb_info); void amdgpu_dm_connector_funcs_force(struct drm_connector *connector); enum dc_status dm_validate_stream_and_context(struct dc *dc, struct dc_stream_state *stream); 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 9b3802dc6836..dc5bf2d9daca 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 @@ -6099,6 +6099,331 @@ static void dm_test_restore_state_same_sink(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, stream->sink, aconnector->dc_sink); } +/* Mock DMCU plumbing for parse_edid_cea_dmcu() tests. */ +static bool dm_test_dmcu_is_init(struct dmcu *dmcu) +{ + return true; +} + +static bool dm_test_dmcu_send_cea(struct dmcu *dmcu, int offset, + int total_length, u8 *data, int length) +{ + return true; +} + +static bool dm_test_dmcu_recv_ack_ok(struct dmcu *dmcu, int *offset) +{ + *offset = 0; + return true; +} + +static bool dm_test_dmcu_recv_ack_fail(struct dmcu *dmcu, int *offset) +{ + return false; +} + +static bool dm_test_dmcu_recv_vsdb_found(struct dmcu *dmcu, int *version, + int *min_frame_rate, int *max_frame_rate) +{ + *version = 2; + *min_frame_rate = 24; + *max_frame_rate = 60; + return true; +} + +static bool dm_test_dmcu_recv_vsdb_none(struct dmcu *dmcu, int *version, + int *min_frame_rate, int *max_frame_rate) +{ + return false; +} + +static const struct dmcu_funcs dm_test_dmcu_funcs_vsdb = { + .is_dmcu_initialized = dm_test_dmcu_is_init, + .send_edid_cea = dm_test_dmcu_send_cea, + .recv_edid_cea_ack = dm_test_dmcu_recv_ack_ok, + .recv_amd_vsdb = dm_test_dmcu_recv_vsdb_found, +}; + +static const struct dmcu_funcs dm_test_dmcu_funcs_novsdb = { + .is_dmcu_initialized = dm_test_dmcu_is_init, + .send_edid_cea = dm_test_dmcu_send_cea, + .recv_edid_cea_ack = dm_test_dmcu_recv_ack_ok, + .recv_amd_vsdb = dm_test_dmcu_recv_vsdb_none, +}; + +static const struct dmcu_funcs dm_test_dmcu_funcs_ackfail = { + .is_dmcu_initialized = dm_test_dmcu_is_init, + .send_edid_cea = dm_test_dmcu_send_cea, + .recv_edid_cea_ack = dm_test_dmcu_recv_ack_fail, + .recv_amd_vsdb = dm_test_dmcu_recv_vsdb_none, +}; + +/* + * Build a bare display manager carrying a dc with a resource pool whose DMCU + * uses the supplied funcs table (or no DMCU at all when @funcs is NULL). The + * DMCU CEA parser path only touches dm->dc->res_pool->dmcu, so no adev/ctx is + * required. + */ +static struct amdgpu_display_manager * +dm_test_alloc_dm_dmcu(struct kunit *test, const struct dmcu_funcs *funcs) +{ + struct amdgpu_display_manager *dm; + struct resource_pool *pool; + struct dmcu *dmcu = NULL; + struct dc *dc; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pool); + + if (funcs) { + dmcu = kunit_kzalloc(test, sizeof(*dmcu), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dmcu); + dmcu->funcs = funcs; + } + + pool->dmcu = dmcu; + dc->res_pool = pool; + dm->dc = dc; + return dm; +} + +/* + * Build a display manager backed by an amdgpu_device and a dc with a ctx (but + * no DMUB), so the DMUB CEA send path can build a command, fail the execute, + * and log via drm_err() safely. + */ +static struct amdgpu_display_manager *dm_test_alloc_dm_adev(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + adev->dm.adev = adev; + adev->dm.dc = dc; + return &adev->dm; +} + +/** + * dm_test_send_cea_length_too_long - Test an oversized chunk is rejected + * @test: KUnit test context + */ +static void dm_test_send_cea_length_too_long(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_test_alloc_dm_adev(test); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 data[16] = {0}; + bool ret; + + ret = dm_edid_parser_send_cea(dm, 0, 128, data, + DMUB_EDID_CEA_DATA_CHUNK_BYTES + 1, &vsdb); + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_send_cea_dmub_unavailable - Test a failed DMUB command reports false + * @test: KUnit test context + */ +static void dm_test_send_cea_dmub_unavailable(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_test_alloc_dm_adev(test); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 data[DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + /* ctx->dmub_srv is NULL, so the DMUB command execute fails. */ + ret = dm_edid_parser_send_cea(dm, 0, 8, data, + DMUB_EDID_CEA_DATA_CHUNK_BYTES, &vsdb); + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_parse_cea_dmcu_empty - Test an empty extension parses to no VSDB + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmcu_empty(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_test_alloc_dm_dmcu(test, NULL); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[1] = {0}; + + KUNIT_EXPECT_FALSE(test, parse_edid_cea_dmcu(dm, ext, 0, &vsdb)); +} + +/** + * dm_test_parse_cea_dmcu_no_dmcu - Test a missing DMCU fails the first send + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmcu_no_dmcu(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_test_alloc_dm_dmcu(test, NULL); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + /* res_pool->dmcu is NULL, so the CEA send returns false. */ + ret = parse_edid_cea_dmcu(dm, ext, DMUB_EDID_CEA_DATA_CHUNK_BYTES, + &vsdb); + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_parse_cea_dmcu_vsdb_found - Test the DMCU reports an AMD VSDB + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmcu_vsdb_found(struct kunit *test) +{ + struct amdgpu_display_manager *dm = + dm_test_alloc_dm_dmcu(test, &dm_test_dmcu_funcs_vsdb); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + ret = parse_edid_cea_dmcu(dm, ext, DMUB_EDID_CEA_DATA_CHUNK_BYTES, + &vsdb); + KUNIT_EXPECT_TRUE(test, ret); + KUNIT_EXPECT_TRUE(test, vsdb.freesync_supported); + KUNIT_EXPECT_EQ(test, vsdb.amd_vsdb_version, 2); + KUNIT_EXPECT_EQ(test, vsdb.min_refresh_rate_hz, 24); + KUNIT_EXPECT_EQ(test, vsdb.max_refresh_rate_hz, 60); + KUNIT_EXPECT_EQ(test, vsdb.freesync_mccs_vcp_code, 0); +} + +/** + * dm_test_parse_cea_dmcu_vsdb_none - Test the DMCU finds no AMD VSDB + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmcu_vsdb_none(struct kunit *test) +{ + struct amdgpu_display_manager *dm = + dm_test_alloc_dm_dmcu(test, &dm_test_dmcu_funcs_novsdb); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + ret = parse_edid_cea_dmcu(dm, ext, DMUB_EDID_CEA_DATA_CHUNK_BYTES, + &vsdb); + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_parse_cea_dmcu_multi_chunk - Test intermediate chunks are acked + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmcu_multi_chunk(struct kunit *test) +{ + struct amdgpu_display_manager *dm = + dm_test_alloc_dm_dmcu(test, &dm_test_dmcu_funcs_vsdb); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[2 * DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + ret = parse_edid_cea_dmcu(dm, ext, 2 * DMUB_EDID_CEA_DATA_CHUNK_BYTES, + &vsdb); + KUNIT_EXPECT_TRUE(test, ret); +} + +/** + * dm_test_parse_cea_dmcu_ack_fail - Test a failed chunk ack aborts the parse + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmcu_ack_fail(struct kunit *test) +{ + struct amdgpu_display_manager *dm = + dm_test_alloc_dm_dmcu(test, &dm_test_dmcu_funcs_ackfail); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[2 * DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + ret = parse_edid_cea_dmcu(dm, ext, 2 * DMUB_EDID_CEA_DATA_CHUNK_BYTES, + &vsdb); + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_parse_cea_dmub_empty - Test an empty extension returns the flag + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmub_empty(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_test_alloc_dm_adev(test); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[1] = {0}; + + /* No chunks are sent, so the freesync flag is returned as-is. */ + vsdb.freesync_supported = true; + KUNIT_EXPECT_TRUE(test, parse_edid_cea_dmub(dm, ext, 0, &vsdb)); +} + +/** + * dm_test_parse_cea_dmub_send_fail - Test a chunk send failure aborts the parse + * @test: KUnit test context + */ +static void dm_test_parse_cea_dmub_send_fail(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_test_alloc_dm_adev(test); + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[DMUB_EDID_CEA_DATA_CHUNK_BYTES] = {0}; + bool ret; + + /* The first chunk send fails (no DMUB), so the parse bails out. */ + ret = parse_edid_cea_dmub(dm, ext, DMUB_EDID_CEA_DATA_CHUNK_BYTES, + &vsdb); + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_parse_cea_routes_dmub - Test a present dmub_srv routes to DMUB + * @test: KUnit test context + */ +static void dm_test_parse_cea_routes_dmub(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[1] = {0}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + mutex_init(&adev->dm.dc_lock); + adev->dm.dmub_srv = kunit_kzalloc(test, 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dmub_srv); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->base.dev = &adev->ddev; + + /* len 0 routes to the DMUB parser, which returns the freesync flag. */ + vsdb.freesync_supported = true; + KUNIT_EXPECT_TRUE(test, parse_edid_cea(aconnector, ext, 0, &vsdb)); +} + +/** + * dm_test_parse_cea_routes_dmcu - Test a missing dmub_srv routes to DMCU + * @test: KUnit test context + */ +static void dm_test_parse_cea_routes_dmcu(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb = {0}; + u8 ext[1] = {0}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + mutex_init(&adev->dm.dc_lock); + adev->dm.dmub_srv = NULL; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->base.dev = &adev->ddev; + + /* len 0 routes to the DMCU parser, which returns false. */ + KUNIT_EXPECT_FALSE(test, parse_edid_cea(aconnector, ext, 0, &vsdb)); +} + /** * dm_test_get_amd_vsdb_unsupported - Test a zero VSDB version reports no support * @test: The KUnit test context @@ -8862,6 +9187,18 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_restore_state_no_encoder), KUNIT_CASE(dm_test_restore_state_no_stream), KUNIT_CASE(dm_test_restore_state_same_sink), + KUNIT_CASE(dm_test_send_cea_length_too_long), + KUNIT_CASE(dm_test_send_cea_dmub_unavailable), + KUNIT_CASE(dm_test_parse_cea_dmcu_empty), + KUNIT_CASE(dm_test_parse_cea_dmcu_no_dmcu), + KUNIT_CASE(dm_test_parse_cea_dmcu_vsdb_found), + KUNIT_CASE(dm_test_parse_cea_dmcu_vsdb_none), + KUNIT_CASE(dm_test_parse_cea_dmcu_multi_chunk), + KUNIT_CASE(dm_test_parse_cea_dmcu_ack_fail), + KUNIT_CASE(dm_test_parse_cea_dmub_empty), + KUNIT_CASE(dm_test_parse_cea_dmub_send_fail), + KUNIT_CASE(dm_test_parse_cea_routes_dmub), + KUNIT_CASE(dm_test_parse_cea_routes_dmcu), /* get_amd_vsdb */ KUNIT_CASE(dm_test_get_amd_vsdb_unsupported), KUNIT_CASE(dm_test_get_amd_vsdb_supported), -- 2.43.0
