[igt-dev] [PATCH i-g-t v2] test/amdgpu: Add DP mst test

Wayne Lin Wayne.Lin at amd.com
Tue Aug 2 10:56:34 UTC 2022


[Why & How]
Add new igt test amd_mst for DP mst feature.

MST (Multi-Stream Transport) is the feature introduced from DP 1.2.
By mst, source can transport multiple streams to different stream
sinks through the same DP Tx.

This new igt test "amd_mst" validate mst feature from two perspectives:

* Trigger hotplug via debugfs to see whether all mst end sinks
  successfully go through mst light up procedure
* Check if all mst end sinks successfully go through mst light up
  procedure after suspend-resume

This tool is developed under the setup below:
* 2xFHD monitors connected after the 3D club mst hub (model CSV-7300).
  Upstream port of 3D club hub is connected directly to ASIC DP port.

v2:
- Revise the commit message to describe the hardware setup while
  developing this tool.
- Rename is_mst_connector to mst_roles
- Remove unnecessary variable/space/brackets
- Add more descriptions for MST progress status
- Add option to specify the delay before checking MST progress status

Signed-off-by: Wayne Lin <Wayne.Lin at amd.com>
---
 lib/igt_amd.c            | 157 +++++++++++++++++++
 lib/igt_amd.h            |  47 ++++++
 tests/amdgpu/amd_mst.c   | 316 +++++++++++++++++++++++++++++++++++++++
 tests/amdgpu/meson.build |   1 +
 4 files changed, 521 insertions(+)
 create mode 100644 tests/amdgpu/amd_mst.c

diff --git a/lib/igt_amd.c b/lib/igt_amd.c
index bef9c193..7ad70dc8 100644
--- a/lib/igt_amd.c
+++ b/lib/igt_amd.c
@@ -1175,3 +1175,160 @@ bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt
 
 	return res;
 }
