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
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.