summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Hung <alex.hung@amd.com>2026-06-17 18:11:42 -0600
committerAlex Deucher <alexander.deucher@amd.com>2026-07-01 11:37:19 -0400
commit04bed7922fa92ad037ff8c5dbafd5cbc7e4f8db0 (patch)
treecaa9b874ca3fb0bdbb07630c911475d4e1e2cf10
parentb292f97d300f373e6de2acfa3a9fa8bd82e84c46 (diff)
downloadlinux-stable-04bed7922fa92ad037ff8c5dbafd5cbc7e4f8db0.tar.gz
linux-stable-04bed7922fa92ad037ff8c5dbafd5cbc7e4f8db0.zip
drm/amd/display: Add KUnit tests for mst_types
Add KUnit coverage for the following MST functions: - dm_dp_aux_transfer(): native read/write, partial write, error result remapping, and HPD disconnect quirk via fake DC link service - dm_dp_aux_transfer_result(): error code translation - dm_dp_aux_fill_payload_flags(): request flag decoding - dm_mst_msg_ready_mask(): ESI mask selection - dm_mst_select_esi_dpcd(): DPCD address/length selection - dm_mst_atomic_best_encoder(): encoder selection by CRTC ID - dm_dp_mst_detect(): unregistered connector early return - dm_dp_mst_atomic_check(): no-old-CRTC early return - dm_dp_create_fake_mst_encoders(): encoder init and CRTC mask - dm_handle_mst_sideband_msg_ready_event(): idle no-ready-bits - retrieve_branch_specific_data(): branch OUI parsing - retrieve_downstream_port_device(): downstream port present - needs_dsc_aux_workaround(): DSC workaround matching - dm_mst_get_pbn_divider(): null link guard - amdgpu_dm_mst_reset_mst_connector_setting(): field reset - dm_dp_mst_is_port_support_mode(): FP-off fallback Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com> Signed-off-by: Alex Hung <alex.hung@amd.com> Signed-off-by: George Zhang <george.zhang@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c19
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h9
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c572
3 files changed, 593 insertions, 7 deletions
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 b6bfe56eeb68..0546efea5de1 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
@@ -99,8 +99,8 @@ EXPORT_IF_KUNIT(dm_dp_aux_fill_payload_flags);
/*
* This function handles both native AUX and I2C-Over-AUX transactions.
*/
-static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux,
- struct drm_dp_aux_msg *msg)
+STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
{
ssize_t result = 0;
struct aux_payload payload;
@@ -167,6 +167,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux,
return result;
}
+EXPORT_IF_KUNIT(dm_dp_aux_transfer);
static void
dm_dp_mst_connector_destroy(struct drm_connector *connector)
@@ -518,7 +519,7 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector)
return ret;
}
-static struct drm_encoder *
+STATIC_IFN_KUNIT struct drm_encoder *
dm_mst_atomic_best_encoder(struct drm_connector *connector,
struct drm_atomic_commit *state)
{
@@ -529,8 +530,9 @@ dm_mst_atomic_best_encoder(struct drm_connector *connector,
return &adev->dm.mst_encoders[acrtc->crtc_id].base;
}
+EXPORT_IF_KUNIT(dm_mst_atomic_best_encoder);
-static int
+STATIC_IFN_KUNIT int
dm_dp_mst_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx, bool force)
{
@@ -600,9 +602,10 @@ dm_dp_mst_detect(struct drm_connector *connector,
return connection_status;
}
+EXPORT_IF_KUNIT(dm_dp_mst_detect);
-static int dm_dp_mst_atomic_check(struct drm_connector *connector,
- struct drm_atomic_commit *state)
+STATIC_IFN_KUNIT int dm_dp_mst_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_commit *state)
{
struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
struct drm_dp_mst_topology_mgr *mst_mgr = &aconnector->mst_root->mst_mgr;
@@ -610,6 +613,7 @@ static int dm_dp_mst_atomic_check(struct drm_connector *connector,
return drm_dp_atomic_release_time_slots(state, mst_mgr, mst_port);
}
+EXPORT_IF_KUNIT(dm_dp_mst_atomic_check);
static const struct drm_connector_helper_funcs dm_dp_mst_connector_helper_funcs = {
.get_modes = dm_dp_mst_get_modes,
@@ -650,6 +654,7 @@ dm_dp_create_fake_mst_encoders(struct amdgpu_device *adev)
drm_encoder_helper_add(encoder, &amdgpu_dm_encoder_helper_funcs);
}
}
+EXPORT_IF_KUNIT(dm_dp_create_fake_mst_encoders);
static struct drm_connector *
dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr,
@@ -855,6 +860,7 @@ void dm_handle_mst_sideband_msg_ready_event(
if (process_count == max_process_count)
DRM_DEBUG_DRIVER("Loop exceeded max iterations\n");
}
+EXPORT_IF_KUNIT(dm_handle_mst_sideband_msg_ready_event);
static void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr)
{
@@ -2108,3 +2114,4 @@ enum dc_status dm_dp_mst_is_port_support_mode(
#endif
return DC_OK;
}
+EXPORT_IF_KUNIT(dm_dp_mst_is_port_support_mode);
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 2aefab5264d0..fecf108a9216 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
@@ -64,7 +64,7 @@ struct aux_payload;
struct dc_state;
struct dc_stream_state;
struct dm_atomic_state;
-struct drm_atomic_state;
+struct drm_atomic_commit;
struct drm_dp_mst_topology_mgr;
uint32_t dm_mst_get_pbn_divider(struct dc_link *link);
@@ -108,8 +108,15 @@ bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector);
ssize_t dm_dp_aux_transfer_result(ssize_t result,
enum aux_return_code_type operation_result);
void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload);
+ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg);
u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type);
void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read);
+struct drm_encoder *dm_mst_atomic_best_encoder(struct drm_connector *connector,
+ struct drm_atomic_commit *state);
+int dm_dp_mst_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_commit *state);
+int dm_dp_mst_detect(struct drm_connector *connector,
+ struct drm_modeset_acquire_ctx *ctx, bool force);
#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 e3b171992be1..d40ed83d8685 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
@@ -7,6 +7,8 @@
#include <kunit/test.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_kunit_helpers.h>
#include <drm/display/drm_dp.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/display/drm_dp_mst_helper.h>
@@ -18,12 +20,67 @@
#include "amdgpu_mode.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_mst_types.h"
+#include "inc/link_service.h"
/*
* Minimal mock DPCD backing store and AUX transfer callback used to exercise
* the DPCD read paths without real hardware.
*/
static u8 dm_mst_test_dpcd[0x10];
+static u8 dm_mst_test_desc_dpcd[0x10];
+static struct aux_payload dm_mst_test_last_payload;
+static int dm_mst_test_aux_transfer_raw_result;
+static enum aux_return_code_type dm_mst_test_aux_transfer_raw_operation_result;
+
+static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc,
+ struct aux_payload *payload,
+ enum aux_return_code_type *operation_result)
+{
+ size_t i;
+
+ dm_mst_test_last_payload = *payload;
+ *operation_result = dm_mst_test_aux_transfer_raw_operation_result;
+
+ if (dm_mst_test_aux_transfer_raw_result)
+ return dm_mst_test_aux_transfer_raw_result;
+
+ if (payload->write)
+ return 0;
+
+ for (i = 0; i < payload->length; i++)
+ payload->data[i] = dm_mst_test_dpcd[(payload->address + i) & 0xf];
+
+ return payload->length;
+}
+
+static void dm_mst_test_setup_dm_aux(struct amdgpu_dm_dp_aux *dm_aux,
+ struct ddc_service *ddc,
+ struct dc_link *link,
+ struct dc *dc,
+ struct link_service *link_srv,
+ struct dc_context *ctx,
+ struct amdgpu_device *adev)
+{
+ memset(&dm_mst_test_last_payload, 0, sizeof(dm_mst_test_last_payload));
+ dm_mst_test_aux_transfer_raw_result = 0;
+ dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_SUCCESS;
+ link_srv->aux_transfer_raw = dm_mst_test_aux_transfer_raw;
+ dc->link_srv = link_srv;
+ link->dc = dc;
+ ctx->driver_context = adev;
+ ddc->link = link;
+ ddc->ctx = ctx;
+ dm_aux->ddc_service = ddc;
+ dm_aux->aux.name = "dm_mst_test_dm_aux";
+ dm_aux->aux.transfer = dm_dp_aux_transfer;
+ drm_dp_aux_init(&dm_aux->aux);
+ drm_dp_dpcd_set_probe(&dm_aux->aux, false);
+}
+
+static const struct dc_link_status *dm_mst_test_get_status(const struct dc_link *link)
+{
+ return &link->link_status;
+}
static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
@@ -45,6 +102,21 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux,
}
}
+static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ size_t i;
+
+ if ((msg->request & ~DP_AUX_I2C_MOT) != DP_AUX_NATIVE_READ)
+ return -EINVAL;
+
+ for (i = 0; i < msg->size; i++)
+ ((u8 *)msg->buffer)[i] = dm_mst_test_desc_dpcd[msg->address + i - DP_BRANCH_OUI];
+
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+}
+
/* Tests for needs_dsc_aux_workaround */
/**
@@ -286,6 +358,51 @@ static void dm_mst_test_retrieve_branch_no_parent(struct kunit *test)
}
/**
+ * dm_mst_test_retrieve_branch_reads_oui - Test branch OUI parsing
+ * @test: KUnit test context
+ *
+ * Verify that retrieve_branch_specific_data() reads the immediate upstream
+ * branch descriptor and caches its IEEE OUI value on the connector.
+ */
+static void dm_mst_test_retrieve_branch_reads_oui(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_dp_mst_topology_mgr *mgr;
+ struct drm_dp_mst_branch *branch;
+ struct drm_dp_mst_port *port;
+ struct drm_dp_aux *aux;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL);
+ branch = kunit_kzalloc(test, sizeof(*branch), GFP_KERNEL);
+ port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL);
+ aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, mgr);
+ KUNIT_ASSERT_NOT_NULL(test, branch);
+ KUNIT_ASSERT_NOT_NULL(test, port);
+ KUNIT_ASSERT_NOT_NULL(test, aux);
+
+ memset(dm_mst_test_desc_dpcd, 0, sizeof(dm_mst_test_desc_dpcd));
+ dm_mst_test_desc_dpcd[0] = 0x12;
+ dm_mst_test_desc_dpcd[1] = 0x34;
+ dm_mst_test_desc_dpcd[2] = 0x56;
+
+ aux->name = "dm_mst_test_desc_aux";
+ aux->transfer = dm_mst_test_desc_aux_transfer;
+ drm_dp_aux_init(aux);
+ drm_dp_dpcd_set_probe(aux, false);
+ mgr->aux = aux;
+ port->parent = branch;
+ port->mgr = mgr;
+ port->aux.drm_dev = NULL;
+ aconnector->mst_output_port = port;
+
+ KUNIT_EXPECT_TRUE(test, retrieve_branch_specific_data(aconnector));
+ KUNIT_EXPECT_EQ(test, aconnector->branch_ieee_oui, 0x123456U);
+}
+
+/**
* dm_mst_test_aux_result_success - AUX_RET_SUCCESS preserves the input result.
* @test: KUnit test context.
*
@@ -341,6 +458,246 @@ static void dm_mst_test_aux_result_timeout(struct kunit *test)
}
/**
+ * dm_mst_test_aux_transfer_native_read - native AUX read through DM callback.
+ * @test: KUnit test context.
+ *
+ * The DM AUX transfer callback should build a read payload, call the DC link
+ * service, and return the number of bytes provided by the fake backend.
+ */
+static void dm_mst_test_aux_transfer_native_read(struct kunit *test)
+{
+ struct amdgpu_dm_dp_aux *dm_aux;
+ struct amdgpu_device *adev;
+ struct ddc_service *ddc;
+ struct dc_link *link;
+ struct dc *dc;
+ struct link_service *link_srv;
+ struct dc_context *ctx;
+ u8 buffer[3] = { 0 };
+ ssize_t ret;
+
+ dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL);
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_aux);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+ KUNIT_ASSERT_NOT_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd));
+ dm_mst_test_dpcd[4] = 0xaa;
+ dm_mst_test_dpcd[5] = 0xbb;
+ dm_mst_test_dpcd[6] = 0xcc;
+ dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev);
+
+ ret = drm_dp_dpcd_read(&dm_aux->aux, 4, buffer, sizeof(buffer));
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer));
+ KUNIT_EXPECT_EQ(test, buffer[0], (u8)0xaa);
+ KUNIT_EXPECT_EQ(test, buffer[1], (u8)0xbb);
+ KUNIT_EXPECT_EQ(test, buffer[2], (u8)0xcc);
+ KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.write);
+ KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.i2c_over_aux);
+ KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U);
+}
+
+/**
+ * dm_mst_test_aux_transfer_native_write - native AUX write through DM callback.
+ * @test: KUnit test context.
+ *
+ * A successful write with an ACK reply should report the requested write size
+ * and pass a write payload into the fake DC link service.
+ */
+static void dm_mst_test_aux_transfer_native_write(struct kunit *test)
+{
+ struct amdgpu_dm_dp_aux *dm_aux;
+ struct amdgpu_device *adev;
+ struct ddc_service *ddc;
+ struct dc_link *link;
+ struct dc *dc;
+ struct link_service *link_srv;
+ struct dc_context *ctx;
+ u8 buffer[2] = { 0x11, 0x22 };
+ ssize_t ret;
+
+ dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL);
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_aux);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+ KUNIT_ASSERT_NOT_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev);
+
+ ret = drm_dp_dpcd_write(&dm_aux->aux, 7, buffer, sizeof(buffer));
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer));
+ KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write);
+ KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.i2c_over_aux);
+ KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 7U);
+ KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.length,
+ (u32)sizeof(buffer));
+}
+
+/**
+ * dm_mst_test_aux_transfer_partial_write - partial write reports byte count.
+ * @test: KUnit test context.
+ *
+ * A positive write result from the DC link service should be interpreted as a
+ * partial write and replaced with the first payload byte.
+ */
+static void dm_mst_test_aux_transfer_partial_write(struct kunit *test)
+{
+ struct amdgpu_dm_dp_aux *dm_aux;
+ struct amdgpu_device *adev;
+ struct ddc_service *ddc;
+ struct dc_link *link;
+ struct dc *dc;
+ struct link_service *link_srv;
+ struct dc_context *ctx;
+ u8 buffer[2] = { 1, 0xaa };
+ struct drm_dp_aux_msg msg = {
+ .address = 7,
+ .request = DP_AUX_NATIVE_WRITE,
+ .buffer = buffer,
+ .size = sizeof(buffer),
+ };
+ ssize_t ret;
+
+ dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL);
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_aux);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+ KUNIT_ASSERT_NOT_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev);
+ dm_mst_test_aux_transfer_raw_result = 1;
+
+ ret = dm_dp_aux_transfer(&dm_aux->aux, &msg);
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)buffer[0]);
+ KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write);
+ KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 7U);
+}
+
+/**
+ * dm_mst_test_aux_transfer_error_result - transfer errors are remapped.
+ * @test: KUnit test context.
+ *
+ * A negative DC link service result should be converted through
+ * dm_dp_aux_transfer_result() using the returned AUX operation result.
+ */
+static void dm_mst_test_aux_transfer_error_result(struct kunit *test)
+{
+ struct amdgpu_dm_dp_aux *dm_aux;
+ struct amdgpu_device *adev;
+ struct ddc_service *ddc;
+ struct dc_link *link;
+ struct dc *dc;
+ struct link_service *link_srv;
+ struct dc_context *ctx;
+ u8 buffer[2] = { 0 };
+ ssize_t ret;
+
+ dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL);
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_aux);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+ KUNIT_ASSERT_NOT_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev);
+ dm_mst_test_aux_transfer_raw_result = -EIO;
+ dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_ERROR_TIMEOUT;
+
+ ret = drm_dp_dpcd_read(&dm_aux->aux, 4, buffer, sizeof(buffer));
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)-ETIMEDOUT);
+ KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.write);
+ KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U);
+}
+
+/**
+ * dm_mst_test_aux_transfer_hpd_discon_quirk - HPD disconnect quirk succeeds.
+ * @test: KUnit test context.
+ *
+ * AUX_RET_ERROR_HPD_DISCON on the sideband down request address should be
+ * treated as a successful transfer when the platform quirk is enabled.
+ */
+static void dm_mst_test_aux_transfer_hpd_discon_quirk(struct kunit *test)
+{
+ struct amdgpu_dm_dp_aux *dm_aux;
+ struct amdgpu_device *adev;
+ struct ddc_service *ddc;
+ struct dc_link *link;
+ struct dc *dc;
+ struct link_service *link_srv;
+ struct dc_context *ctx;
+ u8 buffer[2] = { 2, 0 };
+ ssize_t ret;
+
+ dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL);
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_aux);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+ KUNIT_ASSERT_NOT_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev);
+ adev->dm.aux_hpd_discon_quirk = true;
+ dm_mst_test_aux_transfer_raw_result = -EIO;
+ dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_ERROR_HPD_DISCON;
+
+ ret = drm_dp_dpcd_write(&dm_aux->aux, DP_SIDEBAND_MSG_DOWN_REQ_BASE,
+ buffer, sizeof(buffer));
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer));
+ KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write);
+ KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address,
+ DP_SIDEBAND_MSG_DOWN_REQ_BASE);
+}
+
+/**
* dm_mst_test_fill_payload_flags_native_write - native write request decode.
* @test: KUnit test context.
*
@@ -463,6 +820,203 @@ static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test)
(int)(DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI));
}
+/**
+ * dm_mst_test_sideband_msg_ready_no_ready_bits - Test idle sideband event
+ * @test: KUnit test context
+ *
+ * Verify that dm_handle_mst_sideband_msg_ready_event() returns cleanly when
+ * the ESI read succeeds but no DOWN_REP/UP_REQ ready bits are set.
+ */
+static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct link_service *link_srv;
+ struct dc_link *link;
+ struct dc *dc;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+
+ mutex_init(&aconnector->handle_mst_msg_ready);
+ link_srv->get_status = dm_mst_test_get_status;
+ dc->link_srv = link_srv;
+ link->dc = dc;
+ link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14;
+ link->link_status.dpcd_caps = &link->dpcd_caps;
+ aconnector->dc_link = link;
+ aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux";
+ aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer;
+ drm_dp_aux_init(&aconnector->dm_dp_aux.aux);
+ drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false);
+ memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd));
+
+ dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr,
+ DOWN_REP_MSG_RDY_EVENT);
+
+ KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0);
+}
+
+/**
+ * dm_mst_test_atomic_best_encoder - Test MST encoder selection
+ * @test: KUnit test context
+ *
+ * Verify that dm_mst_atomic_best_encoder() selects the MST encoder indexed by
+ * the CRTC ID in the connector's new atomic state. This uses structural DRM
+ * mocks only; registering connector/CRTC objects is unnecessary for this helper.
+ */
+static void dm_mst_test_atomic_best_encoder(struct kunit *test)
+{
+ struct drm_connector_state connector_state = { 0 };
+ struct drm_atomic_commit state = { 0 };
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_device *adev;
+ struct amdgpu_crtc *acrtc;
+ unsigned int connector_index = 3;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, acrtc);
+
+ aconnector->base.dev = &adev->ddev;
+ aconnector->base.index = connector_index;
+ acrtc->crtc_id = 2;
+ connector_state.connector = &aconnector->base;
+ connector_state.crtc = &acrtc->base;
+ state.num_connector = connector_index + 1;
+ state.connectors = kunit_kzalloc(test,
+ sizeof(*state.connectors) * state.num_connector,
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state.connectors);
+ state.connectors[connector_index].ptr = &aconnector->base;
+ state.connectors[connector_index].new_state = &connector_state;
+
+ KUNIT_EXPECT_PTR_EQ(test, dm_mst_atomic_best_encoder(&aconnector->base, &state),
+ &adev->dm.mst_encoders[2].base);
+}
+
+/**
+ * dm_mst_test_create_fake_mst_encoders - Test fake MST encoder setup
+ * @test: KUnit test context
+ *
+ * Verify that dm_dp_create_fake_mst_encoders() initializes the requested MST
+ * encoders as DPMST encoders with the CRTC mask derived from the device state.
+ */
+static void dm_mst_test_create_fake_mst_encoders(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_device *drm;
+ struct device *dev;
+ int i;
+
+ 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 | DRIVER_ATOMIC);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+ adev = drm_to_adev(drm);
+ adev->dm.display_indexes_num = 3;
+ adev->mode_info.num_crtc = 3;
+
+ dm_dp_create_fake_mst_encoders(adev);
+
+ for (i = 0; i < adev->dm.display_indexes_num; i++) {
+ struct drm_encoder *encoder = &adev->dm.mst_encoders[i].base;
+
+ KUNIT_EXPECT_PTR_EQ(test, encoder->dev, drm);
+ KUNIT_EXPECT_EQ(test, encoder->encoder_type, DRM_MODE_ENCODER_DPMST);
+ KUNIT_EXPECT_EQ(test, encoder->possible_crtcs, 0x7U);
+ KUNIT_EXPECT_TRUE(test, encoder->helper_private != NULL);
+ }
+}
+
+/**
+ * dm_mst_test_atomic_check_no_old_crtc - Test atomic check no-op path
+ * @test: KUnit test context
+ *
+ * Verify that dm_dp_mst_atomic_check() returns success when the MST port's old
+ * connector state has no CRTC, before MST topology state is required.
+ */
+static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test)
+{
+ struct drm_connector_state old_conn_state = { 0 };
+ struct drm_connector_state new_conn_state = { 0 };
+ struct drm_atomic_commit state = { 0 };
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_dm_connector *root;
+ struct drm_dp_mst_port *port;
+ unsigned int connector_index = 2;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ root = kunit_kzalloc(test, sizeof(*root), GFP_KERNEL);
+ port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, root);
+ KUNIT_ASSERT_NOT_NULL(test, port);
+
+ aconnector->base.index = connector_index;
+ aconnector->mst_root = root;
+ aconnector->mst_output_port = port;
+ port->connector = &aconnector->base;
+ old_conn_state.connector = &aconnector->base;
+ new_conn_state.connector = &aconnector->base;
+ state.num_connector = connector_index + 1;
+ state.connectors = kunit_kzalloc(test,
+ sizeof(*state.connectors) * state.num_connector,
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state.connectors);
+ state.connectors[connector_index].ptr = &aconnector->base;
+ state.connectors[connector_index].old_state = &old_conn_state;
+ state.connectors[connector_index].new_state = &new_conn_state;
+
+ KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, &state), 0);
+}
+
+/**
+ * dm_mst_test_detect_unregistered - Test detect skips unregistered connector
+ * @test: KUnit test context
+ *
+ * Verify that dm_dp_mst_detect() returns disconnected for an unregistered
+ * connector before calling into the MST topology helper.
+ */
+static void dm_mst_test_detect_unregistered(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.registration_state = DRM_CONNECTOR_UNREGISTERED;
+
+ KUNIT_EXPECT_EQ(test,
+ dm_dp_mst_detect(&aconnector->base, NULL, false),
+ (int)connector_status_disconnected);
+}
+
+/**
+ * dm_mst_test_fp_guarded_public_stubs - Test FP-off public fallbacks
+ * @test: KUnit test context
+ *
+ * When CONFIG_DRM_AMD_DC_FP is disabled, the public DSC validation helper
+ * has no FP body and must return DC_OK without touching its arguments.
+ */
+static void dm_mst_test_fp_guarded_public_stubs(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_dp_mst_is_port_support_mode(NULL, NULL),
+ (enum dc_status)DC_OK);
+}
+
static struct kunit_case dm_mst_types_test_cases[] = {
/* needs_dsc_aux_workaround tests */
KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match),
@@ -480,11 +1034,17 @@ static struct kunit_case dm_mst_types_test_cases[] = {
KUNIT_CASE(dm_mst_test_retrieve_downstream_present),
/* retrieve_branch_specific_data tests */
KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent),
+ KUNIT_CASE(dm_mst_test_retrieve_branch_reads_oui),
/* dm_dp_aux_transfer_result tests */
KUNIT_CASE(dm_mst_test_aux_result_success),
KUNIT_CASE(dm_mst_test_aux_result_eio),
KUNIT_CASE(dm_mst_test_aux_result_ebusy),
KUNIT_CASE(dm_mst_test_aux_result_timeout),
+ KUNIT_CASE(dm_mst_test_aux_transfer_native_read),
+ KUNIT_CASE(dm_mst_test_aux_transfer_native_write),
+ KUNIT_CASE(dm_mst_test_aux_transfer_partial_write),
+ KUNIT_CASE(dm_mst_test_aux_transfer_error_result),
+ KUNIT_CASE(dm_mst_test_aux_transfer_hpd_discon_quirk),
/* dm_dp_aux_fill_payload_flags tests */
KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write),
KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read),
@@ -495,6 +1055,18 @@ static struct kunit_case dm_mst_types_test_cases[] = {
/* dm_mst_select_esi_dpcd tests */
KUNIT_CASE(dm_mst_test_select_esi_dpcd_legacy),
KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi),
+ /* dm_handle_mst_sideband_msg_ready_event tests */
+ KUNIT_CASE(dm_mst_test_sideband_msg_ready_no_ready_bits),
+ /* dm_mst_atomic_best_encoder tests */
+ KUNIT_CASE(dm_mst_test_atomic_best_encoder),
+ /* dm_dp_create_fake_mst_encoders tests */
+ KUNIT_CASE(dm_mst_test_create_fake_mst_encoders),
+ /* dm_dp_mst_atomic_check tests */
+ KUNIT_CASE(dm_mst_test_atomic_check_no_old_crtc),
+ /* dm_dp_mst_detect tests */
+ KUNIT_CASE(dm_mst_test_detect_unregistered),
+ /* CONFIG_DRM_AMD_DC_FP disabled public paths */
+ KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs),
{}
};