+
+/**
+ * igt_amd_output_has_mst: check if connector has MST debugfs entries
+ * @drm_fd: DRM file descriptor
+ * @connector_name: The connector's name, on which we're reading the status
+ */
+static bool igt_amd_output_has_mst(int drm_fd, char *connector_name)
+{
+	return igt_amd_output_has_debugfs(drm_fd, connector_name, DEBUGFS_MST_IS_MST_CONNECTOR) &
+			igt_amd_output_has_debugfs(drm_fd, connector_name, DEBUGFS_MST_PROGRESS_STATUS);
+}
+
+/**
+ * igt_amd_require_mst: Checks if connectors have mst debugfs entries
+ * @display: A pointer to an #igt_display_t structure
+ * @drm_fd: DRM file descriptor
+ *
+ * Checks if the AMDGPU driver supports the 'is_mst_connector'
+ * and 'mst_progress_status' entry for mst. Skip test if there
+ * are no relevant debugfs entries.
+ */
+void igt_amd_require_mst(igt_display_t *display, int drm_fd)
+{
+	igt_output_t *output;
+
+	for_each_connected_output(display, output)
+		if (igt_amd_output_has_mst(drm_fd, output->name))
+			return;
+
+	igt_skip("No MST relevant debugfs support.\n");
+}
+
+/**
+ * igt_amd_get_mst_role: Get the mst role of this output
+ * @drm_fd: DRM file descriptor
+ * @output: the output to be checked
+ *
+ * DRM enumerates a drm connector to each MST output port. This is used to
+ * determine the mst role of this specific output. The mst role in the
+ * mst topology shoule be 'no', 'root', 'branch' and 'end'
+ */
+enum dp_mst_role igt_amd_get_mst_role(int drm_fd, igt_output_t *output)
+{
+	int fd, ret;
+	char buf[256] = {'\0'};
+	int i = 0;
+	enum dp_mst_role role = MST_NONE;
+
+	fd = igt_debugfs_connector_dir(drm_fd, output->name, O_RDONLY);
+	if (fd < 0) {
+		igt_info("Could not open connector %s debugfs directory\n",
+			 output->name);
+		return MST_NONE;
+	}
+
+	if (output->config.connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+		return MST_NONE;
+
+	ret = igt_debugfs_simple_read(fd, DEBUGFS_MST_IS_MST_CONNECTOR, buf, sizeof(buf));
+	igt_assert_f(ret >= 0, "Reading %s for connector %s failed.\n",
+		     DEBUGFS_MST_IS_MST_CONNECTOR, output->name);
+
+	close(fd);
+
+	for (i = 0; i < sizeof(mst_roles)/sizeof(char *); i++) {
+		if (strstr(buf, mst_roles[i])) {
+			role = i;
+			break;
+		}
+	}
+
+	if (role != MST_NONE)
+		igt_info("Connector %s: is a mst %s", output->name, buf);
+	else
+		igt_info("Connector %s: is not a mst device\n", output->name);
+
+	return role;
+}
+
+/**
+ * igt_amd_get_mst_progress_status: Get the mst progress status of a mst device
+ * @drm_fd: DRM file descriptor
+ * @output: the output to be checked
+ *
+ * Get the mst progress status of this specific output. The mst progress status
+ * is indicated by:
+ * MST_PROBE - drm connector got detected by LINK_ADDRESS sideband msg.
+ * MST_REMOTE_EDID - successfully read out edid by REMOTE_I2C_READ sideband msg.
+ * MST_ALLOCATE_NEW_PAYLOAD - suuccessfully allocate time slots for the stream.
+ * MST_CLEAR_ALLOCATED_PAYLOAD - successfully clean out allocated time slots.
+ */
+uint8_t igt_amd_get_mst_progress_status(int drm_fd, igt_output_t *output)
+{
+	int fd, ret;
+	char buf[256] = {'\0'};
+	int idx = 0;
+	char *token_end;
+	uint8_t status = MST_STATUS_DEFAULT;
+
+	fd = igt_debugfs_connector_dir(drm_fd, output->name, O_RDONLY);
+	if (fd < 0) {
+		igt_info("Could not open connector %s debugfs directory\n",
+			 output->name);
+		return MST_STATUS_DEFAULT;
+	}
+
+	if (output->config.connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+		return MST_STATUS_DEFAULT;
+
+	ret = igt_debugfs_simple_read(fd, DEBUGFS_MST_PROGRESS_STATUS, buf, sizeof(buf));
+	igt_assert_f(ret >= 0, "Reading %s for connector %s failed.\n",
+		     DEBUGFS_MST_PROGRESS_STATUS, output->name);
+
+	close(fd);
+
+	/* Parse values read from file. */
+	for (char *token = strtok_r(buf, "\n", &token_end);
+	     token != NULL;
+	     token = strtok_r(NULL, "\n", &token_end), idx++)
+	{
+		if (strstr(token, "not") == NULL)
+			status |= BIT(idx);
+	}
+
+	return status;
+}
+
+/**
+ * mst_trigger_hotplug: Trigger mst hotplug
+ * @drm_fd: DRM file descriptor
+ * @output: #igt_output_t to check.
+ *
+ * Trigger soft hotplug on mst connector. Caller should make sure the
+ * passed output is mst root by igt_amd_get_mst_role(). We can't generate
+ * soft hotplug on mst end device.
+ */
+void mst_trigger_hotplug(int drm_fd, igt_output_t *output)
+{
+	int fd, hpd_fd;
+	int wr_len;
+	const char *enable_hpd = "1", *disable_hpd = "0";
+
+	fd = igt_debugfs_connector_dir(drm_fd, output->name, O_RDONLY);
+	igt_assert(fd >= 0);
+	hpd_fd = openat(fd, DEBUGFS_HPD_TRIGGER, O_WRONLY);
+	close(fd);
+	igt_assert(hpd_fd >= 0);
+
+	wr_len = write(hpd_fd, disable_hpd, strlen(disable_hpd));
+	igt_assert_eq(wr_len, strlen(disable_hpd));
+
+	sleep(3);
+
+	wr_len = write(hpd_fd, enable_hpd, strlen(enable_hpd));
+	igt_assert_eq(wr_len, strlen(enable_hpd));
+	close(hpd_fd);
+}
\ No newline at end of file
diff --git a/lib/igt_amd.h b/lib/igt_amd.h
index 428bfe6f..2d54ddc3 100644
--- a/lib/igt_amd.h
+++ b/lib/igt_amd.h
@@ -52,6 +52,12 @@
 /* amdgpu DM interface entries */
 #define DEBUGFS_DM_VISUAL_CONFIRM "amdgpu_dm_visual_confirm"
 
+/* mst related read only status*/
+#define DEBUGFS_MST_IS_MST_CONNECTOR "is_mst_connector"
+#define DEBUGFS_MST_PROGRESS_STATUS "mst_progress_status"
+
+#define BIT(x) (1ul <<(x))
+
 enum amd_dsc_clock_force {
 	DSC_AUTOMATIC = 0,
 	DSC_FORCE_ON,
@@ -131,6 +137,41 @@ enum amdgpu_debug_visual_confirm {
 	VISUAL_CONFIRM_SWIZZLE	= 9
 };
 
+/*
+ * Enumeration of the role in MST topology:
+ * MST_NONE - drm connector is not in a MST topology
+ * MST_ROOT - the output DP port locates at stream source.
+ * MST_BRANCH - the intermdediate node of a path from root to leaf within a mst topology
+ * MST_END - the leaf node of a mst topology
+ */
+enum dp_mst_role {
+	MST_NONE = 0,
+	MST_ROOT,
+	MST_BRANCH,
+	MST_END,
+};
+
+static const char *const mst_roles[] = {
+    "no",
+    "root",
+    "branch",
+    "end",
+};
+
+/*
+ * Enumeration of MST progress state. The bit order should align
+ * with the 'enum mst_progress_statusto' in amdgpu_dm.h of
+ * upstreamed amdgpu kernel driver
+ */
+enum mst_progress_status {
+	MST_STATUS_DEFAULT = 0,
+	MST_PROBE = BIT(0),
+	MST_REMOTE_EDID = BIT(1),
+	MST_ALLOCATE_NEW_PAYLOAD = BIT(2),
+	MST_CLEAR_ALLOCATED_PAYLOAD = BIT(3),
+	NUM_MST_STATUS = 4,
+};
+
 uint32_t igt_amd_create_bo(int fd, uint64_t size);
 void *igt_amd_mmap_bo(int fd, uint32_t handle, uint64_t size, int prot);
 unsigned int igt_amd_compute_offset(unsigned int* swizzle_pattern,
@@ -192,4 +233,10 @@ int  igt_amd_read_psr_state(int drm_fd, char *connector_name);
 bool igt_amd_has_visual_confirm(int drm_fd);
 int  igt_amd_get_visual_confirm(int drm_fd);
 bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm option);
+
+/* DP MST debugfs helpers*/
+void igt_amd_require_mst(igt_display_t *display, int drm_fd);
+enum dp_mst_role igt_amd_get_mst_role(int drm_fd, igt_output_t *output);
+uint8_t igt_amd_get_mst_progress_status(int drm_fd, igt_output_t *output);
+void mst_trigger_hotplug(int drm_fd, igt_output_t *output);
 #endif /* IGT_AMD_H */
diff --git a/tests/amdgpu/amd_mst.c b/tests/amdgpu/amd_mst.c
new file mode 100644
index 00000000..def4edbc
--- /dev/null
+++ b/tests/amdgpu/amd_mst.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2022 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "igt.h"
+#include "igt_amd.h"
+
+IGT_TEST_DESCRIPTION("This igt test validates DP MST feature by:"
+	"1. Check each mst end sink successfully go through mst light up procedure"
+	"after hotplug root branch."
+	"2. Check each mst end sink successfully go through mst light up procedure"
+	"after suspend-resume");
+
+/* Maximum pipes on any AMD ASIC. */
+#define MAX_PIPES 6
+
+typedef struct data {
+	igt_display_t display;
+	igt_plane_t *primary[MAX_PIPES];
+	igt_output_t *output_mst_end[MAX_PIPES];
+	igt_output_t *output_mst_root[MAX_PIPES];
+	igt_pipe_t *pipe[MAX_PIPES];
+	igt_fb_t ref_fb[MAX_PIPES];
+	drmModeModeInfo mode[MAX_PIPES];
+	enum pipe pipe_id[MAX_PIPES];
+	int w[MAX_PIPES];
+	int h[MAX_PIPES];
+	int fd;
+	int num_mst_end;
+	int usr_num_mst_end;
+	int delay_for_progress;
+} data_t;
+
+static void test_init(data_t *data)
+{
+	igt_display_t *display = &data->display;
+	int i, m, n, max_pipes = display->n_pipes;
+	enum dp_mst_role role;
+	drmModeConnector *conn;
+
+	for_each_pipe(display, i) {
+		data->pipe_id[i] = PIPE_A + i;
+		data->pipe[i] = &data->display.pipes[data->pipe_id[i]];
+		data->primary[i] = igt_pipe_get_plane_type(
+			data->pipe[i], DRM_PLANE_TYPE_PRIMARY);
+	}
+
+	/* Find mst connected root/end connector*/
+	for (i = 0, n = 0, m = 0, data->num_mst_end = 0;
+		i < display->n_outputs && n < max_pipes && m < max_pipes; i++) {
+		igt_output_t *output = &display->outputs[i];
+
+		conn = output->config.connector;
+		if (conn->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+			continue;
+
+		role = igt_amd_get_mst_role(data->fd, output);
+
+		switch(role) {
+			case MST_ROOT:
+				data->output_mst_root[n++] = output;
+				break;
+			case MST_END:
+				if (!igt_output_is_connected(output))
+					continue;
+				data->output_mst_end[m] = output;
+				igt_assert(kmstest_get_connector_default_mode(
+				data->fd, output->config.connector, &data->mode[m]));
+
+				data->w[m] = data->mode[m].hdisplay;
+				data->h[m] = data->mode[m].vdisplay;
+				m++;
+				data->num_mst_end++;
+				break;
+			default:
+				continue;
+				break;
+		}
+	}
+
+	igt_require(data->output_mst_root[0]);
+	igt_require(data->output_mst_end[0]);
+	if (data->usr_num_mst_end)
+		igt_assert_eq(data->usr_num_mst_end, data->num_mst_end);
+	igt_display_reset(display);
+}
+
+static void test_fini(data_t *data)
+{
+	igt_display_t *display = &data->display;
+
+	igt_display_reset(display);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, 0);
+}
+
+static bool show_mst_status_result(igt_output_t *output, uint8_t status)
+{
+	uint8_t bit_idx = 0;
+	bool result = true;
+
+	igt_info("Connector:%s mst progress status:\n", output->name);
+
+	for (bit_idx = 0; bit_idx < NUM_MST_STATUS; bit_idx++) {
+		switch(BIT(bit_idx)) {
+			case MST_PROBE:
+				if ((status & MST_PROBE) == MST_STATUS_DEFAULT)
+					result = false;
+				igt_info("Probe:%s\n", (status & MST_PROBE) ? "PASS" : "FAIL");
+				break;
+			case MST_REMOTE_EDID:
+				if ((status & MST_REMOTE_EDID) == MST_STATUS_DEFAULT)
+					result = false;
+				igt_info("Remote EDID:%s\n", (status & MST_REMOTE_EDID) ? "PASS" : "FAIL");
+				break;
+			case MST_ALLOCATE_NEW_PAYLOAD:
+				if ((status & MST_ALLOCATE_NEW_PAYLOAD) == MST_STATUS_DEFAULT)
+					result = false;
+				igt_info("Allocate new payload:%s\n",
+					(status & MST_ALLOCATE_NEW_PAYLOAD) ? "PASS" : "FAIL");
+				break;
+			default:
+				break;
+		}
+	}
+
+	return result;
+}
+
+static void reset_resource(data_t *data)
+{
+	igt_display_reset(&data->display);
+	igt_display_fini(&data->display);
+	close(data->fd);
+
+	memset(data->primary, 0, sizeof(igt_plane_t *)*MAX_PIPES);
+	memset(data->output_mst_end, 0, sizeof(igt_output_t *)*MAX_PIPES);
+	memset(data->output_mst_root, 0, sizeof(igt_output_t *)*MAX_PIPES);
+	memset(data->pipe, 0, sizeof(igt_pipe_t *)*MAX_PIPES);
+	memset(data->mode, 0, sizeof(drmModeModeInfo)*MAX_PIPES);
+	memset(data->pipe_id, 0, sizeof(enum pipe)*MAX_PIPES);
+	memset(data->w, 0, sizeof(int)*MAX_PIPES);
+	memset(data->h, 0, sizeof(int)*MAX_PIPES);
+
+	data->fd = drm_open_driver_master(DRIVER_AMDGPU);
+	igt_display_require(&data->display, data->fd);
+}
+
+static void set_up_streams(data_t *data)
+{
+	igt_output_t *output;
+	int i;
+
+	for (i = 0; i < MAX_PIPES; i++) {
+		output = data->output_mst_end[i];
+		if (!output || !igt_output_is_connected(output))
+			continue;
+
+		igt_create_pattern_fb(data->fd, data->w[i], data->h[i],
+				      DRM_FORMAT_XRGB8888, 0, &data->ref_fb[i]);
+		igt_output_set_pipe(output, data->pipe_id[i]);
+		igt_plane_set_fb(data->primary[i], &data->ref_fb[i]);
+	}
+	igt_display_commit_atomic(&data->display, DRM_MODE_ATOMIC_ALLOW_MODESET, 0);
+	sleep(5);
+}
+
+static void validate_mst_progress_result(data_t *data)
+{
+	int i = 0;
+	uint8_t status = 0;
+	igt_output_t *output;
+
+	for (i = 0; i < data->num_mst_end; i++) {
+		output = data->output_mst_end[i];
+
+		if (!output)
+			continue;
+
+		status = igt_amd_get_mst_progress_status(data->fd, output);
+
+		/* make sure all mst end device get lit up correctly*/
+		igt_assert(show_mst_status_result(output, status));
+	}
+}
+
+static void test_mst_hotplug_basic(data_t *data)
+{
+	igt_output_t *output;
+	int i = 0;
+
+	test_init(data);
+
+	/* Setup all end mst output */
+	set_up_streams(data);
+
+	/*confirm mst progress is done*/
+	validate_mst_progress_result(data);
+
+	/* Trigger hotplug*/
+	for (i = 0; i < MAX_PIPES; i++) {
+		output = data->output_mst_root[i];
+
+		if (!output)
+			continue;
+
+		mst_trigger_hotplug(data->fd, output);
+	}
+
+	/* mst connectors are dynamically created/destroyed. Need
+	 * to free and reallocate resources then commit streams again
+	 */
+	reset_resource(data);
+
+	test_init(data);
+
+	set_up_streams(data);
+
+	if (data->delay_for_progress)
+		sleep(data->delay_for_progress);
+	else
+		sleep(5);
+	/*confirm mst progress status again*/
+	validate_mst_progress_result(data);
+
+	/*do suspend resume*/
+	igt_system_suspend_autoresume(SUSPEND_STATE_MEM, SUSPEND_TEST_NONE);
+
+	if (data->delay_for_progress)
+		sleep(data->delay_for_progress);
+	else
+		sleep(5);
+
+	/*confirm mst progress status again*/
+	validate_mst_progress_result(data);
+
+	for (i = 0; i < MAX_PIPES; i++) {
+		output = data->output_mst_end[i];
+
+		if (!output)
+			continue;
+
+		igt_remove_fb(data->fd, &data->ref_fb[i]);
+	}
+
+	test_fini(data);
+}
+
+const char *optstr = "hn:d:";
+static void usage(const char *name)
+{
+	igt_info("Usage: %s options\n", name);
+	igt_info("-h			Show help\n");
+	igt_info("-n mst_num	Set how many monitors connected in the mst topology\n");
+	igt_info("-d delay		Set delay before checking the mst progress status\n");
+	igt_info("NOTE: if -n is not specified, number of monitors will be detected automatically"
+		"by current connection status\n");
+	igt_info("NOTE: if -d is not specified, delay before checking the mst progress status"
+		"will be set as 5s\n");
+}
+
+int main(int argc, char **argv)
+{
+	data_t data;
+	int c;
+
+	igt_skip_on_simulation();
+
+	memset(&data, 0, sizeof(data));
+
+	data.fd = drm_open_driver_master(DRIVER_AMDGPU);
+	kmstest_set_vt_graphics_mode();
+
+	igt_display_require(&data.display, data.fd);
+	igt_require(data.display.is_atomic);
+	igt_display_require_output(&data.display);
+
+	igt_amd_require_hpd(&data.display, data.fd);
+	igt_amd_require_mst(&data.display, data.fd);
+
+	while((c = getopt(argc, argv, optstr)) != -1) {
+		switch(c) {
+		case 'n':
+			data.usr_num_mst_end = atoi(optarg);
+			break;
+		case 'd':
+			data.delay_for_progress = atoi(optarg);
+			break;
+		case 'h':
+		default:
+			usage(argv[0]);
+			exit(EXIT_SUCCESS);
+		}
+	}
+
+	test_mst_hotplug_basic(&data);
+
+	igt_display_fini(&data.display);
+}
diff --git a/tests/amdgpu/meson.build b/tests/amdgpu/meson.build
index f4a0b9fd..5964c080 100644
--- a/tests/amdgpu/meson.build
+++ b/tests/amdgpu/meson.build
@@ -21,6 +21,7 @@ if libdrm_amdgpu.found()
 			  'amd_psr',
 			  'amd_plane',
 			  'amd_ilr',
+			  'amd_mst',
 			]
 	amdgpu_deps += libdrm_amdgpu
 endif
-- 
2.25.1



More information about the igt-dev mailing list