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
