Skip to contentSkip to conversation

test/amdgpu: Add DP mst test

PATCH In review Wayne Lin 4 participants 6 messages Aug 2, 2022 – Aug 8, 2022

Subsystems (2)
lore
Share & tools
lore

Wayne Lin

author
#0 lore raw

PATCH v2 1/1 test/amdgpu: Add DP mst test

[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

---
 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
lib/igt_amd.c +157 -0
index bef9c193..7ad70dc8 100644
@@ -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
lib/igt_amd.h +47 -0

63 lines not on this page · raw message

tests/amdgpu/amd_mst.c new file +316 -0

316 lines not on this page · raw message

tests/amdgpu/meson.build +1 -0
index f4a0b9fd..5964c080 100644
@@ -21,6 +21,7 @@ if libdrm_amdgpu.found()
 			  'amd_psr',
 			  'amd_plane',
 			  'amd_ilr',
+			  'amd_mst',
 			]
 	amdgpu_deps += libdrm_amdgpu
 endif
Signature · 1 line
2.25.1
#1 lore raw

[igt-dev] ✗ Fi.CI.BUILD: failure for test/amdgpu: Add DP mst test (rev2)

== Series Details ==

== Series Details ==

Series: test/amdgpu: Add DP mst test (rev2)
URL : https://patchwork.freedesktop.org/series/106166/
State : failure

== Summary ==

IGT patchset build failed on latest successful build c558dafb6c0e905cfaa02466e6bfb2a9f9cfaffe lib/intel_batchbuffer: Ensure obj.alignment is zeroed for softpinning

311/330 testcase check amdgpu/amd_psr                 OK               0.13s
312/330 testcase check amdgpu/amd_plane               OK               0.12s
313/330 testcase check amdgpu/amd_ilr                 OK               0.11s
314/330 testcase check amdgpu/amd_mst                 FAIL             0.11s   exit status 1
Quoted 1 line
Quoted 1 line
Quoted 1 line

MALLOC_PERTURB_=44 /home/cidrm/igt-gpu-tools/tests/igt_command_line.sh amdgpu/amd_mst

――――――――――――――――――――――――――――――――――――― ✀ ――――――――――――――――――――――――――――――――――――― tests/amdgpu/amd_mst:
Checking invalid option handling...
Test requirement not met in function drm_open_driver, file ../lib/drmtest.c:574:
Test requirement: !(fd<0)
No known gpu found for chipset flags 0x8 (amdgpu)
Last errno: 2, No such file or directory
SKIP (-1.000s)
Checking valid option handling...
FAIL: tests/amdgpu/amd_mst ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

315/330 runner_json                                   OK               0.09s
316/330 assembler test/mov                            OK               0.08s
317/330 assembler test/frc                            OK               0.08s
318/330 assembler test/regtype                        OK               0.08s
319/330 assembler test/rndd                           OK               0.07s
320/330 assembler test/rndu                           OK               0.06s
321/330 assembler test/rnde                           OK               0.06s
322/330 assembler test/rnde-intsrc                    OK               0.05s
323/330 assembler test/rndz                           OK               0.05s
324/330 assembler test/lzd                            OK               0.04s
325/330 assembler test/not                            OK               0.04s
326/330 assembler test/immediate                      OK               0.03s
327/330 lib igt_nesting                               OK               2.31s
328/330 lib igt_fork                                  OK               2.62s
329/330 runner                                        OK               4.78s
330/330 testcase check gem_concurrent_all             OK               5.87s

Summary of Failures:

314/330 testcase check amdgpu/amd_mst FAIL 0.11s exit status 1

Ok:                 326 
Expected Fail:      3   
Fail:               1   
Unexpected Pass:    0   
Skipped:            0   
Timeout:            0   

Full log written to /home/cidrm/igt-gpu-tools/build/meson-logs/testlog.txt
FAILED: meson-test
/usr/bin/meson test --no-rebuild --print-errorlogs
ninja: build stopped: subcommand failed.

#2 lore raw
In reply to Wayne Lin
Quoted 106 lines · Wayne Lin

[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

---
 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
lib/igt_amd.c +63 -0
index bef9c193..7ad70dc8 100644
@@ -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);

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt · line 1250

Quoted 10 lines
lib/igt_amd.c +10 -0
+
+	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);

You're missing a newline from this print.

