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 693fd782283b..bce605fd6cdf 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 @@ -168,7 +168,7 @@ STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, } EXPORT_IF_KUNIT(dm_dp_aux_transfer); -static void +STATIC_IFN_KUNIT void dm_dp_mst_connector_destroy(struct drm_connector *connector) { struct amdgpu_dm_connector *aconnector = @@ -186,6 +186,7 @@ dm_dp_mst_connector_destroy(struct drm_connector *connector) drm_dp_mst_put_port_malloc(aconnector->mst_output_port); kfree(aconnector); } +EXPORT_IF_KUNIT(dm_dp_mst_connector_destroy); STATIC_IFN_KUNIT int amdgpu_dm_mst_connector_late_register(struct drm_connector *connector) 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 63130226e67e..50dca0ef98fe 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 @@ -121,6 +121,7 @@ int dm_dp_mst_detect(struct drm_connector *connector, int dm_dp_mst_get_modes(struct drm_connector *connector); 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); #endif #endif 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 6d920bc783d5..064af0022fb6 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 @@ -1892,6 +1892,98 @@ static void dm_mst_test_connector_early_unregister_releases_sink(struct kunit *t dm_mst_test_fini_child(&child); } +/* Tests for dm_dp_mst_connector_destroy */ + +/* + * dm_dp_mst_connector_destroy() frees both the connector and the MST port, so + * they are allocated outside the KUnit managed allocator. The parent branch + * device takes an extra malloc reference so it survives the port teardown. + */ +static struct amdgpu_dm_connector * +dm_mst_test_alloc_destroyable_connector(struct kunit *test, struct dm_mst_test_child *child) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_port *port; + int ret; + + aconnector = kzalloc_obj(*aconnector); + port = kzalloc_obj(*port); + mstb = kunit_kzalloc(test, sizeof(*mstb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, port); + KUNIT_ASSERT_NOT_NULL(test, mstb); + + mstb->mgr = &child->root->mst_mgr; + kref_init(&mstb->malloc_kref); + kref_get(&mstb->malloc_kref); + + port->mgr = &child->root->mst_mgr; + port->parent = mstb; + kref_init(&port->malloc_kref); + + aconnector->dc_link = child->link; + aconnector->mst_root = child->root; + aconnector->mst_output_port = port; + + ret = drm_connector_init(&child->adev->ddev, &aconnector->base, + &dm_mst_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + KUNIT_ASSERT_EQ(test, ret, 0); + + return aconnector; +} + +/** + * dm_mst_test_connector_destroy_no_sink - Test destroy without a remote sink + * @test: KUnit test context + * + * dm_dp_mst_connector_destroy() must clean up the DRM connector and drop the MST + * port reference even when no remote sink was ever attached. + */ +static void dm_mst_test_connector_destroy_no_sink(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dm_mst_test_child child; + + dm_mst_test_init_child(test, &child); + aconnector = dm_mst_test_alloc_destroyable_connector(test, &child); + + dm_dp_mst_connector_destroy(&aconnector->base); + + KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 0U); + + dm_mst_test_fini_child(&child); +} + +/** + * dm_mst_test_connector_destroy_releases_sink - Test destroy releases the sink + * @test: KUnit test context + * + * dm_dp_mst_connector_destroy() must remove the remote sink from the DC link + * before tearing down the connector. + */ +static void dm_mst_test_connector_destroy_releases_sink(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dm_mst_test_child child; + struct dc_sink *sink; + + dm_mst_test_init_child(test, &child); + aconnector = dm_mst_test_alloc_destroyable_connector(test, &child); + + sink = dm_mst_test_alloc_sink(test); + aconnector->dc_sink = sink; + child.link->sink_count = 1; + + dm_dp_mst_connector_destroy(&aconnector->base); + + KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 1U); + KUNIT_EXPECT_PTR_EQ(test, dm_mst_test_removed_sink, sink); + + dm_mst_test_fini_child(&child); +} + /* * Sideband connector with a live topology manager and the DOWN_REP ready bit * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path. @@ -2057,6 +2149,9 @@ static struct kunit_case dm_mst_types_test_cases[] = { /* amdgpu_dm_mst_connector_early_unregister tests */ KUNIT_CASE(dm_mst_test_connector_early_unregister_no_sink), KUNIT_CASE(dm_mst_test_connector_early_unregister_releases_sink), + /* dm_dp_mst_connector_destroy tests */ + KUNIT_CASE(dm_mst_test_connector_destroy_no_sink), + KUNIT_CASE(dm_mst_test_connector_destroy_releases_sink), /* CONFIG_DRM_AMD_DC_FP disabled public paths */ #if !defined(CONFIG_DRM_AMD_DC_FP) KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs),