[PATCH 36/82] drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink

<[email protected]>
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <[email protected]>
From: Bhawanpreet Lakha <[email protected]>

Add KUnit tests for amdgpu_dm_create_validate_stream_for_sink()
covering the writeback connector path, the colour-depth downgrade when
no valid bpc is found, the HDMI YCbCr and forced YCbCr420/422/444
output paths, and the timing/context prune-and-retry loops.

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          | 416 ++++++++++++++++++
 1 file changed, 416 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 54d6d2acdd7dc..b9b32194a657f 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
@@ -5854,6 +5854,414 @@ static void dm_test_create_validate_stream_null_dm_state(struct kunit *test)
 								    NULL, NULL, NULL));
 }
 
+/* Tests for amdgpu_dm_create_validate_stream_for_sink() */
+
+/*
+ * Build a connector embedded in an amdgpu_device (so drm_to_adev() resolves)
+ * carrying a dc_link but a deliberately low atomic-requested bpc. Every
+ * candidate colour depth then exceeds that cap, so bpc_mask ends up empty and
+ * the enumeration returns NULL before create_stream_for_sink() and
+ * dc_validate_stream() (and thus the unpopulated dc handle) are ever reached.
+ * This lets the encoding/bpc mask-building branches be exercised on their own.
+ */
+struct dm_test_cvs_ctx {
+	struct amdgpu_device *adev;
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+	struct dm_connector_state *dm_state;
+	struct drm_display_mode *mode;
+};
+
+static struct dm_test_cvs_ctx *
+dm_test_cvs_ctx_alloc(struct kunit *test, int connector_type)
+{
+	struct dm_test_cvs_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;
+
+	ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state);
+	ctx->dm_state->base.max_requested_bpc = 4;
+
+	ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->mode);
+	ctx->mode->hdisplay = 1920;
+	ctx->mode->vdisplay = 1080;
+
+	return ctx;
+}
+
+/**
+ * dm_test_create_validate_stream_writeback - Test the writeback stream path
+ * @test: The KUnit test context
+ *
+ * A writeback connector has no sink EDID to enumerate, so the helper builds and
+ * returns a single RGB stream directly instead of running the validation loop.
+ */
+static void dm_test_create_validate_stream_writeback(struct kunit *test)
+{
+	struct amdgpu_dm_wb_connector *wbcon;
+	struct dm_connector_state *dm_state;
+	struct drm_display_mode *mode;
+	struct dc_stream_state *stream;
+	struct amdgpu_device *adev;
+	struct dc_context *dc_ctx;
+	struct drm_device *drm;
+	struct dc_link *link;
+	struct device *dev;
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+	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, drm);
+	adev = drm_to_adev(drm);
+
+	wbcon = drmm_kzalloc(drm, sizeof(*wbcon), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, wbcon);
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(drm, &wbcon->base.base,
+			&dm_test_connector_funcs,
+			DRM_MODE_CONNECTOR_WRITEBACK, NULL), 0);
+
+	dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc_ctx);
+	link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	link->ctx = dc_ctx;
+	link->connector_signal = SIGNAL_TYPE_VIRTUAL;
+	wbcon->link = link;
+
+	dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dm_state);
+	dm_state->scaling = RMX_OFF;
+
+	mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mode);
+	mode->hdisplay = 1920;
+	mode->vdisplay = 1080;
+	mode->clock = 148500;
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&wbcon->base.base,
+							   mode, dm_state, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, stream);
+	dc_stream_release(stream);
+}
+
+/**
+ * dm_test_create_validate_stream_no_valid_bpc - Test the exhausted-mask path
+ * @test: The KUnit test context
+ *
+ * On a plain DisplayPort sink using the default RGB encoding but a bpc cap below
+ * every candidate depth, no (encoding, bpc) pair is attempted and the helper
+ * returns NULL without touching the dc handle.
+ */
+static void dm_test_create_validate_stream_no_valid_bpc(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_hdmi_ycbcr - Test the HDMI encoding mask
+ * @test: The KUnit test context
+ *
+ * A native HDMI sink advertising YCbCr 4:4:4 and 4:2:2 exercises HDMI endpoint
+ * detection and the YCbCr444/YCbCr422 mask branches; the low bpc cap still
+ * prunes every depth, so the helper returns NULL.
+ */
+static void dm_test_create_validate_stream_hdmi_ycbcr(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+	ctx->aconnector->base.display_info.color_formats =
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_force_ycbcr420 - Test the forced YCbCr420 mask
+ * @test: The KUnit test context
+ *
+ * force_yuv_pixel_format pins the encoding mask to YCbCr420 even on an RGB sink;
+ * the low bpc cap prunes every depth, so the helper returns NULL.
+ */
+static void dm_test_create_validate_stream_force_ycbcr420(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR420;
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_force_ycbcr422 - Test the forced YCbCr422 mask
+ * @test: The KUnit test context
+ *
+ * With the sink advertising YCbCr 4:2:2, a force_yuv override pins the encoding
+ * mask to YCbCr422; the low bpc cap prunes every depth, so the helper returns
+ * NULL.
+ */
+static void dm_test_create_validate_stream_force_ycbcr422(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR422;
+	ctx->aconnector->base.display_info.color_formats =
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_force_ycbcr444 - Test the forced YCbCr444 mask
+ * @test: The KUnit test context
+ *
+ * On a native HDMI sink advertising YCbCr 4:4:4, a force_yuv override pins the
+ * encoding mask to YCbCr444; the low bpc cap prunes every depth, so the helper
+ * returns NULL.
+ */
+static void dm_test_create_validate_stream_force_ycbcr444(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+	ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR444;
+	ctx->aconnector->base.display_info.color_formats =
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444);
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/*
+ * Drive the enumeration loop far enough to reach create_stream_for_sink() and
+ * dc_validate_stream() by providing a valid bpc plus a minimal fake dc. The
+ * connector carries a dc_link with a dc_context so a fake VIRTUAL sink and its
+ * stream can be built; adev->dm.dc is wired with just enough resource_pool /
+ * timing_generator / link_service state for dc_validate_stream() and
+ * dm_validate_stream_and_context() to run without a real pipe allocator.
+ */
+struct dm_test_cvs_dc {
+	struct amdgpu_device *adev;
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+	struct dm_connector_state *dm_state;
+	struct drm_display_mode *mode;
+	struct dc *dc;
+	struct timing_generator_funcs *tgfuncs;
+	struct link_service *link_srv;
+};
+
+static bool dm_test_cvs_validate_timing_fail(struct timing_generator *tg,
+					     const struct dc_crtc_timing *timing)
+{
+	return false;
+}
+
+static bool dm_test_cvs_validate_timing_ok(struct timing_generator *tg,
+					   const struct dc_crtc_timing *timing)
+{
+	return true;
+}
+
+static enum dc_status dm_test_cvs_validate_mode_timing_ok(
+		const struct dc_stream_state *stream,
+		struct dc_link *link,
+		const struct dc_crtc_timing *timing)
+{
+	return DC_OK;
+}
+
+static struct dm_test_cvs_dc *dm_test_cvs_dc_alloc(struct kunit *test)
+{
+	struct timing_generator_funcs *tgfuncs;
+	struct resource_funcs *rfuncs;
+	struct resource_caps *rcaps;
+	struct resource_pool *pool;
+	struct timing_generator *tg;
+	struct dc_context *dcc;
+	struct dal_logger *logger;
+	struct dm_test_cvs_dc *c;
+	struct device *dev;
+
+	c = kunit_kzalloc(test, sizeof(*c), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c);
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+	c->drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+			sizeof(*c->adev),
+			offsetof(struct amdgpu_device, ddev),
+			DRIVER_MODESET);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, c->drm);
+	c->adev = drm_to_adev(c->drm);
+
+	c->aconnector = drmm_kzalloc(c->drm, sizeof(*c->aconnector), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->aconnector);
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(c->drm, &c->aconnector->base,
+			&dm_test_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort, NULL), 0);
+	c->aconnector->base.display_info.bpc = 8;
+
+	dcc = kunit_kzalloc(test, sizeof(*dcc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dcc);
+	logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, logger);
+	logger->dev = c->drm;
+	dcc->logger = logger;
+
+	c->link = kunit_kzalloc(test, sizeof(*c->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->link);
+	c->link->ctx = dcc;
+	c->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	c->link->ep_type = DISPLAY_ENDPOINT_UNKNOWN;
+	c->aconnector->dc_link = c->link;
+
+	pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, pool);
+	rfuncs = kunit_kzalloc(test, sizeof(*rfuncs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, rfuncs);
+	rcaps = kunit_kzalloc(test, sizeof(*rcaps), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, rcaps);
+	tg = kunit_kzalloc(test, sizeof(*tg), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, tg);
+	tgfuncs = kunit_kzalloc(test, sizeof(*tgfuncs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, tgfuncs);
+	c->tgfuncs = tgfuncs;
+	c->link_srv = kunit_kzalloc(test, sizeof(*c->link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->link_srv);
+
+	tg->funcs = tgfuncs;
+	pool->funcs = rfuncs;
+	pool->res_cap = rcaps;
+	pool->timing_generators[0] = tg;
+	pool->timing_generator_count = 0;
+
+	c->dc = kunit_kzalloc(test, sizeof(*c->dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->dc);
+	c->dc->ctx = dcc;
+	c->dc->res_pool = pool;
+	c->dc->link_srv = c->link_srv;
+	c->adev->dm.dc = c->dc;
+
+	c->dm_state = kunit_kzalloc(test, sizeof(*c->dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->dm_state);
+	c->dm_state->base.max_requested_bpc = 8;
+	c->dm_state->scaling = RMX_OFF;
+
+	c->mode = kunit_kzalloc(test, sizeof(*c->mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->mode);
+	c->mode->hdisplay = 1920;
+	c->mode->vdisplay = 1080;
+	c->mode->clock = 148500;
+
+	return c;
+}
+
+/**
+ * dm_test_create_validate_stream_prune_timing - Test the dc_validate_stream prune
+ * @test: The KUnit test context
+ *
+ * A built stream fails dc_validate_stream() (timing_generator rejects the
+ * timing), so every candidate is pruned and released and the helper returns
+ * NULL once the enumeration is exhausted.
+ */
+static void dm_test_create_validate_stream_prune_timing(struct kunit *test)
+{
+	struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+	c->tgfuncs->validate_timing = dm_test_cvs_validate_timing_fail;
+
+	KUNIT_EXPECT_NULL(test,
+			  amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
+								    c->mode,
+								    c->dm_state,
+								    NULL));
+}
+
+/**
+ * dm_test_create_validate_stream_prune_context - Test the context-validation prune
+ * @test: The KUnit test context
+ *
+ * dc_validate_stream() succeeds but dm_validate_stream_and_context() fails (no
+ * pipe allocator), exercising the DC_OK sub-branches and MST check before the
+ * candidate is pruned; the exhausted enumeration returns NULL.
+ */
+static void dm_test_create_validate_stream_prune_context(struct kunit *test)
+{
+	struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+	c->tgfuncs->validate_timing = dm_test_cvs_validate_timing_ok;
+	c->link_srv->validate_mode_timing = dm_test_cvs_validate_mode_timing_ok;
+
+	KUNIT_EXPECT_NULL(test,
+			  amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
+								    c->mode,
+								    c->dm_state,
+								    NULL));
+}
+
 /**
  * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst
  * @test: The KUnit test context
@@ -6716,6 +7124,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_s3_handle_hdmi_cec_resume),
 	/* amdgpu_dm_create_validate_stream_for_sink */
 	KUNIT_CASE(dm_test_create_validate_stream_null_dm_state),
+	KUNIT_CASE(dm_test_create_validate_stream_writeback),
+	KUNIT_CASE(dm_test_create_validate_stream_no_valid_bpc),
+	KUNIT_CASE(dm_test_create_validate_stream_hdmi_ycbcr),
+	KUNIT_CASE(dm_test_create_validate_stream_force_ycbcr420),
+	KUNIT_CASE(dm_test_create_validate_stream_force_ycbcr422),
+	KUNIT_CASE(dm_test_create_validate_stream_force_ycbcr444),
+	KUNIT_CASE(dm_test_create_validate_stream_prune_timing),
+	KUNIT_CASE(dm_test_create_validate_stream_prune_context),
 	/* amdgpu_dm_update_connector_after_detect */
 	KUNIT_CASE(dm_test_update_after_detect_mst_noop),
 	KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
-- 
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.