Signature · 1 line
Petri Latvala
Quoted 492 lines
lib/igt_amd.c +84 -0
+	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
lib/igt_amd.h +47 -0
index 428bfe6f..2d54ddc3 100644
@@ -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 */
tests/amdgpu/amd_mst.c new file +316 -0
new file mode 100644
index 00000000..def4edbc
@@ -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);
+}
tests/amdgpu/meson.build +1 -0
index f4a0b9fd..5964c080 100644
@@ -21,6 +21,7 @@ if libdrm_amdgpu.found()
 			  'amd_psr',
 			  'amd_plane',
 			  'amd_ilr',
+			  'amd_mst',
 			]
 	amdgpu_deps += libdrm_amdgpu
 endif
Signature · 1 line
2.25.1
#3 lore raw
Quoted 111 lines · Petri Latvala
Quoted 110 lines · Wayne Lin

[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

---
 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
lib/igt_amd.c +67 -0
index bef9c193..7ad70dc8 100644
@@ -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;
Quoted 454 lines

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt · line 1250

Quoted 6 lines
lib/igt_amd.c +6 -0
+			break;
+		}
+	}
+
+	if (role != MST_NONE)
+		igt_info("Connector %s: is a mst %s", output->name, buf);

You're missing a newline from this print.

Signature · 1 line
Petri Latvala
Quoted 430 lines
lib/igt_amd.c +84 -0
+	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
lib/igt_amd.h +47 -0
index 428bfe6f..2d54ddc3 100644
@@ -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 */
tests/amdgpu/amd_mst.c new file +269 -0
new file mode 100644
index 00000000..def4edbc
@@ -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);

tests/amdgpu/amd_mst.c line 279

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -0
+	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)

In addition to the earlier comment, no custom main functions in tests please. Use igt_main_args for this. Or igt_simple_main_args if you don't plan to have subtests.

Signature · 1 line
Petri Latvala
Quoted 52 lines
Quoted 52 lines
tests/amdgpu/amd_mst.c new file +37 -0
+{
+	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);
+}
tests/amdgpu/meson.build +1 -0
index f4a0b9fd..5964c080 100644
@@ -21,6 +21,7 @@ if libdrm_amdgpu.found()
 			  'amd_psr',
 			  'amd_plane',
 			  'amd_ilr',
+			  'amd_mst',
 			]
 	amdgpu_deps += libdrm_amdgpu
 endif
Signature · 1 line
2.25.1
#4 lore raw
In reply to Wayne Lin

I realized I should just be open about what the delay on looking at this was, and just some of the general delay from me with reviewing MST work like this in general both in igt and the kernel. It's been pretty difficult finding the motivation to review work like this, and this is a sentiment I've been seeing increasingly in other parts of the community as well.

This is a lot of code, all of which is amdgpu specific despite covering functionality that is not at all specific to amdgpu, in a codebase where testing MST has been a topic of discussion for quite a while now. This adds to another pile of functionality where all of the time to implement it has been taken, without consideration to how other drivers might be able to benefit from this. And it's being done in addition to debugfs code we already have that could be modified to support these things. Put more simply: if someone had the time to write this code in the first place, it's very hard for me to understand how they didn't also have the time to try to contribute this functionality back in an open source project where that's more or less expected and has been the standard. Especially considering that just looking at this code, I'm not finding anything that would be difficult to move to DRM helpers. Some extra state tracking might be needed to keep track of whether we've probed the link address of something yet, or have read the EDID from it, etc. but that doesn't really seem like a deal breaker.

Sure - you could say that the code's been written now, and surely that must be less effort then trying to come up with something like this from scratch. And sometimes it is, but it still takes considerably more effort across the community then just making it work outside your driver in the first place would have. And sometimes it isn't even easier then writing it from scratch. In porting it to other drivers, there might be issues with the approach taken that come up. There also might just be the general struggle of trying to understand how the various layers of abstraction in amdgpu correspond to DRM at all, and the more stuff like this that gets added the higher that bar of understanding for contributions gets even for contributors who very regularly jump between numerous different GPU drivers for different vendors and have been doing so for the better half of a decade.

So, may I ask that we consider changing the approach to how this is implemented, and start from the DRM helpers? I would love to see this kind of functionality introduced to igt and DRM, and I'd be quite happy to review a patch series that added this generally in a way other drivers could take advantage of for either project. I'm sure others would likely find a lot of use with more MST testing infra as well, along with a lot of the other feature work currently going on with amdgpu.

Quoted 605 lines · Wayne Lin

