[PATCH] [i-g-t] tests/amdgpu/amd_multidisplay_modeset: Run mode set and display enable for multidisplays

Kamil Konieczny kamil.konieczny at linux.intel.com
Wed Feb 21 08:37:42 UTC 2024


Hi Hersen,
On 2024-02-20 at 11:43:23 -0500, Hersen Wu wrote:
> Run mode set, display enable and disable for multiple
> displays.
> 
> Loop all display modes. Loop all display combinations.
> For DP display(eDP, SST DP, displays connected to
> outputs of MST hub), if it supports DP rx crc test, IGT
> will read DP rx crc and compare with pipe crc. If DSC
> is not enabled for display, DP rx crc should equal to
> pipe crc.
> 
> Signed-off-by: Hersen Wu <hersenxs.wu at amd.com>
> ---
>  tests/amdgpu/amd_multidisplay_modeset.c | 446 ++++++++++++++++++++++++
>  tests/amdgpu/meson.build                |  11 +-
>  2 files changed, 452 insertions(+), 5 deletions(-)
>  create mode 100644 tests/amdgpu/amd_multidisplay_modeset.c
> 
> diff --git a/tests/amdgpu/amd_multidisplay_modeset.c b/tests/amdgpu/amd_multidisplay_modeset.c
> new file mode 100644
> index 000000000..66981f212
> --- /dev/null
> +++ b/tests/amdgpu/amd_multidisplay_modeset.c
> @@ -0,0 +1,446 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright 2024 Advanced Micro Devices, Inc.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

Delere this text from "THE SOFTWARE...", it is SPDX which raplaces it.

