From: Alex Hung <[email protected]>

[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 <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
Signed-off-by: George Zhang <[email protected]>
---
 .../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 04e3c34b9e50..48ed6d8d9580 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),
        {}
 };

--
2.55.0

Reply via email to