[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

---
 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
lib/igt_amd.c +157 -0
index bef9c193..7ad70dc8 100644
@@ -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
lib/igt_amd.h +47 -0
index 428bfe6f..2d54ddc3 100644
@@ -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 */
tests/amdgpu/amd_mst.c new file +316 -0
new file mode 100644
index 00000000..def4edbc
@@ -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);
+}
tests/amdgpu/meson.build +1 -0
index f4a0b9fd..5964c080 100644
@@ -21,6 +21,7 @@ if libdrm_amdgpu.found()
 			  'amd_psr',
 			  'amd_plane',
 			  'amd_ilr',
+			  'amd_mst',
 			]
 	amdgpu_deps += libdrm_amdgpu
 endif
Signature · 3 lines
Cheers,
 Lyude Paul (she/her)
 Software Engineer at Red Hat
author
#5 lore raw

[Public]

Hi Lyude,

Much appreciated for the feedback and sorry for any inconvenience it brought to you. You're right that it should be not difficult to have such indications in DRM helpers and is much better if we can have this in general.

My thought was that DRM provides helpers for handling sideband message only and it probably will be better if I have those progress status tags within our driver to indicate mst status more accurate. However, it turns out that not much difference at the end. Sorry for not considering it carefully.

I'll try to come up with another approach by drm helpers later. Thanks for your time and help again!

Regards,
Wayne Lin

Quoted 711 lines

-----Original Message-----
From: Lyude Paul <[email protected]>
Sent: Thursday, August 4, 2022 7:43 AM
To: Lin, Wayne <[email protected]>; [email protected]
Cc: Siqueira, Rodrigo <[email protected]>; Wentland, Harry <[email protected]>; Pillai, Aurabindo <[email protected]>; Wu, Hersen <[email protected]>; Zuo, Jerry <[email protected]>; Li, Sun peng (Leo) <[email protected]>
Subject: Re: [PATCH i-g-t v2] test/amdgpu: Add DP mst test

I realized I should just be open about what the delay on looking at this was, and just some of the general delay from me with reviewing MST work like this in general both in igt and the kernel. It's been pretty difficult finding the motivation to review work like this, and this is a sentiment I've been seeing increasingly in other parts of the community as well.

This is a lot of code, all of which is amdgpu specific despite covering functionality that is not at all specific to amdgpu, in a codebase where testing MST has been a topic of discussion for quite a while now. This adds to another pile of functionality where all of the time to implement it has been taken, without consideration to how other drivers might be able to benefit from this. And it's being done in addition to debugfs code we already have that could be modified to support these things. Put more simply: if someone had the time to write this code in the first place, it's very hard for me to understand how they didn't also have the time to try to contribute this functionality back in an open source project where that's more or less expected and has been the standard. Especially considering that just looking at this code, I'm not finding anything that would be difficult to move to DRM helpers. Some extra state tracking might be needed to keep track of whether we've probed the link address of something yet, or have read the EDID from it, etc. but that doesn't really seem like a deal breaker.

Sure - you could say that the code's been written now, and surely that must be less effort then trying to come up with something like this from scratch. And sometimes it is, but it still takes considerably more effort across the community then just making it work outside your driver in the first place would have. And sometimes it isn't even easier then writing it from scratch. In porting it to other drivers, there might be issues with the approach taken that come up. There also might just be the general struggle of trying to understand how the various layers of abstraction in amdgpu correspond to DRM at all, and the more stuff like this that gets added the higher that bar of understanding for contributions gets even for contributors who very regularly jump between numerous different GPU drivers for different vendors and have been doing so for the better half of a decade.

So, may I ask that we consider changing the approach to how this is implemented, and start from the DRM helpers? I would love to see this kind of functionality introduced to igt and DRM, and I'd be quite happy to review a patch series that added this generally in a way other drivers could take advantage of for either project. I'm sure others would likely find a lot of use with more MST testing infra as well, along with a lot of the other feature work currently going on with amdgpu.

Quoted 26 lines · Wayne Lin

