From: Alex Hung <[email protected]>

[WHAT]
Add KUnit tests for dm_dp_add_mst_connector() covering successful
connector creation, inheritance of the master connector properties and
the connector init failure path.

[HOW]
Set up an MST topology manager owned by a master amdgpu_dm_connector
plus the mode properties the helper attaches, then add a port and check
the resulting connector, its property values and the encoder links. The
failure path is driven by a mode config without the connector functions
required by drm_connector_init(), and the port reference count is
checked to confirm the error unwind releases it.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
Signed-off-by: Chenyu Chen <[email protected]>
---
 .../display/amdgpu_dm/amdgpu_dm_mst_types.c   |   3 +-
 .../display/amdgpu_dm/amdgpu_dm_mst_types.h   |   4 +
 .../tests/amdgpu_dm_mst_types_test.c          | 202 ++++++++++++++++++
 3 files changed, 208 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
index aad8fff2cd6f..f857303fe386 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
@@ -660,7 +660,7 @@ dm_dp_create_fake_mst_encoders(struct amdgpu_device *adev)
 }
 EXPORT_IF_KUNIT(dm_dp_create_fake_mst_encoders);
 
-static struct drm_connector *
+STATIC_IFN_KUNIT struct drm_connector *
 dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr,
                        struct drm_dp_mst_port *port,
                        const char *pathprop)
@@ -742,6 +742,7 @@ dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr,
 
        return connector;
 }
