From: Bhawanpreet Lakha <[email protected]>

Add KUnit coverage for amdgpu_dm_connector_destroy(): minimal
teardown plus releasing the dc_sink and dc_em_sink references.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <[email protected]>
Signed-off-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Wayne Lin <[email protected]>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   3 +-
 .../display/amdgpu_dm/amdgpu_dm_connector.h   |   1 +
 .../tests/amdgpu_dm_connector_test.c          | 149 ++++++++++++++++++
 drivers/gpu/drm/amd/display/dc/core/dc_sink.c |   1 +
 4 files changed, 153 insertions(+), 1 deletion(-)

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 131387524aa9..d1c95199d598 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
@@ -1776,7 +1776,7 @@ STATIC_IFN_KUNIT void 
amdgpu_dm_connector_unregister(struct drm_connector *conne
 }
 EXPORT_IF_KUNIT(amdgpu_dm_connector_unregister);
 
-static void amdgpu_dm_connector_destroy(struct drm_connector *connector)
+STATIC_IFN_KUNIT void amdgpu_dm_connector_destroy(struct drm_connector 
*connector)
 {
        struct amdgpu_dm_connector *aconnector = 
to_amdgpu_dm_connector(connector);
        struct amdgpu_device *adev = drm_to_adev(connector->dev);
@@ -1817,6 +1817,7 @@ static void amdgpu_dm_connector_destroy(struct 
drm_connector *connector)
 
        kfree(connector);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_connector_destroy);
 
 void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector)
 {
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 f7057e83b4d5..22423e878b64 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
@@ -170,6 +170,7 @@ enum drm_connector_status
 amdgpu_dm_connector_detect(struct drm_connector *connector, bool force);
 void amdgpu_dm_connector_unregister(struct drm_connector *connector);
 int amdgpu_dm_connector_late_register(struct drm_connector *connector);
+void amdgpu_dm_connector_destroy(struct drm_connector *connector);
 enum display_content_type
 get_output_content_type(const struct drm_connector_state *connector_state);
 bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out,
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 645347994734..efaf5730e893 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
@@ -3989,6 +3989,151 @@ static void dm_test_unregister_non_dp_noop(struct kunit 
*test)
        amdgpu_dm_connector_unregister(&aconnector->base);
 }
 