[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

Quoted 5 lines
---
 lib/igt_amd.c            | 157 +++++++++++++++++++
 lib/igt_amd.h            |  47 ++++++
 tests/amdgpu/amd_mst.c   | 316

+++++++++++++++++++++++++++++++++++++++

Quoted 11 lines
 tests/amdgpu/meson.build |   1 +
 4 files changed, 521 insertions(+)
 create mode 100644 tests/amdgpu/amd_mst.c
lib/igt_amd.c index bef9c193..7ad70dc8 renamed from lib/igt_amd.c +0 -0

100644

lib/igt_amd.c +0 -0
@@ -1175,3 +1175,160 @@ bool igt_amd_set_visual_confirm(int drm_fd,

enum amdgpu_debug_visual_confirm opt

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt

Quoted 10 lines
lib/igt_amd.c +8 -0
 	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) &

lib/igt_amd.c

Quoted 1 line
lib/igt_amd.c +1 -0
+			igt_amd_output_has_debugfs(drm_fd,

connector_name,

Quoted 36 lines
lib/igt_amd.c +36 -0
+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;

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt

Quoted 10 lines
lib/igt_amd.c +10 -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)

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt

Quoted 3 lines
lib/igt_amd.c +3 -0
+		return MST_NONE;
+
+	ret = igt_debugfs_simple_read(fd,

DEBUGFS_MST_IS_MST_CONNECTOR, buf, sizeof(buf));

Quoted 21 lines
lib/igt_amd.c +16 -0
+	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);

lib/igt_amd.c +4 -0
+
+	return role;
+}
+

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt

Quoted 10 lines
lib/igt_amd.c +10 -0
+/**
+ * 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.

Quoted 1 line

+ * MST_REMOTE_EDID - successfully read out edid by REMOTE_I2C_READ

sideband msg.

Quoted 1 line

+ * MST_ALLOCATE_NEW_PAYLOAD - suuccessfully allocate time slots for

the stream.

Quoted 1 line

+ * MST_CLEAR_ALLOCATED_PAYLOAD - successfully clean out allocated

time slots.

Quoted 7 lines

+ */

lib/igt_amd.c +6 -0
+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;

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt

Quoted 10 lines
lib/igt_amd.c +10 -0
+	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)

lib/igt_amd.c bool igt_amd_set_visual_confirm(int drm_fd, enum amdgpu_debug_visual_confirm opt

Quoted 3 lines
lib/igt_amd.c +3 -0
+		return MST_STATUS_DEFAULT;
+
+	ret = igt_debugfs_simple_read(fd,

DEBUGFS_MST_PROGRESS_STATUS, buf, sizeof(buf));

Quoted 133 lines
lib/igt_amd.c +47 -0
+	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
lib/igt_amd.h index 428bfe6f..2d54ddc3 renamed from lib/igt_amd.h +0 -0

100644

lib/igt_amd.h +40 -0
@@ -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);

lib/igt_amd.h +6 -0
+
+/* 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 */
tests/amdgpu/amd_mst.c new file renamed from tests/amdgpu/amd_mst.c +0 -0

mode 100644 index 00000000..def4edbc

tests/amdgpu/amd_mst.c new file +10 -0
@@ -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,

tests/amdgpu/amd_mst.c line 20

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -0
+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,

tests/amdgpu/amd_mst.c line 24

Quoted 4 lines
tests/amdgpu/amd_mst.c new file +4 -0
+EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN

NO EVENT

tests/amdgpu/amd_mst.c line 30

Quoted 6 lines
tests/amdgpu/amd_mst.c new file +6 -0
+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

tests/amdgpu/amd_mst.c

Quoted 9 lines
tests/amdgpu/amd_mst.c new file +9 -0
+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"

tests/amdgpu/amd_mst.c line 41

Quoted 2 lines
tests/amdgpu/amd_mst.c new file +2 -0
+	"after hotplug root branch."
+	"2. Check each mst end sink successfully go through mst light up

procedure"

Quoted 29 lines
tests/amdgpu/amd_mst.c new file +29 -0
+	"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;
+

tests/amdgpu/amd_mst.c

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -0
+	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++) {

tests/amdgpu/amd_mst.c

Quoted 4 lines
tests/amdgpu/amd_mst.c new file +4 -0
+		igt_output_t *output = &display->outputs[i];
+
+		conn = output->config.connector;
+		if (conn->connector_type !=

DRM_MODE_CONNECTOR_DisplayPort)

tests/amdgpu/amd_mst.c line 87

Quoted 13 lines
tests/amdgpu/amd_mst.c new file +13 -0
+			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(
Quoted 19 lines
tests/amdgpu/amd_mst.c new file +1 -0
+				data->fd, output->config.connector, &data-

mode[m]));

tests/amdgpu/amd_mst.c new file +16 -0
+
+				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);

tests/amdgpu/amd_mst.c