+EXPORT_IF_KUNIT(dm_dp_add_mst_connector);
 
 /*
  * Select the ESI[1] mask used to filter the MST sideband ready bits for a
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h
index 50dca0ef98fe..0e6189219eb2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h
@@ -65,6 +65,7 @@ struct dc_state;
 struct dc_stream_state;
 struct dm_atomic_state;
 struct drm_atomic_commit;
+struct drm_dp_mst_port;
 struct drm_dp_mst_topology_mgr;
 
 uint32_t dm_mst_get_pbn_divider(struct dc_link *link);
@@ -119,6 +120,9 @@ int dm_dp_mst_atomic_check(struct drm_connector *connector,
 int dm_dp_mst_detect(struct drm_connector *connector,
                             struct drm_modeset_acquire_ctx *ctx, bool force);
 int dm_dp_mst_get_modes(struct drm_connector *connector);
+struct drm_connector *dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr 
*mgr,
+                                             struct drm_dp_mst_port *port,
+                                             const char *pathprop);
 int amdgpu_dm_mst_connector_late_register(struct drm_connector *connector);
 void amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector);
 void dm_dp_mst_connector_destroy(struct drm_connector *connector);
diff --git 
a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c
index af2dd5b8e79d..f2bae3a50941 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c
@@ -15,6 +15,7 @@
 #include <drm/drm_connector.h>
 #include <drm/drm_mode_config.h>
 #include <drm/drm_modeset_lock.h>
+#include <drm/drm_property.h>
 #include <drm/display/drm_dp.h>
 #include <drm/display/drm_dp_helper.h>
 #include <drm/display/drm_dp_mst_helper.h>
@@ -1297,6 +1298,203 @@ static void dm_mst_test_create_fake_mst_encoders(struct 
kunit *test)
        }
 }
 
+/* Tests for dm_dp_add_mst_connector */
+
+struct dm_mst_test_add_ctx {
+       struct amdgpu_device *adev;
+       struct amdgpu_dm_connector *master;
+       struct drm_dp_mst_port *port;
+       struct dc_link *link;
+};
+
+/*
+ * Stand in for amdgpu_display_modeset_create_props(), which the test module
+ * cannot link against. Only the properties amdgpu_dm_connector_init_helper()
+ * attaches are created.
+ */
+static void dm_mst_test_create_mode_props(struct kunit *test, struct 
amdgpu_device *adev)
+{
+       struct amdgpu_mode_info *mode_info = &adev->mode_info;
+       struct drm_device *drm = adev_to_drm(adev);
+
+       KUNIT_ASSERT_EQ(test, drm_mode_create_scaling_mode_property(drm), 0);
+
+       mode_info->underscan_property =
+               drm_property_create_range(drm, 0, "underscan", 0, 1);
+       mode_info->underscan_hborder_property =
+               drm_property_create_range(drm, 0, "underscan hborder", 0, 128);
+       mode_info->underscan_vborder_property =
+               drm_property_create_range(drm, 0, "underscan vborder", 0, 128);
+       KUNIT_ASSERT_NOT_NULL(test, mode_info->underscan_property);
+       KUNIT_ASSERT_NOT_NULL(test, mode_info->underscan_hborder_property);
+       KUNIT_ASSERT_NOT_NULL(test, mode_info->underscan_vborder_property);
+}
+
+/*
+ * Build the MST root connector and the topology port the new downstream
+ * connector hangs off. The port has no parent branch so the callback takes the
+ * "no branch descriptor" path without needing a sideband AUX backend.
+ */
+static void dm_mst_test_init_add_ctx(struct kunit *test, struct 
dm_mst_test_add_ctx *ctx)
+{
+       struct amdgpu_device *adev;
+       struct drm_device *drm;
+       int ret;
+
+       adev = dm_kunit_alloc_adev(test);
+       drm = &adev->ddev;
+       ret = drmm_mode_config_init(drm);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+       dm_mst_test_create_mode_props(test, adev);
+
+       adev->dm.adev = adev;
+       adev->dm.ddev = drm;
+       adev->dm.display_indexes_num = 2;
+       adev->mode_info.num_crtc = 2;
+       dm_dp_create_fake_mst_encoders(adev);
+
+       ctx->adev = adev;
+       ctx->link = dm_kunit_alloc_link(test);
+       /* Inflexible DIG mapping lets link_enc_cfg_get_link_enc() skip the dc 
lookup. */
+       ctx->link->link_enc = kunit_kzalloc(test, sizeof(*ctx->link->link_enc), 
GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->link->link_enc);
+
+       ctx->master = dm_kunit_alloc_connector(test, adev, ctx->link);
+       ctx->master->connector_id = 5;
+       ret = drm_connector_init(drm, &ctx->master->base, 
&dm_mst_test_connector_funcs,
+                                DRM_MODE_CONNECTOR_DisplayPort);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ctx->port = kunit_kzalloc(test, sizeof(*ctx->port), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->port);
+       kref_init(&ctx->port->malloc_kref);
+       ctx->port->mgr = &ctx->master->mst_mgr;
+}
+
+/* True when @prop is attached to the connector's mode object. */
+static bool dm_mst_test_has_prop(struct drm_connector *connector, struct 
drm_property *prop)
+{
+       struct drm_object_properties *props = connector->base.properties;
+       int i;
+
+       if (!prop)
+               return false;
+
+       for (i = 0; i < props->count; i++)
+               if (props->properties[i] == prop)
+                       return true;
+
+       return false;
+}
+
+/**
+ * dm_mst_test_add_mst_connector_creates - Test downstream connector creation
+ * @test: KUnit test context
+ *
+ * The topology callback must allocate a DisplayPort connector bound to the 
port
+ * and the root connector, inherit the root's dc_link and connector id, attach
+ * every fake MST encoder, publish the path property and take a malloc
+ * reference on the port.
+ */
+static void dm_mst_test_add_mst_connector_creates(struct kunit *test)
+{
+       struct amdgpu_dm_connector *aconnector;
+       struct dm_mst_test_add_ctx ctx;
+       struct drm_connector *connector;
+
+       dm_mst_test_init_add_ctx(test, &ctx);
+
+       connector = dm_dp_add_mst_connector(&ctx.master->mst_mgr, ctx.port, 
"1-3");
+       KUNIT_ASSERT_NOT_NULL(test, connector);
+
+       aconnector = to_amdgpu_dm_connector(connector);
+       KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_output_port, ctx.port);
+       KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_root, ctx.master);
+       KUNIT_EXPECT_PTR_EQ(test, aconnector->dc_link, ctx.link);
+       KUNIT_EXPECT_EQ(test, aconnector->connector_id, 5);
+       KUNIT_EXPECT_TRUE(test, aconnector->mst_status & MST_PROBE);
+       KUNIT_EXPECT_EQ(test, aconnector->branch_ieee_oui, 0U);
+       KUNIT_EXPECT_EQ(test, connector->connector_type, 
DRM_MODE_CONNECTOR_DisplayPort);
+       KUNIT_EXPECT_EQ(test, connector->possible_encoders, 0x3U);
+       KUNIT_EXPECT_NOT_NULL(test, connector->state);
+       KUNIT_EXPECT_NOT_NULL(test, connector->path_blob_ptr);
+       KUNIT_EXPECT_EQ(test, kref_read(&ctx.port->malloc_kref), 2U);
+       KUNIT_EXPECT_TRUE(test,
+                         dm_mst_test_has_prop(connector,
+                                              
ctx.adev->ddev.mode_config.path_property));
+       KUNIT_EXPECT_TRUE(test,
+                         dm_mst_test_has_prop(connector,
+                                              
ctx.adev->ddev.mode_config.tile_property));
+
+       dm_dp_mst_connector_destroy(connector);
+}
+
+/**
+ * dm_mst_test_add_mst_connector_inherits_props - Test optional property 
sharing
+ * @test: KUnit test context
+ *
+ * Max bpc, VRR capable and colorspace properties are only attached to the new
+ * connector when the root connector already owns them, so the downstream
+ * connector shares the root's property objects.
+ */
+static void dm_mst_test_add_mst_connector_inherits_props(struct kunit *test)
+{
+       struct dm_mst_test_add_ctx ctx;
+       struct drm_connector *connector;
+       struct drm_device *drm;
+
+       dm_mst_test_init_add_ctx(test, &ctx);
+       drm = &ctx.adev->ddev;
+
+       ctx.master->base.max_bpc_property =
+               drm_property_create_range(drm, 0, "max bpc", 8, 16);
+       ctx.master->base.vrr_capable_property =
+               drm_property_create_bool(drm, DRM_MODE_PROP_IMMUTABLE, 
"vrr_capable");
+       KUNIT_ASSERT_NOT_NULL(test, ctx.master->base.max_bpc_property);
+       KUNIT_ASSERT_NOT_NULL(test, ctx.master->base.vrr_capable_property);
+       KUNIT_ASSERT_EQ(test, 
drm_mode_create_dp_colorspace_property(&ctx.master->base, 0), 0);
+
+       connector = dm_dp_add_mst_connector(&ctx.master->mst_mgr, ctx.port, 
"1-4");
+       KUNIT_ASSERT_NOT_NULL(test, connector);
+
+       KUNIT_EXPECT_PTR_EQ(test, connector->max_bpc_property, 
ctx.master->base.max_bpc_property);
+       KUNIT_EXPECT_PTR_EQ(test, connector->vrr_capable_property,
+                           ctx.master->base.vrr_capable_property);
+       KUNIT_EXPECT_PTR_EQ(test, connector->colorspace_property,
+                           ctx.master->base.colorspace_property);
+       KUNIT_EXPECT_TRUE(test, dm_mst_test_has_prop(connector, 
connector->max_bpc_property));
+       KUNIT_EXPECT_TRUE(test, dm_mst_test_has_prop(connector, 
connector->colorspace_property));
+
+       dm_dp_mst_connector_destroy(connector);
+}
+
+/**
+ * dm_mst_test_add_mst_connector_init_fails - Test the connector init failure 
path
+ * @test: KUnit test context
+ *
+ * Exhausting the device connector index space makes 
drm_connector_dynamic_init()
+ * fail, so the callback must free the connector it allocated, leave the port
+ * malloc reference untouched and report no connector to the MST helpers.
+ */
+static void dm_mst_test_add_mst_connector_init_fails(struct kunit *test)
+{
+       struct dm_mst_test_add_ctx ctx;
+       struct drm_device *drm;
+       int id;
+
+       dm_mst_test_init_add_ctx(test, &ctx);
+       drm = &ctx.adev->ddev;
+
+       /* drm_connector_init_only() takes an index out of the same [0, 31] 
space. */
+       do {
+               id = ida_alloc_max(&drm->mode_config.connector_ida, 31, 
GFP_KERNEL);
+       } while (id >= 0);
+       KUNIT_ASSERT_EQ(test, id, -ENOSPC);
+
+       KUNIT_EXPECT_NULL(test, dm_dp_add_mst_connector(&ctx.master->mst_mgr, 
ctx.port, "1-5"));
+       KUNIT_EXPECT_EQ(test, kref_read(&ctx.port->malloc_kref), 1U);
+}
+
 /**
  * dm_mst_test_atomic_check_no_old_crtc - Test atomic check no-op path
  * @test: KUnit test context
@@ -2134,6 +2332,10 @@ static struct kunit_case dm_mst_types_test_cases[] = {
        KUNIT_CASE(dm_mst_test_atomic_best_encoder),
        /* dm_dp_create_fake_mst_encoders tests */
        KUNIT_CASE(dm_mst_test_create_fake_mst_encoders),
+       /* dm_dp_add_mst_connector tests */
+       KUNIT_CASE(dm_mst_test_add_mst_connector_creates),
+       KUNIT_CASE(dm_mst_test_add_mst_connector_inherits_props),
+       KUNIT_CASE(dm_mst_test_add_mst_connector_init_fails),
        /* dm_dp_mst_atomic_check tests */
        KUNIT_CASE(dm_mst_test_atomic_check_no_old_crtc),
        /* dm_dp_mst_detect tests */
-- 
2.43.0

Reply via email to