From: Bhawanpreet Lakha <[email protected]>

Add KUnit tests covering amdgpu_dm_update_connector_after_detect(): the
MST-sink early return, connect with and without EDID, disconnect teardown,
and the four forced emulated-sink paths. Sinks the function releases to
zero use real allocations while retained sinks use kunit-managed memory to
keep dc_sink refcounting balanced.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <[email protected]>
Signed-off-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Ivan Lipski <[email protected]>
---
 .../tests/amdgpu_dm_connector_test.c          | 331 ++++++++++++++++++
 1 file changed, 331 insertions(+)

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 14240e6485049..61cd1ebe9fcd1 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
@@ -5516,6 +5516,329 @@ static void 
dm_test_update_after_detect_sink_unchanged(struct kunit *test)
        KUNIT_EXPECT_NULL(test, aconnector->dc_sink);
 }
 
+/* Tests for amdgpu_dm_update_connector_after_detect() */
+
+/*
+ * A minimal but structurally valid 128-byte EDID base block (correct header
+ * and checksum) so drm_edid_alloc()/drm_edid_connector_update() accept it when
+ * exercising the "sink carries EDID" branch.
+ */
+static const u8 dm_test_uad_edid[128] = {
+       0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x21, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c,
+};
+
+/*
+ * 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 for
+ * amdgpu_dm_update_connector_after_detect() and all of its helpers.
+ *
+ * bl_idx is forced to -1 and adev->dm.freesync_module is left NULL so the
+ * backlight, CEC and freesync helpers take their early-return paths and the
+ * test stays focused on the sink-adoption logic.
+ */
+struct dm_test_uad_ctx {
+       struct amdgpu_device *adev;
+       struct drm_device *drm;
+       struct amdgpu_dm_connector *aconnector;
+       struct dc_link *link;
+};
+
+static struct dm_test_uad_ctx *
+dm_test_uad_ctx_alloc(struct kunit *test, int connector_type)
+{
+       struct dm_test_uad_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->adev),
+                       offsetof(struct amdgpu_device, ddev),
+                       DRIVER_MODESET);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm);
+       ctx->adev = drm_to_adev(ctx->drm);
+
+       ctx->aconnector = drmm_kzalloc(ctx->drm, 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, connector_type,
+                       NULL), 0);
+
+       ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+       ctx->aconnector->dc_link = ctx->link;
+
+       /* Keep the backlight/CEC/freesync helpers on their early-return paths. 
*/
+       ctx->aconnector->bl_idx = -1;
+
+       return ctx;
+}
+
+/* A real (non-kunit) sink the function is expected to free via 
dc_sink_release. */
+static struct dc_sink *dm_test_uad_owned_sink(void)
+{
+       struct dc_sink *sink = kzalloc_obj(*sink, GFP_KERNEL);
+
+       if (sink)
+               kref_init(&sink->refcount);
+       return sink;
+}
+
+/* A kunit-managed sink that stays referenced (never released to zero). */
+static struct dc_sink *dm_test_uad_kept_sink(struct kunit *test)
+{
+       struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+
+       if (sink)
+               kref_init(&sink->refcount);
+       return sink;
+}
+
+/**
+ * dm_test_update_after_detect_mst_sink - Test an MST sink is left to drm_mst
+ * @test: The KUnit test context
+ *
+ * A local sink reporting SIGNAL_TYPE_DISPLAY_PORT_MST is handled by the
+ * drm_mst framework, so the function returns before adopting it.
+ */
+static void dm_test_update_after_detect_mst_sink(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, sink);
+       sink->sink_signal = SIGNAL_TYPE_DISPLAY_PORT_MST;
+       ctx->link->local_sink = sink;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_sink);
+}
+
+/**
+ * dm_test_update_after_detect_connect_edid - Test adopting a new sink with 
EDID
+ * @test: The KUnit test context
+ *
+ * A freshly detected DisplayPort sink carrying EDID is adopted: the connector
+ * takes the sink, allocates a drm_edid and a requested-timing structure.
+ */
+static void dm_test_update_after_detect_connect_edid(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, sink);
+       sink->sink_signal = SIGNAL_TYPE_DISPLAY_PORT;
+       memcpy(sink->dc_edid.raw_edid, dm_test_uad_edid, 
sizeof(dm_test_uad_edid));
+       sink->dc_edid.length = sizeof(dm_test_uad_edid);
+       ctx->link->local_sink = sink;
+       ctx->link->aux_mode = true;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, sink);
+       KUNIT_EXPECT_NOT_NULL(test, ctx->aconnector->timing_requested);
+
+       kfree(ctx->aconnector->timing_requested);
+       ctx->aconnector->timing_requested = NULL;
+}
+
+/**
+ * dm_test_update_after_detect_replace_no_edid - Test replacing a sink with no
+ * EDID and HDMI compression auto
+ * @test: The KUnit test context
+ *
+ * When a new EDID-less sink replaces an existing one, the old sink is released
+ * and, with hdmi_comp_auto set, an HDMI sink signal is promoted to FRL.
+ */
+static void dm_test_update_after_detect_replace_no_edid(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *old_sink = dm_test_uad_owned_sink();
+       struct dc_sink *new_sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, old_sink);
+       KUNIT_ASSERT_NOT_NULL(test, new_sink);
+
+       ctx->aconnector->dc_sink = old_sink;
+       new_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+       new_sink->dc_edid.length = 0;
+       ctx->link->local_sink = new_sink;
+       ctx->link->aux_mode = true;
+       ctx->aconnector->hdmi_comp_auto = true;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, new_sink);
+       KUNIT_EXPECT_EQ(test, (int)new_sink->sink_signal,
+                       (int)SIGNAL_TYPE_HDMI_FRL);
+}
+
+/**
+ * dm_test_update_after_detect_disconnect - Test tearing down on unplug
+ * @test: The KUnit test context
+ *
+ * With no local sink but an existing dc_sink, the disconnect path releases the
+ * sink, clears modes/timing, downgrades content protection and notifies audio.
+ */
+static void dm_test_update_after_detect_disconnect(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *old_sink = dm_test_uad_owned_sink();
+       struct dc_crtc_timing *timing = kzalloc_obj(*timing, GFP_KERNEL);
+
+       KUNIT_ASSERT_NOT_NULL(test, old_sink);
+       KUNIT_ASSERT_NOT_NULL(test, timing);
+
+       mutex_init(&ctx->adev->dm.audio_lock);
+       ctx->aconnector->dc_sink = old_sink;
+       ctx->aconnector->timing_requested = timing;
+       ctx->aconnector->num_modes = 3;
+       ctx->aconnector->audio_inst = 5;
+       ctx->link->local_sink = NULL;
+
+       amdgpu_dm_connector_funcs_reset(&ctx->aconnector->base);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector->base.state);
+       ctx->aconnector->base.state->content_protection =
+               DRM_MODE_CONTENT_PROTECTION_ENABLED;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_sink);
+       KUNIT_EXPECT_NULL(test, ctx->aconnector->timing_requested);
+       KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 0);
+       KUNIT_EXPECT_EQ(test, ctx->aconnector->audio_inst, -1);
+       KUNIT_EXPECT_EQ(test,
+                       (int)ctx->aconnector->base.state->content_protection,
+                       (int)DRM_MODE_CONTENT_PROTECTION_DESIRED);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_adopt - Test forced eml_sink adoption
+ * @test: The KUnit test context
+ *
+ * A forced connector with an emulated sink adopts a newly reported local sink
+ * under the mode_config lock.
+ */
+static void dm_test_update_after_detect_force_em_adopt(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+       struct dc_sink *sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, em_sink);
+       KUNIT_ASSERT_NOT_NULL(test, sink);
+
+       ctx->aconnector->base.force = DRM_FORCE_ON;
+       ctx->aconnector->dc_em_sink = em_sink;
+       ctx->link->local_sink = sink;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, sink);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_replace - Test forced eml_sink replace
+ * @test: The KUnit test context
+ *
+ * A forced connector that already has a dc_sink releases it before adopting 
the
+ * newly reported local sink.
+ */
+static void dm_test_update_after_detect_force_em_replace(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+       struct dc_sink *old_sink = dm_test_uad_owned_sink();
+       struct dc_sink *new_sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, em_sink);
+       KUNIT_ASSERT_NOT_NULL(test, old_sink);
+       KUNIT_ASSERT_NOT_NULL(test, new_sink);
+
+       ctx->aconnector->base.force = DRM_FORCE_ON;
+       ctx->aconnector->dc_em_sink = em_sink;
+       ctx->aconnector->dc_sink = old_sink;
+       ctx->link->local_sink = new_sink;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, new_sink);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_fake - Test forced fallback to eml_sink
+ * @test: The KUnit test context
+ *
+ * A forced connector with no local sink and no dc_sink falls back to using the
+ * emulated sink so a headless stream can still be faked.
+ */
+static void dm_test_update_after_detect_force_em_fake(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, em_sink);
+
+       ctx->aconnector->base.force = DRM_FORCE_ON;
+       ctx->aconnector->dc_em_sink = em_sink;
+       ctx->link->local_sink = NULL;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, em_sink);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_keep - Test forced no-sink keeps 
dc_sink
+ * @test: The KUnit test context
+ *
+ * A forced connector with no local sink but an existing dc_sink keeps that 
sink
+ * (the emulated-sink fallback is skipped).
+ */
+static void dm_test_update_after_detect_force_em_keep(struct kunit *test)
+{
+       struct dm_test_uad_ctx *ctx =
+               dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+       struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+       struct dc_sink *dc_sink = dm_test_uad_kept_sink(test);
+
+       KUNIT_ASSERT_NOT_NULL(test, em_sink);
+       KUNIT_ASSERT_NOT_NULL(test, dc_sink);
+
+       ctx->aconnector->base.force = DRM_FORCE_ON;
+       ctx->aconnector->dc_em_sink = em_sink;
+       ctx->aconnector->dc_sink = dc_sink;
+       ctx->link->local_sink = NULL;
+
+       amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+       KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, dc_sink);
+}
+
 /* Tests for amdgpu_dm_update_stream_scaling_settings() */
 
 /**
@@ -6007,6 +6330,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
        /* amdgpu_dm_update_connector_after_detect */
        KUNIT_CASE(dm_test_update_after_detect_mst_noop),
        KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
+       KUNIT_CASE(dm_test_update_after_detect_mst_sink),
+       KUNIT_CASE(dm_test_update_after_detect_connect_edid),
+       KUNIT_CASE(dm_test_update_after_detect_replace_no_edid),
+       KUNIT_CASE(dm_test_update_after_detect_disconnect),
+       KUNIT_CASE(dm_test_update_after_detect_force_em_adopt),
+       KUNIT_CASE(dm_test_update_after_detect_force_em_replace),
+       KUNIT_CASE(dm_test_update_after_detect_force_em_fake),
+       KUNIT_CASE(dm_test_update_after_detect_force_em_keep),
        /* amdgpu_dm_update_stream_scaling_settings */
        KUNIT_CASE(dm_test_update_scaling_null_mode),
        KUNIT_CASE(dm_test_update_scaling_fullscreen_default),
-- 
2.43.0

Reply via email to