Quoted 9 lines
tests/amdgpu/amd_mst.c new file +9 -0
+	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,

tests/amdgpu/amd_mst.c

Quoted 13 lines

+0); }

tests/amdgpu/amd_mst.c new file +12 -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)

tests/amdgpu/amd_mst.c

Quoted 2 lines
tests/amdgpu/amd_mst.c new file +2 -0
+					result = false;
+				igt_info("Probe:%s\n", (status &

MST_PROBE) ? "PASS" : "FAIL");

tests/amdgpu/amd_mst.c

Quoted 3 lines
tests/amdgpu/amd_mst.c new file +3 -0
+				break;
+			case MST_REMOTE_EDID:
+				if ((status & MST_REMOTE_EDID) ==

MST_STATUS_DEFAULT)

tests/amdgpu/amd_mst.c

Quoted 2 lines
tests/amdgpu/amd_mst.c new file +2 -0
+					result = false;
+				igt_info("Remote EDID:%s\n", (status &

MST_REMOTE_EDID) ? "PASS" : "FAIL");

tests/amdgpu/amd_mst.c

Quoted 3 lines
tests/amdgpu/amd_mst.c new file +3 -0
+				break;
+			case MST_ALLOCATE_NEW_PAYLOAD:
+				if ((status & MST_ALLOCATE_NEW_PAYLOAD)

== MST_STATUS_DEFAULT)

tests/amdgpu/amd_mst.c

Quoted 3 lines
tests/amdgpu/amd_mst.c new file +3 -0
+					result = false;
+				igt_info("Allocate new payload:%s\n",
+					(status &

MST_ALLOCATE_NEW_PAYLOAD) ? "PASS" : "FAIL");

Quoted 6 lines
tests/amdgpu/amd_mst.c new file +6 -0
+				break;
+			default:
+				break;
+		}
+	}
+

tests/amdgpu/amd_mst.c

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -0
+	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);

tests/amdgpu/amd_mst.c

Quoted 1 line
tests/amdgpu/amd_mst.c new file +1 -0
+	memset(data->output_mst_root, 0, sizeof(igt_output_t

*)*MAX_PIPES);

Quoted 21 lines
tests/amdgpu/amd_mst.c new file +20 -0
+	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]);

tests/amdgpu/amd_mst.c

Quoted 4 lines
tests/amdgpu/amd_mst.c new file +4 -0
+		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);

Quoted 5 lines
tests/amdgpu/amd_mst.c new file +5 -0
+	sleep(5);
+}
+
+static void validate_mst_progress_result(data_t *data) {
+	int i = 0;

tests/amdgpu/amd_mst.c

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -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);

Quoted 36 lines
tests/amdgpu/amd_mst.c new file +36 -0
+
+		/* 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);

tests/amdgpu/amd_mst.c

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -0
+
+	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);

Quoted 17 lines
tests/amdgpu/amd_mst.c new file +17 -0
+
+	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]);
+	}

tests/amdgpu/amd_mst.c

Quoted 10 lines
tests/amdgpu/amd_mst.c new file +10 -0
+
+	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");

tests/amdgpu/amd_mst.c

Quoted 1 line
tests/amdgpu/amd_mst.c new file +1 -0
+	igt_info("-d delay		Set delay before checking the mst

progress status\n");

tests/amdgpu/amd_mst.c

Quoted 1 line
tests/amdgpu/amd_mst.c new file +1 -0
+	igt_info("NOTE: if -n is not specified, number of monitors will be

detected automatically"

tests/amdgpu/amd_mst.c

Quoted 2 lines
tests/amdgpu/amd_mst.c new file +2 -0
+		"by current connection status\n");
+	igt_info("NOTE: if -d is not specified, delay before checking the mst

progress status"

Quoted 46 lines
tests/amdgpu/amd_mst.c new file +41 -0
+		"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);
+}
tests/amdgpu/meson.build index renamed from tests/amdgpu/meson.build +0 -0

f4a0b9fd..5964c080 100644

tests/amdgpu/meson.build if libdrm_amdgpu.found() · line 27

Quoted 7 lines
tests/amdgpu/meson.build +1 -0
@@ -21,6 +21,7 @@ if libdrm_amdgpu.found()
 			  'amd_psr',
 			  'amd_plane',
 			  'amd_ilr',
+			  'amd_mst',
 			]
 	amdgpu_deps += libdrm_amdgpu
 endif

--
Cheers,
Lyude Paul (she/her)
Software Engineer at Red Hat