From 0b0ff65d3ca19a87e5772c64355895787d35e6ee Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Tue, 21 Jul 2026 17:47:11 -0400 Subject: [PATCH] drm/amd/display: Refactor stream validation [Why] amdgpu_dm_create_validate_stream_for_sink() drove its RGB -> YUV422 -> YUV420 chroma fallback by recursing and toggling the shared aconnector->force_yuv420_output / force_yuv422_output fields, resetting them after each recursive call. Those fields have no locking and the function runs concurrently on the same connector from two paths: the connector probe worker (->mode_valid) and a compositor's atomic check (dm_update_crtc_state). When both run at once, one thread can clear the override just before the other tests its exit condition, so the exit is missed and validation loops indefinitely, hanging the modeset path. [How] - Replace the recursion with an explicit loop over the chroma encodings wrapping the existing bpc walk. - Carry the encoding/bpc selection on the stack, passed by value into create_stream_for_sink() / fill_stream_properties_from_drm_display_mode(), instead of mutating shared connector state. - Derive the supported encodings and bit depths into bitmaps and drive validation from them, gating each candidate on the sink's advertised capability so unsupported encodings are never retried. - Move encoding selection entirely to the caller and pass the chosen dc_pixel_encoding into fill_stream_properties_from_drm_display_mode(). v2: sqaush in KUnit test fixes, merge with drm-misc changes (Alex) Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Jerry Zuo Signed-off-by: Ivan Lipski Signed-off-by: Roman Li Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 317 +++++++++++------- .../display/amdgpu_dm/amdgpu_dm_connector.h | 8 +- .../tests/amdgpu_dm_connector_test.c | 155 ++++++++- 3 files changed, 347 insertions(+), 133 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 2b0ffa20d98e..ba41ae10c4cd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -855,16 +855,15 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode( const struct drm_connector *connector, const struct drm_connector_state *connector_state, const struct dc_stream_state *old_stream, - int requested_bpc) + int requested_bpc, + enum dc_pixel_encoding requested_encoding, + bool is_hdmi_ep) { - bool is_dp_or_hdmi = dc_is_hdmi_signal(stream->signal) || dc_is_dp_signal(stream->signal); struct dc_crtc_timing *timing_out = &stream->timing; const struct drm_display_info *info = &connector->display_info; struct amdgpu_dm_connector *aconnector = NULL; struct hdmi_vendor_infoframe hv_frame; struct hdmi_avi_infoframe avi_frame; - bool want_420; - bool want_422; ssize_t err; if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) @@ -878,40 +877,13 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode( timing_out->v_border_top = 0; timing_out->v_border_bottom = 0; - want_420 = (aconnector && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) || - (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420); - want_422 = (aconnector && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422) || - (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR422); - - if (drm_mode_is_420_only(info, mode_in) && - (want_420 || connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO)) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - } else if (drm_mode_is_420_also(info, mode_in) && want_420) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - } else if ((info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) && - want_422 && is_dp_or_hdmi) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422; - } else if (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR444 && - (info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) && - is_dp_or_hdmi) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444; - } else if (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_RGB444 || - connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO) { - timing_out->pixel_encoding = PIXEL_ENCODING_RGB; - } else { - /* - * If a format was explicitly requested but the requested format - * can't be satisfied, set it to an invalid value so that an - * error bubbles up to userspace. This way, userspace knows it - * needs to make a better choice. - */ - if (connector_state->color_format != DRM_CONNECTOR_COLOR_FORMAT_AUTO) - timing_out->pixel_encoding = PIXEL_ENCODING_UNDEFINED; - else if (drm_mode_is_420_only(info, mode_in)) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else - timing_out->pixel_encoding = PIXEL_ENCODING_RGB; - } + /* + * The pixel encoding to use is decided entirely by the caller (see + * amdgpu_dm_create_validate_stream_for_sink()), which enumerates only + * the encodings the sink actually advertises. This helper must not + * second-guess that choice; it simply applies it. + */ + timing_out->pixel_encoding = requested_encoding; timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE; timing_out->display_color_depth = amdgpu_dm_convert_color_depth_from_display_info( @@ -977,14 +949,15 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode( stream->out_transfer_func.type = TF_TYPE_PREDEFINED; stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB; - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) { - if (!adjust_colour_depth_from_display_info(timing_out, info) && - drm_mode_is_420_also(info, mode_in) && - timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - adjust_colour_depth_from_display_info(timing_out, info); - } - } + + /* Clamp the HDMI colour depth to what the sink's max TMDS clock + * allows. The pixel encoding is fixed by the caller and must not be + * changed here: the caller already enumerates YCbCr420 as its own + * candidate, so silently switching to it would hide an enumeration + * bug and produce a stream the caller never asked to validate. + */ + if (is_hdmi_ep) + adjust_colour_depth_from_display_info(timing_out, info); stream->output_color_space = amdgpu_dm_get_output_color_space(timing_out, connector_state); stream->content_type = get_output_content_type(connector_state); @@ -1451,7 +1424,9 @@ create_stream_for_sink(struct drm_connector *connector, const struct drm_display_mode *drm_mode, const struct dm_connector_state *dm_state, const struct dc_stream_state *old_stream, - int requested_bpc) + int requested_bpc, + enum dc_pixel_encoding requested_encoding, + bool is_hdmi_ep) { struct drm_device *dev = connector->dev; struct amdgpu_dm_connector *aconnector = NULL; @@ -1562,11 +1537,11 @@ create_stream_for_sink(struct drm_connector *connector, if (!scale || mode_refresh != preferred_refresh) fill_stream_properties_from_drm_display_mode( stream, &mode, connector, con_state, NULL, - requested_bpc); + requested_bpc, requested_encoding, is_hdmi_ep); else fill_stream_properties_from_drm_display_mode( stream, &mode, connector, con_state, old_stream, - requested_bpc); + requested_bpc, requested_encoding, is_hdmi_ep); /* The rest isn't needed for writeback connectors */ if (!aconnector) @@ -2271,13 +2246,32 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, const struct dm_connector_state *dm_state, const struct dc_stream_state *old_stream) { + /* + * Ordered lists of the encodings and bit depths we are willing to + * validate, best quality/bandwidth first. The per-sink masks built + * below gate which of these entries are actually attempted. + */ + static const enum dc_pixel_encoding encoding_order[] = { + PIXEL_ENCODING_YCBCR444, + PIXEL_ENCODING_RGB, + PIXEL_ENCODING_YCBCR422, + PIXEL_ENCODING_YCBCR420, + }; + static const u8 bpc_order[] = { 16, 12, 10, 8, 6 }; + struct amdgpu_dm_connector *aconnector = NULL; struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct dc_stream_state *stream; + const struct drm_display_info *info = &connector->display_info; + struct dc_stream_state *stream = NULL; const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL; int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8; enum dc_status dc_result = DC_OK; - uint8_t bpc_limit = 6; + enum signal_type signal = SIGNAL_TYPE_NONE; + bool want_420, want_422, is_dp_or_hdmi; + u32 encoding_mask = 0; + u32 bpc_mask = 0; + bool is_hdmi_ep = false; + unsigned int i, j; if (!dm_state) return NULL; @@ -2285,90 +2279,179 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) aconnector = to_amdgpu_dm_connector(connector); - if (aconnector && - (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A || - aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL || - aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)) - bpc_limit = 8; - - do { - drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc); - stream = create_stream_for_sink(connector, drm_mode, - dm_state, old_stream, - requested_bpc); - if (stream == NULL) { + /* + * Writeback connectors have no sink EDID to enumerate against. They + * only ever use RGB at the requested depth, so build and validate a + * single stream directly and return it (dc_validate_stream() is not + * meaningful for the writeback path). + */ + if (!aconnector) { + stream = create_stream_for_sink(connector, drm_mode, dm_state, + old_stream, requested_bpc, + PIXEL_ENCODING_RGB, is_hdmi_ep); + if (!stream) drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n"); - break; - } + return stream; + } - dc_result = dc_validate_stream(adev->dm.dc, stream); + signal = aconnector->dc_link->connector_signal; - if (!aconnector) /* writeback connector */ - return stream; + /* + * Determine whether this is a native HDMI sink or a DP->HDMI dongle. + * Since we check for it a few times below, cache the result. + */ + is_hdmi_ep = (signal == SIGNAL_TYPE_HDMI_TYPE_A || + signal == SIGNAL_TYPE_HDMI_FRL || + aconnector->dc_link->dpcd_caps.dongle_type == + DISPLAY_DONGLE_DP_HDMI_CONVERTER); + is_dp_or_hdmi = dc_is_hdmi_signal(signal) || dc_is_dp_signal(signal); - if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) - dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream); + /* + * Build the set of pixel encodings this sink advertises, so we only + * ever validate combinations the display can actually accept. Ordered + * best-first: RGB / YCbCr444 (full bandwidth) -> YCbCr422 -> YCbCr420. + * + * - RGB is the mandatory baseline and always available. + * - YCbCr444 is only meaningful for native HDMI sinks. + * - A 420-only mode collapses the mask to YCbCr420 alone. + * - The debugfs force_yuv420_output / force_yuv422_output overrides + * pin the encoding to a single value when set. An explicit YCbCr420 + * force is honoured even on modes the sink only lists as RGB/4:4:4 + * capable (drm_mode_is_420_also() clear), as required for HDMI + * compliance testing; dc_validate_stream() still rejects anything + * the link genuinely cannot carry. The YCbCr422 force stays gated on + * the sink's advertised caps. + */ + want_420 = (aconnector && aconnector->force_yuv420_output) || + (drm_state && drm_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420); + want_422 = (aconnector && aconnector->force_yuv422_output) || + (drm_state && drm_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR422); - if (dc_result == DC_OK) - dc_result = dm_validate_stream_and_context(adev->dm.dc, stream); + if (drm_mode_is_420_only(info, drm_mode) && + (want_420 || (drm_state && drm_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO))) { + encoding_mask = BIT(PIXEL_ENCODING_YCBCR420); + } else if (drm_mode_is_420_also(info, drm_mode) && want_420) { + encoding_mask = BIT(PIXEL_ENCODING_YCBCR420); + } else if ((info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) && + want_422 && is_dp_or_hdmi) { + encoding_mask = BIT(PIXEL_ENCODING_YCBCR422); + } else if ((drm_state && drm_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR444) && + (info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) && + is_dp_or_hdmi) { + encoding_mask = BIT(PIXEL_ENCODING_YCBCR444); + } else if (drm_state && drm_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_RGB444) { + encoding_mask = BIT(PIXEL_ENCODING_RGB); + } else { + encoding_mask = BIT(PIXEL_ENCODING_RGB); - if (dc_result == DC_OK) - dc_result = dm_validate_stream_color_format(drm_state, stream); + if ((info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) && + is_hdmi_ep) + encoding_mask |= BIT(PIXEL_ENCODING_YCBCR444); - if (dc_result != DC_OK) { - drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n", + if (info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + encoding_mask |= BIT(PIXEL_ENCODING_YCBCR422); + + if (drm_mode_is_420_also(info, drm_mode)) + encoding_mask |= BIT(PIXEL_ENCODING_YCBCR420); + } + + /* + * Build the set of bit depths to try, high-to-low. Start from the + * atomic-requested cap and clear anything the sink cannot do: + * + * - Never exceed the requested bpc. + * - HDMI/FRL and DP->HDMI dongles have no defined 6 bpc mode. + * - Any depth above the EDID-reported bpc is dropped. + * + * amdgpu_dm_convert_color_depth_from_display_info() applies the final + * per-encoding cap (e.g. YCbCr420 deep-colour limits) when the stream + * is built, so this mask only needs the coarse sink limits. + */ + for (j = 0; j < ARRAY_SIZE(bpc_order); j++) { + u8 bpc = bpc_order[j]; + + if (requested_bpc > 0 && bpc > requested_bpc) + continue; + + if (bpc == 6 && is_hdmi_ep) + continue; + + if (info->bpc && bpc > info->bpc) + continue; + + bpc_mask |= BIT(bpc); + } + + /* + * Enumerate the supported (encoding, bpc) combinations in priority + * order and return the first that validates. Because the masks only + * contain sink-supported entries, this is generic across connector + * types and needs no encoding-specific fallback afterwards. + * + * The selection lives entirely on the stack and is passed by value to + * create_stream_for_sink(); no shared connector state is mutated. This + * matters because this helper runs concurrently from the connector + * probe worker (->mode_valid) and from a compositor's atomic check on + * the same connector. + */ + for (i = 0; i < ARRAY_SIZE(encoding_order); i++) { + enum dc_pixel_encoding enc = encoding_order[i]; + + if (!(encoding_mask & BIT(enc))) + continue; + + for (j = 0; j < ARRAY_SIZE(bpc_order); j++) { + u8 bpc = bpc_order[j]; + + if (!(bpc_mask & BIT(bpc))) + continue; + + drm_dbg_kms(connector->dev, + "Trying %s with %d bpc (encoding_mask=0x%x bpc_mask=0x%x requested_bpc=%d drm max_bpc)\n", + dc_pixel_encoding_to_str(enc), bpc, + encoding_mask, bpc_mask, requested_bpc); + + stream = create_stream_for_sink(connector, drm_mode, + dm_state, old_stream, + bpc, enc, is_hdmi_ep); + if (stream == NULL) { + drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n"); + return NULL; + } + + dc_result = dc_validate_stream(adev->dm.dc, stream); + + if (dc_result == DC_OK && + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream); + + if (dc_result == DC_OK) + dc_result = dm_validate_stream_and_context(adev->dm.dc, stream); + + if (dc_result == DC_OK) + dc_result = dm_validate_stream_color_format(drm_state, stream); + + if (dc_result == DC_OK) + return stream; + + drm_dbg_kms(connector->dev, "Pruned mode %d x %d (refresh rate %d) %s %s -- %s\n", drm_mode->hdisplay, drm_mode->vdisplay, - drm_mode->clock, + drm_mode_vrefresh(drm_mode), dc_pixel_encoding_to_str(stream->timing.pixel_encoding), dc_color_depth_to_str(stream->timing.display_color_depth), dc_status_to_str(dc_result)); dc_stream_release(stream); stream = NULL; - requested_bpc -= 2; /* lower bpc to retry validation */ } - - } while (stream == NULL && requested_bpc >= bpc_limit); - - switch (dc_result) { - /* - * If we failed to validate DP bandwidth stream with the requested RGB color depth, - * we try to fallback and configure in order: - * YUV422 (8bpc, 6bpc) - * YUV420 (8bpc, 6bpc) - */ - case DC_FAIL_ENC_VALIDATE: - case DC_EXCEED_DONGLE_CAP: - case DC_NO_DP_LINK_BANDWIDTH: - /* recursively entered twice and already tried both YUV422 and YUV420 */ - if (aconnector->force_yuv422_output && aconnector->force_yuv420_output) - break; - /* first failure; try YUV422 */ - if (!aconnector->force_yuv422_output) { - drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n", - __func__, __LINE__, dc_result); - aconnector->force_yuv422_output = true; - /* recursively entered and YUV422 failed, try YUV420 */ - } else if (!aconnector->force_yuv420_output) { - drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n", - __func__, __LINE__, dc_result); - aconnector->force_yuv420_output = true; - } - stream = amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode, - dm_state, old_stream); - aconnector->force_yuv422_output = false; - aconnector->force_yuv420_output = false; - break; - case DC_OK: - break; - default: - drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n", - __func__, __LINE__, dc_result); - break; } - return stream; + /* + * Every sink-supported combination was exhausted without validating; + * the mode is genuinely unsupported on this link. + */ + return NULL; } EXPORT_IF_KUNIT(amdgpu_dm_create_validate_stream_for_sink); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 13c54229d72c..4e9eb3ff2c90 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -189,13 +189,17 @@ void fill_stream_properties_from_drm_display_mode( const struct drm_connector *connector, const struct drm_connector_state *connector_state, const struct dc_stream_state *old_stream, - int requested_bpc); + int requested_bpc, + enum dc_pixel_encoding requested_encoding, + bool is_hdmi_ep); struct dc_stream_state * create_stream_for_sink(struct drm_connector *connector, const struct drm_display_mode *drm_mode, const struct dm_connector_state *dm_state, const struct dc_stream_state *old_stream, - int requested_bpc); + int requested_bpc, + enum dc_pixel_encoding requested_encoding, + bool is_hdmi_ep); enum drm_connector_status amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force); enum drm_connector_status 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 1a8f02c78c4d..1658d4d5997a 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 @@ -3416,7 +3416,8 @@ static void dm_test_fill_stream_borders_zeroed(struct kunit *test) timing->v_border_bottom = 8; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0); KUNIT_EXPECT_EQ(test, (int)timing->h_border_right, 0); @@ -3437,7 +3438,8 @@ static void dm_test_fill_stream_rgb_defaults(struct kunit *test) struct dc_crtc_timing *timing = &ctx->stream->timing; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB); KUNIT_EXPECT_EQ(test, (int)timing->timing_3d_format, @@ -3463,7 +3465,8 @@ static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test) ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1); KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 1); @@ -3482,7 +3485,8 @@ static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test) ctx->mode->flags = 0; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0); KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0); @@ -3511,7 +3515,8 @@ static void dm_test_fill_stream_inherits_old_stream(struct kunit *test) ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, old_stream, 8); + &ctx->aconnector->base, ctx->conn_state, old_stream, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->vic, 16); KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1); @@ -3541,7 +3546,8 @@ static void dm_test_fill_stream_timing_from_crtc(struct kunit *test) ctx->mode->crtc_clock = 148500; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920); KUNIT_EXPECT_EQ(test, (int)timing->h_total, 2200); @@ -3568,7 +3574,8 @@ static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test) ctx->aconnector->base.display_info.bpc = 12; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 10); + &ctx->aconnector->base, ctx->conn_state, NULL, 10, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth, (int)COLOR_DEPTH_101010); @@ -3585,7 +3592,8 @@ static void dm_test_fill_stream_content_type(struct kunit *test) ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type, (int)DISPLAY_CONTENT_TYPE_GRAPHICS); @@ -3603,12 +3611,121 @@ static void dm_test_fill_stream_aspect_ratio(struct kunit *test) ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9; fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, - &ctx->aconnector->base, ctx->conn_state, NULL, 8); + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio, (int)ASPECT_RATIO_16_9); } +/** + * dm_test_fill_stream_encoding_from_caller_ycbcr420 - Test caller-selected 420 + * @test: The KUnit test context + * + * The helper no longer derives the pixel encoding from the display info; it + * applies whatever the caller selected. Passing YCbCr420 must be honoured even + * though the DisplayPort sink advertises no YCbCr color formats. + */ +static void dm_test_fill_stream_encoding_from_caller_ycbcr420(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_YCBCR420, false); + + KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, + (int)PIXEL_ENCODING_YCBCR420); +} + +/** + * dm_test_fill_stream_encoding_from_caller_ycbcr422 - Test caller-selected 422 + * @test: The KUnit test context + * + * A caller-selected YCbCr422 encoding is applied verbatim. + */ +static void dm_test_fill_stream_encoding_from_caller_ycbcr422(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_YCBCR422, false); + + KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, + (int)PIXEL_ENCODING_YCBCR422); +} + +/** + * dm_test_fill_stream_encoding_from_caller_ycbcr444 - Test caller-selected 444 + * @test: The KUnit test context + * + * A caller-selected YCbCr444 encoding is applied verbatim. + */ +static void dm_test_fill_stream_encoding_from_caller_ycbcr444(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8, + PIXEL_ENCODING_YCBCR444, false); + + KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, + (int)PIXEL_ENCODING_YCBCR444); +} + +/** + * dm_test_fill_stream_hdmi_ep_clamps_depth - Test HDMI TMDS depth clamp applied + * @test: The KUnit test context + * + * With is_hdmi_ep set the colour depth is clamped to what the sink's max TMDS + * clock allows: a 10bpc request that exceeds the limit is reduced to 8bpc. + */ +static void dm_test_fill_stream_hdmi_ep_clamps_depth(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->aconnector->base.display_info.bpc = 10; + /* 10bpc RGB needs 185625 KHz, over the sink's 160 MHz TMDS limit. */ + ctx->aconnector->base.display_info.max_tmds_clock = 160000; + ctx->mode->crtc_clock = 148500; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 10, + PIXEL_ENCODING_RGB, true); + + KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth, + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_fill_stream_non_hdmi_ep_keeps_depth - Test no TMDS clamp off HDMI + * @test: The KUnit test context + * + * With is_hdmi_ep clear the TMDS clamp is skipped, so the same over-limit + * 10bpc request is left untouched. The clamp is HDMI-specific. + */ +static void dm_test_fill_stream_non_hdmi_ep_keeps_depth(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->aconnector->base.display_info.bpc = 10; + ctx->aconnector->base.display_info.max_tmds_clock = 160000; + ctx->mode->crtc_clock = 148500; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 10, + PIXEL_ENCODING_RGB, false); + + KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth, + (int)COLOR_DEPTH_101010); +} + /* Tests for create_stream_for_sink() */ /* @@ -3687,7 +3804,8 @@ static void dm_test_create_stream_fake_sink_success(struct kunit *test) struct dc_stream_state *stream; stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, - ctx->dm_state, NULL, 8); + ctx->dm_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_ASSERT_NOT_NULL(test, stream); dc_stream_release(stream); @@ -3703,7 +3821,8 @@ static void dm_test_create_stream_sets_dm_context(struct kunit *test) struct dc_stream_state *stream; stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, - ctx->dm_state, NULL, 8); + ctx->dm_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_ASSERT_NOT_NULL(test, stream); KUNIT_EXPECT_PTR_EQ(test, stream->dm_stream_context, ctx->aconnector); @@ -3720,7 +3839,8 @@ static void dm_test_create_stream_virtual_signal(struct kunit *test) struct dc_stream_state *stream; stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, - ctx->dm_state, NULL, 8); + ctx->dm_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_ASSERT_NOT_NULL(test, stream); KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_VIRTUAL); @@ -3739,7 +3859,8 @@ static void dm_test_create_stream_scaling_src(struct kunit *test) struct dc_stream_state *stream; stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, - ctx->dm_state, NULL, 8); + ctx->dm_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_ASSERT_NOT_NULL(test, stream); KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920); @@ -3770,7 +3891,8 @@ static void dm_test_create_stream_existing_sink(struct kunit *test) ctx->aconnector->dc_sink = sink; stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, - ctx->dm_state, NULL, 8); + ctx->dm_state, NULL, 8, + PIXEL_ENCODING_RGB, false); KUNIT_ASSERT_NOT_NULL(test, stream); KUNIT_EXPECT_PTR_EQ(test, stream->sink, sink); @@ -5456,6 +5578,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_fill_stream_color_depth_requested_bpc), KUNIT_CASE(dm_test_fill_stream_content_type), KUNIT_CASE(dm_test_fill_stream_aspect_ratio), + KUNIT_CASE(dm_test_fill_stream_encoding_from_caller_ycbcr420), + KUNIT_CASE(dm_test_fill_stream_encoding_from_caller_ycbcr422), + KUNIT_CASE(dm_test_fill_stream_encoding_from_caller_ycbcr444), + KUNIT_CASE(dm_test_fill_stream_hdmi_ep_clamps_depth), + KUNIT_CASE(dm_test_fill_stream_non_hdmi_ep_keeps_depth), /* create_stream_for_sink */ KUNIT_CASE(dm_test_create_stream_fake_sink_success), KUNIT_CASE(dm_test_create_stream_sets_dm_context),