+/* Tests for amdgpu_dm_connector_destroy() */
+
+/*
+ * amdgpu_dm_connector_destroy() ends with drm_connector_cleanup() followed by
+ * kfree(connector), so the connector must be initialised with the unmanaged
+ * drm_connector_init() and allocated with kzalloc() (the function frees it, so
+ * kunit_kzalloc() would double free at teardown). It is embedded in an
+ * amdgpu_device so drm_to_adev() resolves and a dc_link carries a dc_context 
so
+ * dc_sink_create() works for the sink-release branches.
+ */
+struct dm_test_destroy_ctx {
+       struct drm_device *drm;
+       struct dc_context *dc_ctx;
+       struct dc_link *link;
+};
+
+static struct dm_test_destroy_ctx *dm_test_destroy_ctx_alloc(struct kunit 
*test)
+{
+       struct dm_test_destroy_ctx *ctx;
+       struct amdgpu_device *adev;
+       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(*adev),
+                                                      offsetof(struct 
amdgpu_device, ddev),
+                                                      DRIVER_MODESET);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm);
+
+       ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx);
+
+       ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+       ctx->link->ctx = ctx->dc_ctx;
+
+       return ctx;
+}
+
+/*
+ * Allocate a connector the destroy path can free. Uses kzalloc() (not
+ * kunit_kzalloc) and the unmanaged drm_connector_init() because the function
+ * under test calls drm_connector_cleanup() + kfree(connector).
+ *
+ * drm_connector_init() requires funcs->destroy to be set, so a dedicated funcs
+ * table wires it to amdgpu_dm_connector_destroy() (the test invokes it
+ * directly; the connector is removed from the device before teardown).
+ */
+static const struct drm_connector_funcs dm_test_destroy_funcs = {
+       .reset = amdgpu_dm_connector_funcs_reset,
+       .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+       .destroy = amdgpu_dm_connector_destroy,
+};
+
+static struct amdgpu_dm_connector *
+dm_test_destroy_connector(struct kunit *test, struct drm_device *drm)
+{
+       struct amdgpu_dm_connector *aconnector;
+
+       aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+       KUNIT_ASSERT_EQ(test,
+               drm_connector_init(drm, &aconnector->base,
+                                  &dm_test_destroy_funcs,
+                                  DRM_MODE_CONNECTOR_VGA), 0);
+       aconnector->bl_idx = -1;
+
+       return aconnector;
+}
+
+/**
+ * dm_test_destroy_minimal - Test destroy tears down a bare connector
+ * @test: The KUnit test context
+ *
+ * With no MST, backlight, sinks or registered AUX/CEC, destroy must clean up
+ * and free the connector without crashing.
+ */
+static void dm_test_destroy_minimal(struct kunit *test)
+{
+       struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test);
+       struct amdgpu_dm_connector *aconnector =
+               dm_test_destroy_connector(test, ctx->drm);
+
+       amdgpu_dm_connector_destroy(&aconnector->base);
+}
+
+/**
+ * dm_test_destroy_releases_dc_sink - Test destroy releases the dc_sink
+ * @test: The KUnit test context
+ */
+static void dm_test_destroy_releases_dc_sink(struct kunit *test)
+{
+       struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test);
+       struct amdgpu_dm_connector *aconnector =
+               dm_test_destroy_connector(test, ctx->drm);
+       struct dc_sink_init_data sink_init = { 0 };
+       struct dc_sink *sink;
+
+       sink_init.link = ctx->link;
+       sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL;
+       sink = dc_sink_create(&sink_init);
+       KUNIT_ASSERT_NOT_NULL(test, sink);
+
+       /* Extra reference so the sink survives destroy for inspection. */
+       dc_sink_retain(sink);
+       aconnector->dc_sink = sink;
+
+       amdgpu_dm_connector_destroy(&aconnector->base);
+
+       KUNIT_EXPECT_EQ(test, (int)kref_read(&sink->refcount), 1);
+       dc_sink_release(sink);
+}
+
+/**
+ * dm_test_destroy_releases_dc_em_sink - Test destroy releases the emulated 
sink
+ * @test: The KUnit test context
+ */
+static void dm_test_destroy_releases_dc_em_sink(struct kunit *test)
+{
+       struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test);
+       struct amdgpu_dm_connector *aconnector =
+               dm_test_destroy_connector(test, ctx->drm);
+       struct dc_sink_init_data sink_init = { 0 };
+       struct dc_sink *sink;
+
+       sink_init.link = ctx->link;
+       sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL;
+       sink = dc_sink_create(&sink_init);
+       KUNIT_ASSERT_NOT_NULL(test, sink);
+
+       dc_sink_retain(sink);
+       aconnector->dc_em_sink = sink;
+
+       amdgpu_dm_connector_destroy(&aconnector->base);
+
+       KUNIT_EXPECT_EQ(test, (int)kref_read(&sink->refcount), 1);
+       dc_sink_release(sink);
+}
+
 static struct kunit_case amdgpu_dm_connector_tests[] = {
        /* get_subconnector_type */
        KUNIT_CASE(dm_test_subconnector_type_none),
@@ -4201,6 +4346,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
        KUNIT_CASE(dm_test_late_register_non_dp_succeeds),
        /* amdgpu_dm_connector_unregister */
        KUNIT_CASE(dm_test_unregister_non_dp_noop),
+       /* amdgpu_dm_connector_destroy */
+       KUNIT_CASE(dm_test_destroy_minimal),
+       KUNIT_CASE(dm_test_destroy_releases_dc_sink),
+       KUNIT_CASE(dm_test_destroy_releases_dc_em_sink),
        {}
 };
 
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c 
b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c
index 436d033361ab..79b9738e9c68 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c
@@ -61,6 +61,7 @@ void dc_sink_retain(struct dc_sink *sink)
 {
        kref_get(&sink->refcount);
 }
+EXPORT_IF_KUNIT(dc_sink_retain);
 
 static void dc_sink_free(struct kref *kref)
 {
-- 
2.43.0

Reply via email to