drm/amd/display: Add destroy tests for connector

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 <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Wayne Lin <wayne.lin@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
Bhawanpreet Lakha 2026-06-24 15:58:07 -04:00 committed by Alex Deucher
parent 663c4a8216
commit e1b6b01c92
4 changed files with 153 additions and 1 deletions

View File

@ -1814,7 +1814,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);
@ -1855,6 +1855,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)
{

View File

@ -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,

View File

@ -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),
{}
};

View File

@ -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)
{