> + * 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 <dirent.h>
> +#include "igt.h"
> +#include "igt_amd.h"
> +
> +#define MAX_PIPES 6
> +
> +/* Common test data. */
> +struct data_t {
> +	igt_display_t display;
> +	igt_plane_t *primary[MAX_PIPES];
> +	igt_output_t *output[MAX_PIPES];
> +	int fd;
> +	igt_pipe_crc_t *pipe_crc_dprx[MAX_PIPES];
> +	igt_crc_t crc_fb[MAX_PIPES];
> +	igt_crc_t crc_dprx[MAX_PIPES];
> +	igt_pipe_crc_t *pipe_crc_otg[MAX_PIPES];
> +	igt_crc_t crc_otg[MAX_PIPES];
> +};
> +
> +enum sub_test {
> +	MODE_SET,
> +	DISPLAY_ENABLE_DISABLE
> +};
> +
> +/* DP DPCD offset */
> +#define DPCD_TEST_SINK_MISC 0x246
> +
> +static bool is_mst_connector(int drm_fd, uint32_t connector_id)
> +{
> +	return kmstest_get_property(drm_fd, connector_id,
> +				    DRM_MODE_OBJECT_CONNECTOR,
> +				    "PATH", NULL,
> +				    NULL, NULL);
> +}
> +
> +static bool sink_detect_error(int drm_fd, uint32_t connector_id, int error_code)
> +{
> +	switch (error_code) {
> +	case ETIMEDOUT:
> +	case ENXIO:
> +		return true;
> +	case EIO:
> +		return is_mst_connector(drm_fd, connector_id);
> +	default:
> +		return false;
> +	};
> +}
> +
> +/* Read from /dev/drm_dp_aux
> + * addr: DPCD offset
> + * val:  Read value of DPCD register
> + */
> +static bool dpcd_read_byte(int drm_fd,
> +	drmModeConnector *connector, uint32_t addr, uint8_t *val)
> +{
> +	DIR *dir;
> +	int dir_fd;
> +	uint8_t buf[16] = {0};
> +	*val = 0;
> +
> +	dir_fd = igt_connector_sysfs_open(drm_fd, connector);
> +	igt_assert(dir_fd >= 0);
> +
> +	if (connector->connection != DRM_MODE_CONNECTED &&
> +	    is_mst_connector(drm_fd, connector->connector_id)) {
> +		close(dir_fd);
> +		return false;
> +	}
> +
> +	dir = fdopendir(dir_fd);
> +	igt_assert(dir);
> +
> +	for (;;) {
> +		struct dirent *ent;
> +		char path[5 + sizeof(ent->d_name)];
> +		int fd, ret, i, j, k;
> +
> +		ent = readdir(dir);
> +		if (!ent)
> +			break;
> +
> +		if (strncmp(ent->d_name, "drm_dp_aux", 10))
> +			continue;
> +
> +		snprintf(path, sizeof(path), "/dev/%s", ent->d_name);
> +
> +		fd = open(path, O_RDONLY);
> +		igt_assert(fd >= 0);
> +
> +		k = (addr / 16) + 1;
> +		j = addr % 16;
> +
> +		/* read 16 bytes each loop */
> +		for (i = 0; i < k; i++) {
> +			ret = read(fd, buf, sizeof(buf));
> +			if (ret < 0)
> +				break;
> +			if (ret != sizeof(buf))
> +				break;
> +		}
> +
> +		igt_info("%s: %s\n", path,
> +			 ret > 0 ? "success" : strerror(errno));
> +
> +		igt_assert(ret == sizeof(buf) ||
> +			   sink_detect_error(drm_fd, connector->connector_id, errno));
> +
> +		close(fd);
> +
> +		closedir(dir);
> +		close(dir_fd);
> +
> +		if (ret > 0)
> +			*val = buf[j];
> +
> +		return (ret > 0);
> +	}
> +
> +	closedir(dir);
> +	close(dir_fd);
> +	return false;
> +}
> +
> +static void set_all_output_pipe_to_none(struct data_t *data)
> +{
> +	igt_output_t *output;
> +
> +	for_each_connected_output(&data->display, output) {
> +		igt_output_set_pipe(output, PIPE_NONE);
> +	}
> +
> +	igt_display_commit_atomic(&data->display,
> +		DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
> +}
> +
> +static void test_init(struct data_t *data)
> +{
> +	igt_display_t *display = &data->display;
> +	int i;
> +	bool ret = false;
> +	uint8_t dpcd_246h = 0;
> +
> +	for (i = 0; i < MAX_PIPES; i++)
> +		data->pipe_crc_dprx[i] = NULL;
> +
> +	for_each_pipe(display, i) {
> +		igt_output_t *output;
> +		igt_pipe_t *pipes;
> +
> +		/* For each valid pipe, get one connected display.
> +		 * This will let displays connected to MST hub be
> +		 * tested
> +		 */
> +		output = igt_get_single_output_for_pipe(display, i);
> +		pipes = &display->pipes[i];
> +		data->primary[i] = igt_pipe_get_plane_type(
> +			&data->display.pipes[i], DRM_PLANE_TYPE_PRIMARY);
> +		data->output[i] = output;
> +
> +		/* dp rx crc only available for eDP, SST DP, MST DP */
> +		if ((output->config.connector->connector_type ==
> +			DRM_MODE_CONNECTOR_eDP ||
> +			output->config.connector->connector_type ==
> +				DRM_MODE_CONNECTOR_DisplayPort)) {
> +			/* DPCD 0x246 bit5: 1 -- sink device support CRC test */
> +			ret = dpcd_read_byte(data->fd, output->config.connector,
> +				DPCD_TEST_SINK_MISC, &dpcd_246h);
> +			if (ret && ((dpcd_246h & 0x20) != 0x0))
> +				data->pipe_crc_dprx[i] = igt_pipe_crc_new(
> +					data->fd, pipes->pipe,
> +					AMDGPU_PIPE_CRC_SOURCE_DPRX);
> +		}
> +
> +		data->pipe_crc_otg[i] = igt_pipe_crc_new(data->fd, pipes->pipe,
> +						IGT_PIPE_CRC_SOURCE_AUTO);
> +		/* disable eDP PSR */
> +		if (data->output[i]->config.connector->connector_type ==
> +				DRM_MODE_CONNECTOR_eDP) {
> +			kmstest_set_connector_dpms(display->drm_fd,
> +				data->output[i]->config.connector,
> +				DRM_MODE_DPMS_OFF);
> +
> +			igt_amd_disallow_edp_enter_psr(data->fd,
> +				data->output[i]->name, true);
> +
> +			kmstest_set_connector_dpms(display->drm_fd,
> +				data->output[i]->config.connector,
> +				DRM_MODE_DPMS_ON);
> +		}
> +	}
> +
> +	igt_require(data->output[0]);
> +	igt_display_reset(display);
> +}
> +
> +static void test_fini(struct data_t *data)
> +{
> +	igt_display_t *display = &data->display;
> +	int i = 0;
> +
> +	for_each_pipe(display, i) {
> +		if (data->pipe_crc_dprx[i])
> +			igt_pipe_crc_free(data->pipe_crc_dprx[i]);
> +		igt_pipe_crc_free(data->pipe_crc_otg[i]);
> +	}
> +
> +	igt_display_reset(display);
> +	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, 0);
> +}
> +
> +static void multiple_display_test(struct data_t *data, enum sub_test test_mode)
> +{
> +	igt_output_t *output;
> +	struct igt_fb *buf;
> +	drmModeConnectorPtr conn;
> +	drmModeModeInfoPtr kmode;
> +	igt_display_t *display = &data->display;
> +	int i, j, ret, num_disps, count_modes_disp_config, bitmap_disps;
> +	char *crc_str, *crc_str_1, *crc_str_2;
> +	bool any_mst = false;
> +
> +	test_init(data);
> +
> +	num_disps = 0;
> +	for_each_connected_output(display, output)
> +		num_disps++;
> +
> +	igt_info("Connected num_disps:%d\n", num_disps);
> +
> +	igt_skip_on_f(num_disps > igt_display_get_n_pipes(&data->display) ||
> +			      num_disps > data->display.n_outputs,
> +		      "ASIC does not have %d outputs/pipes\n", num_disps);
> +
> +	buf = calloc(sizeof(struct igt_fb), num_disps);
> +	igt_assert_f(buf, "Failed to allocate memory\n");
> +
> +	/* For mode test, it is max number of modes for
> +	 * all connected displays. For enable/disable test,
> +	 * it is number of connected display combinations
> +	 */
> +	count_modes_disp_config = 0;
> +	for_each_connected_output(display, output) {
> +		conn = output->config.connector;
> +		if (count_modes_disp_config < conn->count_modes)
> +			count_modes_disp_config = conn->count_modes;
> +		if  (is_mst_connector(data->fd, conn->connector_id))
> +			any_mst = true;
> +	}
> +
> +	if (test_mode == DISPLAY_ENABLE_DISABLE)
> +		count_modes_disp_config = (1 << num_disps) - 1;
> +
> +	/* display combination bitmap for mode set or enable display
> +	 * bit 0: display 0
> +	 * bit 1: display 1
> +	 * bit 2: display 2, etc.
> +	 *
> +	 * bitmap_disps:0x5 means display 0,2 light up
> +	 */
> +	bitmap_disps = (1 << num_disps) - 1;
> +	igt_info("count_modes_disp_config:%d bitmap_disps:%x\n\n\n",
> +		count_modes_disp_config, bitmap_disps);
> +
> +	for (i = 0; i < count_modes_disp_config; i++) {
> +		if (test_mode == DISPLAY_ENABLE_DISABLE) {
> +			bitmap_disps = i + 1;
> +			igt_info("\n\ndispconfig loop:%d disp bitmap:%x -----\n",
> +				i, bitmap_disps);
> +
> +			/* disable all displays */
> +			set_all_output_pipe_to_none(data);
> +		} else
> +			igt_info("\n\nnmode loop:%d -----\n", i);
> +		j = 0;
> +		for_each_connected_output(display, output) {
> +			if (test_mode == DISPLAY_ENABLE_DISABLE) {
> +				/* only enable display mapping to
> +				 * bitmap_disps with value 1
> +				 */
> +				if (!(bitmap_disps & (1 << j))) {
> +					j++;
> +					continue;
> +				}
> +				kmode = igt_output_get_mode(output);
> +				igt_assert(kmode);
> +				igt_info("pipe:%d %s mode:%s\n",
> +					j, output->name, kmode->name);
> +				igt_info("clk:%d ht:%d vt:%d hz:%d\n",
> +					kmode->clock, kmode->htotal,
> +					kmode->vtotal, kmode->vrefresh);
> +			} else {
> +				conn = output->config.connector;
> +
> +				if (i < conn->count_modes)
> +					kmode = &conn->modes[i];
> +				else
> +					kmode = &conn->modes[0];
> +
> +				igt_info("pipe:%d %s mode:%s\n", j, output->name, kmode->name);
> +				igt_info("clk:%d ht:%d vt:%d hz:%d\n", kmode->clock,
> +					kmode->htotal, kmode->vtotal, kmode->vrefresh);
> +
> +				if  (is_mst_connector(data->fd, conn->connector_id)) {
> +					/* mst hub may not support mode with high clk
> +					 * more than 4k at 60hz or high refresh rate
> +					 */
> +					if (kmode->clock > 596000 || kmode->vrefresh > 120) {
> +						kmode = &conn->modes[0];
> +						igt_info("Mode may not be supported by mst hub.	Use default mode from monitor!\n");
> +						igt_info("clk:%d ht:%d vt:%d hz:%d\n",
> +							kmode->clock, kmode->htotal,
> +							kmode->vtotal, kmode->vrefresh);
> +					}
> +				}
> +
> +				/* memory for output->config.connector will be re-alloacted */
> +				igt_output_override_mode(output, kmode);
> +			}
> +
> +			igt_create_pattern_fb(data->fd, kmode->hdisplay,
> +					kmode->vdisplay, DRM_FORMAT_XRGB8888,
> +					0, (buf + j));
> +
> +			igt_output_set_pipe(output, j);
> +			igt_plane_set_fb(data->primary[j], (buf + j));
> +			j++;
> +		}
> +
> +		igt_display_commit_atomic(&data->display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
> +
> +		/* MST hub may take longer time for mode change or display
> +		 * enable/disable. Wait for MST stable before CRC check.
> +		 * TODO: check if there is a better way to check MST stable
> +		 */
> +		if (any_mst)
> +			sleep(20);
> +
> +		j = 0;
> +		for_each_connected_output(display, output) {
> +			bool dsc_on = false;
> +
> +			/* For test_mode:MODE_SET, bitmap_disps =
> +			 * (1 << num_disps) - 1. All connected
> +			 * displays will be checked.
> +			 */
> +			if (!(bitmap_disps & (1 << j))) {
> +				j++;
> +				continue;
> +			}
> +
> +			if (data->pipe_crc_dprx[j]) {
> +				igt_pipe_crc_collect_crc(data->pipe_crc_dprx[j],
> +					&data->crc_dprx[j]);
> +				crc_str = igt_crc_to_string(&data->crc_dprx[j]);
> +				igt_info("pipe:%d %s dprx crc:%s\n",
> +					j, output->name, crc_str);
> +			} else
> +				igt_info("pipe:%d %s monitor dprx not available\n",
> +					j, output->name);
> +
> +			igt_pipe_crc_collect_crc(data->pipe_crc_otg[j],
> +				&data->crc_otg[j]);
> +			igt_fb_calc_crc((buf + j), &data->crc_fb[j]);
> +
> +			crc_str_1 = igt_crc_to_string(&data->crc_otg[j]);
> +			igt_info("pipe:%d %s otg crc:%s\n",
> +				j, output->name, crc_str_1);
> +
> +			crc_str_2 = igt_crc_to_string(&data->crc_fb[j]);
> +			igt_info("pipe:%d %s fb crc:%s\n",
> +				j, output->name, crc_str_2);
> +
> +			if (data->pipe_crc_dprx[j]) {
> +				ret = strcmp(crc_str, crc_str_1);
> +				dsc_on = igt_amd_read_dsc_clock_status(
> +						data->fd, output->name);
> +				if (ret != 0 && dsc_on)
> +					igt_info("pipe:%d %s otg crc != dprx crc due to dsc on\n",
> +					j, output->name);
> +
> +				igt_assert_f(((ret == 0) || (dsc_on)),
> +					"ERROR! OTG CRC != DPRX and DSC off\n");
> +			}
> +			j++;
> +		}
> +
> +		j = 0;
> +		for_each_connected_output(display, output) {
> +			igt_remove_fb(data->fd, (buf + j));
> +			j++;
> +		}
> +
> +		set_all_output_pipe_to_none(data);
> +	}
> +
> +	test_fini(data);
> +	free(buf);
> +}
> +
> +IGT_TEST_DESCRIPTION("Test multi-display mode set, display enable and disable");
> +igt_main
> +{
> +	struct data_t data;
> +
> +	igt_skip_on_simulation();
> +
> +	memset(&data, 0, sizeof(data));
> +
> +	igt_fixture
> +	{
> +		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_describe("Loop through all supported modes and check DP RX CRC and Pipe CRC");
> +	igt_subtest("multiple-display-mode-switch")
> +		multiple_display_test(&data, MODE_SET);
> +	igt_describe("Enable and Disable displays and check DP RX CRC and Pipe CRC");
> +	igt_subtest("multiple-display-enable-disable")
> +		multiple_display_test(&data, DISPLAY_ENABLE_DISABLE);
> +
> +
> +	igt_fixture
> +	{
> +		igt_display_fini(&data.display);
> +		drm_close_driver(data.fd);
> +	}
> +}
> diff --git a/tests/amdgpu/meson.build b/tests/amdgpu/meson.build
> index 5bd502496..af1d80b6b 100644
> --- a/tests/amdgpu/meson.build
> +++ b/tests/amdgpu/meson.build
> @@ -13,32 +13,33 @@ if libdrm_amdgpu.found()
>  			  'amd_deadlock',
>  			  'amd_dp_dsc',
>  			  'amd_freesync_video_mode',
> -			  'amd_hotplug',
>  			  'amd_gang_cs' ,
> +			  'amd_hotplug',
>  			  'amd_ilr',
>  			  'amd_info',
>  			  'amd_jpeg_dec',
>  			  'amd_link_settings',
> +			  'amd_mall',
>  			  'amd_max_bpc',
>  			  'amd_mem_leak',
>  			  'amd_mode_switch',
>  			  'amd_module_load',
> +			  'amd_multidisplay_modeset',


This is ok, rest of meson.build cleanup imho should go in
separate patch.

Regards,
Kamil

> +			  'amd_odm',
>  			  'amd_pci_unplug',
>  			  'amd_plane',
>  			  'amd_prime',
>  			  'amd_psr',
>  			  'amd_ras',
>  			  'amd_security',
> +			  'amd_subvp',
>  			  'amd_uvd_dec',
>  			  'amd_uvd_enc',
>  			  'amd_vce_dec',
>  			  'amd_vcn',
>  			  'amd_vm',
> -			  'amd_vrr_range',
> -			  'amd_mall',
> -			  'amd_odm',
> -			  'amd_subvp',
>  			  'amd_vpe',
> +			  'amd_vrr_range',
>  			]
>  	if libdrm_amdgpu.version().version_compare('> 2.4.97')
>  		amdgpu_progs +=[ 'amd_syncobj', ]
> -- 
> 2.25.1
> 


More information about the igt-dev mailing list