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

Reply via email to