public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
* [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI
@ 2022-12-29 19:27 Mark Yacoub
  2022-12-29 21:16 ` [igt-dev] ✓ Fi.CI.BAT: success for testing headers in CI (rev2) Patchwork
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Mark Yacoub @ 2022-12-29 19:27 UTC (permalink / raw)
  To: igt-dev; +Cc: Mark Yacoub

From: Mark Yacoub <markyacoub@google.com>

---
 docs/chamelium.txt                            |    2 +-
 lib/igt_edid.h                                |    1 +
 lib/igt_eld.h                                 |    1 +
 lib/monitor_edids/monitor_edids_helper.c      |    2 +-
 tests/chamelium/kms_chamelium.c               | 3132 -----------------
 tests/chamelium/kms_chamelium_audio.c         |  858 +++++
 ...olor_chamelium.c => kms_chamelium_color.c} |    0
 tests/chamelium/kms_chamelium_edid.c          |  543 +++
 tests/chamelium/kms_chamelium_frames.c        | 1085 ++++++
 tests/chamelium/kms_chamelium_helper.c        |  330 ++
 tests/chamelium/kms_chamelium_helper.h        |   74 +
 tests/chamelium/kms_chamelium_hpd.c           |  512 +++
 tests/intel-ci/blacklist.txt                  |    2 +-
 tests/intel-ci/fast-feedback.testlist         |   18 +-
 tests/kms_color_helper.h                      |    2 +-
 tests/meson.build                             |   14 +-
 tests/vc4_ci/vc4-chamelium-fast.testlist      |   28 +-
 17 files changed, 3440 insertions(+), 3164 deletions(-)
 delete mode 100644 tests/chamelium/kms_chamelium.c
 create mode 100644 tests/chamelium/kms_chamelium_audio.c
 rename tests/chamelium/{kms_color_chamelium.c => kms_chamelium_color.c} (100%)
 create mode 100644 tests/chamelium/kms_chamelium_edid.c
 create mode 100644 tests/chamelium/kms_chamelium_frames.c
 create mode 100644 tests/chamelium/kms_chamelium_helper.c
 create mode 100644 tests/chamelium/kms_chamelium_helper.h
 create mode 100644 tests/chamelium/kms_chamelium_hpd.c

diff --git a/docs/chamelium.txt b/docs/chamelium.txt
index c4c22468..f82c8b0c 100644
--- a/docs/chamelium.txt
+++ b/docs/chamelium.txt
@@ -241,7 +241,7 @@ Current Support in IGT
 
 Support for the Chamelium platform in IGT is found in the following places:
 * lib/igt_chamelium.c: library with Chamelium-related helpers
-* tests/kms_chamelium.c: sub-tests using the Chamelium
+* tests/kms_chamelium_*.c: sub-tests using the Chamelium
 
 As of early April 2019, the following features are tested by IGT:
 * Pixel-by-pixel frame integrity tests for DP and HDMI
diff --git a/lib/igt_edid.h b/lib/igt_edid.h
index 477f16c2..85a9ef5e 100644
--- a/lib/igt_edid.h
+++ b/lib/igt_edid.h
@@ -29,6 +29,7 @@
 #include "config.h"
 
 #include <stdint.h>
+#include <stddef.h>
 
 #include <xf86drmMode.h>
 
diff --git a/lib/igt_eld.h b/lib/igt_eld.h
index 30d7012d..1a46b6d2 100644
--- a/lib/igt_eld.h
+++ b/lib/igt_eld.h
@@ -29,6 +29,7 @@
 #include "config.h"
 
 #include <stdbool.h>
+#include <stddef.h>
 
 #include "igt_edid.h"
 
diff --git a/lib/monitor_edids/monitor_edids_helper.c b/lib/monitor_edids/monitor_edids_helper.c
index 41f199bd..1cbf1c22 100644
--- a/lib/monitor_edids/monitor_edids_helper.c
+++ b/lib/monitor_edids/monitor_edids_helper.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: MIT
 /*
  * A helper library for parsing and making use of real EDID data from monitors
  * and make them compatible with IGT and Chamelium.
diff --git a/tests/chamelium/kms_chamelium.c b/tests/chamelium/kms_chamelium.c
deleted file mode 100644
index 3c4b4d75..00000000
--- a/tests/chamelium/kms_chamelium.c
+++ /dev/null
@@ -1,3132 +0,0 @@
-/*
- * Copyright © 2016 Red Hat 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 (including the next
- * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
- *
- * Authors:
- *    Lyude Paul <lyude@redhat.com>
- */
-
-#include "config.h"
-#include "igt.h"
-#include "igt_vc4.h"
-#include "igt_edid.h"
-#include "igt_eld.h"
-#include "igt_infoframe.h"
-#include "monitor_edids/dp_edids.h"
-#include "monitor_edids/hdmi_edids.h"
-#include "monitor_edids/monitor_edids_helper.h"
-
-#include <fcntl.h>
-#include <pthread.h>
-#include <string.h>
-#include <stdatomic.h>
-// #include <stdio.h>
-
-// struct chamelium_edid;
-
-enum test_modeset_mode {
-	TEST_MODESET_ON,
-	TEST_MODESET_ON_OFF,
-	TEST_MODESET_OFF,
-};
-
-typedef struct {
-	struct chamelium *chamelium;
-	struct chamelium_port **ports;
-	igt_display_t display;
-	int port_count;
-
-	int drm_fd;
-
-	struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT];
-} data_t;
-
-#define ONLINE_TIMEOUT 20 /* seconds */
-
-#define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */
-#define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */
-
-#define HPD_TOGGLE_COUNT_VGA 5
-#define HPD_TOGGLE_COUNT_DP_HDMI 15
-#define HPD_TOGGLE_COUNT_FAST 3
-
-static void
-get_connectors_link_status_failed(data_t *data, bool *link_status_failed)
-{
-	drmModeConnector *connector;
-	uint64_t link_status;
-	drmModePropertyPtr prop;
-	int p;
-
-	for (p = 0; p < data->port_count; p++) {
-		connector = chamelium_port_get_connector(data->chamelium,
-							 data->ports[p], false);
-
-		igt_assert(kmstest_get_property(data->drm_fd,
-						connector->connector_id,
-						DRM_MODE_OBJECT_CONNECTOR,
-						"link-status", NULL,
-						&link_status, &prop));
-
-		link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD;
-
-		drmModeFreeProperty(prop);
-		drmModeFreeConnector(connector);
-	}
-}
-
-/* Wait for hotplug and return the remaining time left from timeout */
-static bool wait_for_hotplug(struct udev_monitor *mon, int *timeout)
-{
-	struct timespec start, end;
-	int elapsed;
-	bool detected;
-
-	igt_assert_eq(igt_gettime(&start), 0);
-	detected = igt_hotplug_detected(mon, *timeout);
-	igt_assert_eq(igt_gettime(&end), 0);
-
-	elapsed = igt_time_elapsed(&start, &end);
-	igt_assert_lte(0, elapsed);
-	*timeout = max(0, *timeout - elapsed);
-
-	return detected;
-}
-
-static void
-wait_for_connector_after_hotplug(data_t *data, struct udev_monitor *mon,
-				 struct chamelium_port *port,
-				 drmModeConnection status)
-{
-	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
-	int hotplug_count = 0;
-
-	igt_debug("Waiting for %s to get %s after a hotplug event...\n",
-			  chamelium_port_get_name(port),
-			  kmstest_connector_status_str(status));
-
-	while (timeout > 0) {
-		if (!wait_for_hotplug(mon, &timeout))
-			break;
-
-		hotplug_count++;
-
-		if (chamelium_reprobe_connector(&data->display, data->chamelium,
-						port) == status)
-			return;
-	}
-
-	igt_assert_f(false, "Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n",
-			    chamelium_port_get_name(port),
-			    kmstest_connector_status_str(status),
-			    kmstest_connector_status_str(chamelium_reprobe_connector(&data->display, data->chamelium, port)), hotplug_count);
-}
-
-
-static int chamelium_vga_modes[][2] = {
-	{ 1600, 1200 },
-	{ 1920, 1200 },
-	{ 1920, 1080 },
-	{ 1680, 1050 },
-	{ 1280, 1024 },
-	{ 1280, 960 },
-	{ 1440, 900 },
-	{ 1280, 800 },
-	{ 1024, 768 },
-	{ 1360, 768 },
-	{ 1280, 720 },
-	{ 800, 600 },
-	{ 640, 480 },
-	{ -1, -1 },
-};
-
-static bool
-prune_vga_mode(data_t *data, drmModeModeInfo *mode)
-{
-	int i = 0;
-
-	while (chamelium_vga_modes[i][0] != -1) {
-		if (mode->hdisplay == chamelium_vga_modes[i][0] &&
-		    mode->vdisplay == chamelium_vga_modes[i][1])
-			return false;
-
-		i++;
-	}
-
-	return true;
-}
-
-static bool
-check_analog_bridge(data_t *data, struct chamelium_port *port)
-{
-	drmModePropertyBlobPtr edid_blob = NULL;
-	drmModeConnector *connector = chamelium_port_get_connector(
-	    data->chamelium, port, false);
-	uint64_t edid_blob_id;
-	const struct edid *edid;
-	char edid_vendor[3];
-
-	if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) {
-		drmModeFreeConnector(connector);
-		return false;
-	}
-
-	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
-					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
-					&edid_blob_id, NULL));
-	igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
-						      edid_blob_id));
-
-	edid = (const struct edid *) edid_blob->data;
-	edid_get_mfg(edid, edid_vendor);
-
-	drmModeFreePropertyBlob(edid_blob);
-	drmModeFreeConnector(connector);
-
-	/* Analog bridges provide their own EDID */
-	if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' ||
-	    edid_vendor[2] != 'T')
-		return true;
-
-	return false;
-}
-
-static void chamelium_paint_xr24_pattern(uint32_t *data,
-					 size_t width, size_t height,
-					 size_t stride, size_t block_size)
-{
-	uint32_t colors[] = { 0xff000000,
-			      0xffff0000,
-			      0xff00ff00,
-			      0xff0000ff,
-			      0xffffffff };
-	unsigned i, j;
-
-	for (i = 0; i < height; i++)
-		for (j = 0; j < width; j++)
-			*(data + i * stride / 4 + j) = colors[((j / block_size) + (i / block_size)) % 5];
-}
-
-static int chamelium_get_pattern_fb(data_t *data, size_t width, size_t height,
-				    uint32_t fourcc, size_t block_size,
-				    struct igt_fb *fb)
-{
-	int fb_id;
-	void *ptr;
-
-	igt_assert(fourcc == DRM_FORMAT_XRGB8888);
-
-	fb_id = igt_create_fb(data->drm_fd, width, height, fourcc,
-			      DRM_FORMAT_MOD_LINEAR, fb);
-	igt_assert(fb_id > 0);
-
-	ptr = igt_fb_map_buffer(fb->fd, fb);
-	igt_assert(ptr);
-
-	chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0],
-				     block_size);
-	igt_fb_unmap_buffer(fb, ptr);
-
-	return fb_id;
-}
-
-static void
-enable_output(data_t *data,
-	      struct chamelium_port *port,
-	      igt_output_t *output,
-	      drmModeModeInfo *mode,
-	      struct igt_fb *fb)
-{
-	igt_display_t *display = output->display;
-	igt_plane_t *primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-	drmModeConnector *connector = chamelium_port_get_connector(
-	    data->chamelium, port, false);
-
-	igt_assert(primary);
-
-	igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
-	igt_plane_set_fb(primary, fb);
-	igt_output_override_mode(output, mode);
-
-	/* Clear any color correction values that might be enabled */
-	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT))
-		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_DEGAMMA_LUT, NULL, 0);
-	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT))
-		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_GAMMA_LUT, NULL, 0);
-	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM))
-		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM, NULL, 0);
-
-	igt_display_commit2(display, COMMIT_ATOMIC);
-
-	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA)
-		usleep(250000);
-
-	drmModeFreeConnector(connector);
-}
-
-static enum pipe get_pipe_for_output(igt_display_t *display, igt_output_t *output)
-{
-	enum pipe pipe;
-
-	for_each_pipe(display, pipe) {
-		if (igt_pipe_connector_valid(pipe, output)) {
-			return pipe;
-		}
-	}
-
-	igt_assert_f(false, "No pipe found for output %s\n",
-		     igt_output_name(output));
-}
-
-static void create_fb_for_mode(data_t *data, struct igt_fb *fb, drmModeModeInfo *mode)
-{
-	int fb_id;
-
-	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
-					 DRM_FORMAT_XRGB8888, 64, fb);
-
-	igt_assert(fb_id > 0);
-}
-
-static drmModeModeInfo get_mode_for_port(struct chamelium *chamelium,
-					 struct chamelium_port *port)
-{
-	drmModeConnector *connector = chamelium_port_get_connector(chamelium,
-								   port, false);
-	drmModeModeInfo mode;
-	igt_assert(&connector->modes[0] != NULL);
-	memcpy(&mode, &connector->modes[0], sizeof(mode));
-	drmModeFreeConnector(connector);
-	return mode;
-}
-
-static igt_output_t *get_output_for_port(data_t *data,
-					 struct chamelium_port *port)
-{
-	drmModeConnector *connector =
-		chamelium_port_get_connector(data->chamelium, port, true);
-	igt_output_t *output = igt_output_from_connector(&data->display,
-							 connector);
-	drmModeFreeConnector(connector);
-	igt_assert(output != NULL);
-	return output;
-}
-
-static const char test_hotplug_for_each_pipe_desc[] =
-	"Check that we get uevents and updated connector status on "
-	"hotplug and unplug for each pipe with valid output";
-static void
-test_hotplug_for_each_pipe(data_t *data, struct chamelium_port *port)
-{
-	igt_output_t *output;
-	enum pipe pipe;
-	struct udev_monitor *mon = igt_watch_uevents();
-
-	chamelium_reset_state(&data->display,
-			      data->chamelium,
-			      port,
-			      data->ports,
-			      data->port_count);
-
-	igt_hpd_storm_set_threshold(data->drm_fd, 0);
-	/* Disconnect if any port got connected */
-	chamelium_unplug(data->chamelium, port);
-	wait_for_connector_after_hotplug(data, mon, port,
-			DRM_MODE_DISCONNECTED);
-
-	for_each_pipe(&data->display, pipe) {
-		igt_flush_uevents(mon);
-		/* Check if we get a sysfs hotplug event */
-		chamelium_plug(data->chamelium, port);
-		wait_for_connector_after_hotplug(data, mon, port,
-				DRM_MODE_CONNECTED);
-		igt_flush_uevents(mon);
-		output = get_output_for_port(data, port);
-
-		/* If pipe is valid for output then set it */
-		if (igt_pipe_connector_valid(pipe, output)) {
-			igt_output_set_pipe(output, pipe);
-			igt_display_commit2(&data->display, COMMIT_ATOMIC);
-		}
-
-		chamelium_unplug(data->chamelium, port);
-		wait_for_connector_after_hotplug(data, mon, port,
-				DRM_MODE_DISCONNECTED);
-		igt_flush_uevents(mon);
-	}
-
-	igt_cleanup_uevents(mon);
-	igt_hpd_storm_reset(data->drm_fd);
-}
-
-static const char test_basic_hotplug_desc[] =
-	"Check that we get uevents and updated connector status on "
-	"hotplug and unplug";
-static void
-test_hotplug(data_t *data, struct chamelium_port *port, int toggle_count,
-	     enum test_modeset_mode modeset_mode)
-{
-	int i;
-	enum pipe pipe;
-	struct igt_fb fb = {0};
-	drmModeModeInfo mode;
-	struct udev_monitor *mon = igt_watch_uevents();
-	igt_output_t *output = get_output_for_port(data, port);
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium, NULL,
-			      data->ports, data->port_count);
-
-
-	igt_hpd_storm_set_threshold(data->drm_fd, 0);
-
-	for (i = 0; i < toggle_count; i++) {
-		igt_flush_uevents(mon);
-
-		/* Check if we get a sysfs hotplug event */
-		chamelium_plug(data->chamelium, port);
-
-		wait_for_connector_after_hotplug(data, mon, port,
-						 DRM_MODE_CONNECTED);
-		igt_flush_uevents(mon);
-
-		if (modeset_mode == TEST_MODESET_ON_OFF ||
-		    (modeset_mode == TEST_MODESET_ON && i == 0 )) {
-			if (i == 0) {
-				/* We can only get mode and pipe once we are connected */
-				output = get_output_for_port(data, port);
-				pipe = get_pipe_for_output(&data->display, output);
-				mode = get_mode_for_port(data->chamelium, port);
-				create_fb_for_mode(data, &fb, &mode);
-			}
-
-			igt_output_set_pipe(output, pipe);
-			enable_output(data, port, output, &mode, &fb);
-		}
-
-		/* Now check if we get a hotplug from disconnection */
-		chamelium_unplug(data->chamelium, port);
-
-		wait_for_connector_after_hotplug(data, mon, port,
-						 DRM_MODE_DISCONNECTED);
-
-		igt_flush_uevents(mon);
-
-		if (modeset_mode == TEST_MODESET_ON_OFF) {
-			igt_output_set_pipe(output, PIPE_NONE);
-			igt_display_commit2(&data->display, COMMIT_ATOMIC);
-		}
-	}
-
-	igt_cleanup_uevents(mon);
-	igt_hpd_storm_reset(data->drm_fd);
-	igt_remove_fb(data->drm_fd, &fb);
-}
-
-static void set_edid(data_t *data, struct chamelium_port *port,
-		     enum igt_custom_edid_type edid)
-{
-	chamelium_port_set_edid(data->chamelium, port, data->edids[edid]);
-}
-
-static const char igt_custom_edid_type_read_desc[] =
-	"Make sure the EDID exposed by KMS is the same as the screen's";
-static void
-igt_custom_edid_type_read(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid)
-{
-	drmModePropertyBlobPtr edid_blob = NULL;
-	drmModeConnector *connector;
-	size_t raw_edid_size;
-	const struct edid *raw_edid;
-	uint64_t edid_blob_id;
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	set_edid(data, port, edid);
-	chamelium_plug(data->chamelium, port);
-	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
-					      port, DRM_MODE_CONNECTED);
-
-	igt_skip_on(check_analog_bridge(data, port));
-
-	connector = chamelium_port_get_connector(data->chamelium, port, true);
-	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
-					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
-					&edid_blob_id, NULL));
-	igt_assert(edid_blob_id != 0);
-	igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
-						      edid_blob_id));
-
-	raw_edid = chamelium_edid_get_raw(data->edids[edid], port);
-	raw_edid_size = edid_get_size(raw_edid);
-	igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0);
-
-	drmModeFreePropertyBlob(edid_blob);
-	drmModeFreeConnector(connector);
-}
-
-static void
-try_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
-		       enum igt_suspend_state state, enum igt_suspend_test test,
-		       struct udev_monitor *mon, bool connected)
-{
-	drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED :
-						     DRM_MODE_CONNECTED;
-	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
-	int delay;
-	int p;
-
-	igt_flush_uevents(mon);
-
-	delay = igt_get_autoresume_delay(state) * 1000 / 2;
-
-	if (port) {
-		chamelium_schedule_hpd_toggle(data->chamelium, port, delay,
-					      !connected);
-	} else {
-		for (p = 0; p < data->port_count; p++) {
-			port = data->ports[p];
-			chamelium_schedule_hpd_toggle(data->chamelium, port,
-						      delay, !connected);
-		}
-
-		port = NULL;
-	}
-
-	igt_system_suspend_autoresume(state, test);
-	igt_assert(wait_for_hotplug(mon, &timeout));
-	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
-
-	if (port) {
-		igt_assert_eq(chamelium_reprobe_connector(&data->display,
-							  data->chamelium,
-							  port),
-							  target_state);
-	} else {
-		for (p = 0; p < data->port_count; p++) {
-			drmModeConnection current_state;
-
-			port = data->ports[p];
-			/*
-			 * There could be as many hotplug events sent by
-			 * driver as connectors we scheduled an HPD toggle on
-			 * above, depending on timing. So if we're not seeing
-			 * the expected connector state try to wait for an HPD
-			 * event for each connector/port.
-			 */
-			current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port);
-			if (p > 0 && current_state != target_state) {
-				igt_assert(wait_for_hotplug(mon, &timeout));
-				current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port);
-			}
-
-			igt_assert_eq(current_state, target_state);
-		}
-
-		port = NULL;
-	}
-}
-
-static const char test_suspend_resume_hpd_desc[] =
-	"Toggle HPD during suspend, check that uevents are sent and connector "
-	"status is updated";
-static void
-test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
-			enum igt_suspend_state state,
-			enum igt_suspend_test test)
-{
-	struct udev_monitor *mon = igt_watch_uevents();
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	/* Make sure we notice new connectors after resuming */
-	try_suspend_resume_hpd(data, port, state, test, mon, false);
-
-	/* Now make sure we notice disconnected connectors after resuming */
-	try_suspend_resume_hpd(data, port, state, test, mon, true);
-
-	igt_cleanup_uevents(mon);
-}
-
-static const char test_suspend_resume_hpd_common_desc[] =
-	"Toggle HPD during suspend on all connectors, check that uevents are "
-	"sent and connector status is updated";
-static void
-test_suspend_resume_hpd_common(data_t *data, enum igt_suspend_state state,
-			       enum igt_suspend_test test)
-{
-	struct udev_monitor *mon = igt_watch_uevents();
-	struct chamelium_port *port;
-	int p;
-
-	for (p = 0; p < data->port_count; p++) {
-		port = data->ports[p];
-		igt_debug("Testing port %s\n", chamelium_port_get_name(port));
-	}
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium, NULL,
-			      data->ports, data->port_count);
-
-	/* Make sure we notice new connectors after resuming */
-	try_suspend_resume_hpd(data, NULL, state, test, mon, false);
-
-	/* Now make sure we notice disconnected connectors after resuming */
-	try_suspend_resume_hpd(data, NULL, state, test, mon, true);
-
-	igt_cleanup_uevents(mon);
-}
-
-static const char test_suspend_resume_edid_change_desc[] =
-	"Simulate a screen being unplugged and another screen being plugged "
-	"during suspend, check that a uevent is sent and connector status is "
-	"updated";
-static void
-test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
-				enum igt_suspend_state state,
-				enum igt_suspend_test test,
-				enum igt_custom_edid_type edid,
-				enum igt_custom_edid_type alt_edid)
-{
-	struct udev_monitor *mon = igt_watch_uevents();
-	bool link_status_failed[2][data->port_count];
-	int p;
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	/* Catch the event and flush all remaining ones. */
-	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
-	igt_flush_uevents(mon);
-
-	/* First plug in the port */
-	set_edid(data, port, edid);
-	chamelium_plug(data->chamelium, port);
-	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
-
-	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
-					      port, DRM_MODE_CONNECTED);
-
-	/*
-	 * Change the edid before we suspend. On resume, the machine should
-	 * notice the EDID change and fire a hotplug event.
-	 */
-	set_edid(data, port, alt_edid);
-
-	get_connectors_link_status_failed(data, link_status_failed[0]);
-
-	igt_flush_uevents(mon);
-
-	igt_system_suspend_autoresume(state, test);
-	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
-	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
-
-	get_connectors_link_status_failed(data, link_status_failed[1]);
-
-	for (p = 0; p < data->port_count; p++)
-		igt_skip_on(!link_status_failed[0][p] && link_status_failed[1][p]);
-}
-
-static igt_output_t *
-prepare_output(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid)
-{
-	igt_display_t *display = &data->display;
-	igt_output_t *output;
-	enum pipe pipe;
-
-	/* The chamelium's default EDID has a lot of resolutions, way more then
-	 * we need to test. Additionally the default EDID doesn't support HDMI
-	 * audio.
-	 */
-	set_edid(data, port, edid);
-
-	chamelium_plug(data->chamelium, port);
-	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
-					      port, DRM_MODE_CONNECTED);
-
-	igt_display_reset(display);
-
-	output = get_output_for_port(data, port);
-
-	/* Refresh pipe to update connected status */
-	igt_output_set_pipe(output, PIPE_NONE);
-
-	pipe = get_pipe_for_output(display, output);
-	igt_output_set_pipe(output, pipe);
-
-	return output;
-}
-
-static void do_test_display(data_t *data, struct chamelium_port *port,
-			    igt_output_t *output, drmModeModeInfo *mode,
-			    uint32_t fourcc, enum chamelium_check check,
-			    int count)
-{
-	struct chamelium_fb_crc_async_data *fb_crc;
-	struct igt_fb frame_fb, fb;
-	int i, fb_id, captured_frame_count;
-	int frame_id;
-
-	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
-					 DRM_FORMAT_XRGB8888, 64, &fb);
-	igt_assert(fb_id > 0);
-
-	frame_id = igt_fb_convert(&frame_fb, &fb, fourcc,
-				  DRM_FORMAT_MOD_LINEAR);
-	igt_assert(frame_id > 0);
-
-	if (check == CHAMELIUM_CHECK_CRC)
-		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
-								&fb);
-
-	enable_output(data, port, output, mode, &frame_fb);
-
-	if (check == CHAMELIUM_CHECK_CRC) {
-		igt_crc_t *expected_crc;
-		igt_crc_t *crc;
-
-		/* We want to keep the display running for a little bit, since
-		 * there's always the potential the driver isn't able to keep
-		 * the display running properly for very long
-		 */
-		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count);
-		crc = chamelium_read_captured_crcs(data->chamelium,
-						   &captured_frame_count);
-
-		igt_assert(captured_frame_count == count);
-
-		igt_debug("Captured %d frames\n", captured_frame_count);
-
-		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
-
-		for (i = 0; i < captured_frame_count; i++)
-			chamelium_assert_crc_eq_or_dump(data->chamelium,
-							expected_crc, &crc[i],
-							&fb, i);
-
-		free(expected_crc);
-		free(crc);
-	} else if (check == CHAMELIUM_CHECK_ANALOG ||
-		   check == CHAMELIUM_CHECK_CHECKERBOARD) {
-		struct chamelium_frame_dump *dump;
-
-		igt_assert(count == 1);
-
-		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
-						  0, 0);
-
-		if (check == CHAMELIUM_CHECK_ANALOG)
-			chamelium_crop_analog_frame(dump, mode->hdisplay,
-						    mode->vdisplay);
-
-		chamelium_assert_frame_match_or_dump(data->chamelium, port,
-						     dump, &fb, check);
-		chamelium_destroy_frame_dump(dump);
-	}
-
-	igt_remove_fb(data->drm_fd, &frame_fb);
-	igt_remove_fb(data->drm_fd, &fb);
-}
-
-static const char test_display_one_mode_desc[] =
-	"Pick the first mode of the IGT base EDID, display and capture a few "
-	"frames, then check captured frames are correct";
-static void test_display_one_mode(data_t *data, struct chamelium_port *port,
-				  uint32_t fourcc, enum chamelium_check check,
-				  int count)
-{
-	drmModeConnector *connector;
-	drmModeModeInfo *mode;
-	igt_output_t *output;
-	igt_plane_t *primary;
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
-	connector = chamelium_port_get_connector(data->chamelium, port, false);
-	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-	igt_assert(primary);
-
-	igt_require(igt_plane_has_format_mod(primary, fourcc, DRM_FORMAT_MOD_LINEAR));
-
-	mode = &connector->modes[0];
-	if (check == CHAMELIUM_CHECK_ANALOG) {
-		bool bridge = check_analog_bridge(data, port);
-
-		igt_assert(!(bridge && prune_vga_mode(data, mode)));
-	}
-
-	do_test_display(data, port, output, mode, fourcc, check, count);
-
-	drmModeFreeConnector(connector);
-}
-
-static const char test_display_all_modes_desc[] =
-	"For each mode of the IGT base EDID, display and capture a few "
-	"frames, then check captured frames are correct";
-static void test_display_all_modes(data_t *data, struct chamelium_port *port,
-				   uint32_t fourcc, enum chamelium_check check,
-				   int count)
-{
-	bool bridge;
-	int i, count_modes;
-
-	if (check == CHAMELIUM_CHECK_ANALOG)
-		bridge = check_analog_bridge(data, port);
-
-	i = 0;
-	do {
-		igt_output_t *output;
-		igt_plane_t *primary;
-		drmModeConnector *connector;
-		drmModeModeInfo *mode;
-
-		/*
-		 * let's reset state each mode so we will get the
-		 * HPD pulses realibably
-		 */
-		igt_modeset_disable_all_outputs(&data->display);
-		chamelium_reset_state(&data->display, data->chamelium,
-				      port, data->ports, data->port_count);
-
-		/*
-		 * modes may change due to mode pruining and link issues, so we
-		 * need to refresh the connector
-		 */
-		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
-		connector = chamelium_port_get_connector(data->chamelium, port,
-							 false);
-		primary = igt_output_get_plane_type(output,
-						    DRM_PLANE_TYPE_PRIMARY);
-		igt_assert(primary);
-		igt_require(igt_plane_has_format_mod(primary, fourcc,
-			    DRM_FORMAT_MOD_LINEAR));
-
-		/* we may skip some modes due to above but that's ok */
-		count_modes = connector->count_modes;
-		if (i >= count_modes)
-			break;
-
-		mode = &connector->modes[i];
-
-		if (check == CHAMELIUM_CHECK_ANALOG && bridge &&
-		    prune_vga_mode(data, mode))
-			continue;
-
-		do_test_display(data, port, output, mode, fourcc, check,
-				count);
-		drmModeFreeConnector(connector);
-	} while (++i < count_modes);
-}
-
-static const char test_display_frame_dump_desc[] =
-	"For each mode of the IGT base EDID, display and capture a few "
-	"frames, then download the captured frames and compare them "
-	"bit-by-bit to the sent ones";
-static void
-test_display_frame_dump(data_t *data, struct chamelium_port *port)
-{
-
-	int i, count_modes;
-
-	i = 0;
-	do {
-		igt_output_t *output;
-		igt_plane_t *primary;
-		struct igt_fb fb;
-		struct chamelium_frame_dump *frame;
-		drmModeModeInfo *mode;
-		drmModeConnector *connector;
-		int fb_id, j;
-
-		/*
-		 * let's reset state each mode so we will get the
-		 * HPD pulses realibably
-		 */
-		igt_modeset_disable_all_outputs(&data->display);
-		chamelium_reset_state(&data->display, data->chamelium,
-				      port, data->ports, data->port_count);
-
-		/*
-		 * modes may change due to mode pruining and link issues, so we
-		 * need to refresh the connector
-		 */
-		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
-		connector = chamelium_port_get_connector(data->chamelium, port,
-							 false);
-		primary = igt_output_get_plane_type(output,
-						    DRM_PLANE_TYPE_PRIMARY);
-		igt_assert(primary);
-
-		/* we may skip some modes due to above but that's ok */
-		count_modes = connector->count_modes;
-		if (i >= count_modes)
-			break;
-
-		mode = &connector->modes[i];
-
-		fb_id = igt_create_color_pattern_fb(data->drm_fd,
-						    mode->hdisplay, mode->vdisplay,
-						    DRM_FORMAT_XRGB8888,
-						    DRM_FORMAT_MOD_LINEAR,
-						    0, 0, 0, &fb);
-		igt_assert(fb_id > 0);
-
-		enable_output(data, port, output, mode, &fb);
-
-		igt_debug("Reading frame dumps from Chamelium...\n");
-		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
-		for (j = 0; j < 5; j++) {
-			frame = chamelium_read_captured_frame(data->chamelium,
-							      j);
-			chamelium_assert_frame_eq(data->chamelium, frame, &fb);
-			chamelium_destroy_frame_dump(frame);
-		}
-
-		igt_remove_fb(data->drm_fd, &fb);
-		drmModeFreeConnector(connector);
-	} while (++i < count_modes);
-}
-
-#define MODE_CLOCK_ACCURACY 0.05 /* 5% */
-
-static void check_mode(struct chamelium *chamelium, struct chamelium_port *port,
-		       drmModeModeInfo *mode)
-{
-	struct chamelium_video_params video_params = {0};
-	double mode_clock;
-	int mode_hsync_offset, mode_vsync_offset;
-	int mode_hsync_width, mode_vsync_width;
-	int mode_hsync_polarity, mode_vsync_polarity;
-
-	chamelium_port_get_video_params(chamelium, port, &video_params);
-
-	mode_clock = (double) mode->clock / 1000;
-
-	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) {
-		/* this is what chamelium understands as offsets for DP */
-		mode_hsync_offset = mode->htotal - mode->hsync_start;
-		mode_vsync_offset = mode->vtotal - mode->vsync_start;
-	} else {
-		/* and this is what they are for other connectors */
-		mode_hsync_offset = mode->hsync_start - mode->hdisplay;
-		mode_vsync_offset = mode->vsync_start - mode->vdisplay;
-	}
-
-	mode_hsync_width = mode->hsync_end - mode->hsync_start;
-	mode_vsync_width = mode->vsync_end - mode->vsync_start;
-
-	mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
-	mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
-
-	igt_debug("Checking video mode:\n");
-	igt_debug("clock: got %f, expected %f ± %f%%\n",
-		  video_params.clock, mode_clock, MODE_CLOCK_ACCURACY * 100);
-	igt_debug("hactive: got %d, expected %d\n",
-		  video_params.hactive, mode->hdisplay);
-	igt_debug("vactive: got %d, expected %d\n",
-		  video_params.vactive, mode->vdisplay);
-	igt_debug("hsync_offset: got %d, expected %d\n",
-		  video_params.hsync_offset, mode_hsync_offset);
-	igt_debug("vsync_offset: got %d, expected %d\n",
-		  video_params.vsync_offset, mode_vsync_offset);
-	igt_debug("htotal: got %d, expected %d\n",
-		  video_params.htotal, mode->htotal);
-	igt_debug("vtotal: got %d, expected %d\n",
-		  video_params.vtotal, mode->vtotal);
-	igt_debug("hsync_width: got %d, expected %d\n",
-		  video_params.hsync_width, mode_hsync_width);
-	igt_debug("vsync_width: got %d, expected %d\n",
-		  video_params.vsync_width, mode_vsync_width);
-	igt_debug("hsync_polarity: got %d, expected %d\n",
-		  video_params.hsync_polarity, mode_hsync_polarity);
-	igt_debug("vsync_polarity: got %d, expected %d\n",
-		  video_params.vsync_polarity, mode_vsync_polarity);
-
-	if (!isnan(video_params.clock)) {
-		igt_assert(video_params.clock >
-			   mode_clock * (1 - MODE_CLOCK_ACCURACY));
-		igt_assert(video_params.clock <
-			   mode_clock * (1 + MODE_CLOCK_ACCURACY));
-	}
-	igt_assert(video_params.hactive == mode->hdisplay);
-	igt_assert(video_params.vactive == mode->vdisplay);
-	igt_assert(video_params.hsync_offset == mode_hsync_offset);
-	igt_assert(video_params.vsync_offset == mode_vsync_offset);
-	igt_assert(video_params.htotal == mode->htotal);
-	igt_assert(video_params.vtotal == mode->vtotal);
-	igt_assert(video_params.hsync_width == mode_hsync_width);
-	igt_assert(video_params.vsync_width == mode_vsync_width);
-	igt_assert(video_params.hsync_polarity == mode_hsync_polarity);
-	igt_assert(video_params.vsync_polarity == mode_vsync_polarity);
-}
-
-static const char test_mode_timings_desc[] =
-	"For each mode of the IGT base EDID, perform a modeset and check the "
-	"mode detected by the Chamelium receiver matches the mode we set";
-static void test_mode_timings(data_t *data, struct chamelium_port *port)
-{
-	int i, count_modes;
-
-	i = 0;
-	igt_require(chamelium_supports_get_video_params(data->chamelium));
-	do {
-		igt_output_t *output;
-		igt_plane_t *primary;
-		drmModeConnector *connector;
-		drmModeModeInfo *mode;
-		int fb_id;
-		struct igt_fb fb;
-
-		/*
-		 * let's reset state each mode so we will get the
-		 * HPD pulses realibably
-		 */
-		igt_modeset_disable_all_outputs(&data->display);
-		chamelium_reset_state(&data->display, data->chamelium,
-				      port, data->ports, data->port_count);
-
-		/*
-		 * modes may change due to mode pruining and link issues, so we
-		 * need to refresh the connector
-		 */
-		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
-		connector = chamelium_port_get_connector(data->chamelium, port, false);
-		primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-		igt_assert(primary);
-
-		/* we may skip some modes due to above but that's ok */
-		count_modes = connector->count_modes;
-		if (i >= count_modes)
-			break;
-
-		mode = &connector->modes[i];
-
-		fb_id = igt_create_color_pattern_fb(data->drm_fd,
-						    mode->hdisplay, mode->vdisplay,
-						    DRM_FORMAT_XRGB8888,
-						    DRM_FORMAT_MOD_LINEAR,
-						    0, 0, 0, &fb);
-		igt_assert(fb_id > 0);
-
-		enable_output(data, port, output, mode, &fb);
-
-		/* Trigger the FSM */
-		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0);
-
-		check_mode(data->chamelium, port, mode);
-
-		igt_remove_fb(data->drm_fd, &fb);
-		drmModeFreeConnector(connector);
-	} while (++i < count_modes);
-}
-
-struct vic_mode {
-	int hactive, vactive;
-	int vrefresh; /* Hz */
-	uint32_t picture_ar;
-};
-
-/* Maps Video Identification Codes to a mode */
-static const struct vic_mode vic_modes[] = {
-	[16] = {
-		.hactive = 1920,
-		.vactive = 1080,
-		.vrefresh = 60,
-		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
-	},
-};
-
-/* Maps aspect ratios to their mode flag */
-static const uint32_t mode_ar_flags[] = {
-	[DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9,
-};
-
-static enum infoframe_avi_picture_aspect_ratio
-get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
-{
-	/* The AVI picture aspect ratio field only supports 4:3 and 16:9 */
-	switch (aspect_ratio) {
-	case DRM_MODE_PICTURE_ASPECT_4_3:
-		return INFOFRAME_AVI_PIC_AR_4_3;
-	case DRM_MODE_PICTURE_ASPECT_16_9:
-		return INFOFRAME_AVI_PIC_AR_16_9;
-	default:
-		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
-	}
-}
-
-static bool vic_mode_matches_drm(const struct vic_mode *vic_mode,
-				 drmModeModeInfo *drm_mode)
-{
-	uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar];
-
-	return vic_mode->hactive == drm_mode->hdisplay &&
-	       vic_mode->vactive == drm_mode->vdisplay &&
-	       vic_mode->vrefresh == drm_mode->vrefresh &&
-	       ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
-}
-
-static const char test_display_aspect_ratio_desc[] =
-	"Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and "
-	"check they include the relevant fields";
-static void test_display_aspect_ratio(data_t *data, struct chamelium_port *port)
-{
-	igt_output_t *output;
-	igt_plane_t *primary;
-	drmModeConnector *connector;
-	drmModeModeInfo *mode;
-	int fb_id, i;
-	struct igt_fb fb;
-	bool found, ok;
-	struct chamelium_infoframe *infoframe;
-	struct infoframe_avi infoframe_avi;
-	uint8_t vic = 16; /* TODO: test more VICs */
-	const struct vic_mode *vic_mode;
-	uint32_t aspect_ratio;
-	enum infoframe_avi_picture_aspect_ratio frame_ar;
-
-	igt_require(chamelium_supports_get_last_infoframe(data->chamelium));
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	output = prepare_output(data, port, IGT_CUSTOM_EDID_ASPECT_RATIO);
-	connector = chamelium_port_get_connector(data->chamelium, port, false);
-	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-	igt_assert(primary);
-
-	vic_mode = &vic_modes[vic];
-	aspect_ratio = vic_mode->picture_ar;
-
-	found = false;
-	igt_assert(connector->count_modes > 0);
-	for (i = 0; i < connector->count_modes; i++) {
-		mode = &connector->modes[i];
-
-		if (vic_mode_matches_drm(vic_mode, mode)) {
-			found = true;
-			break;
-		}
-	}
-	igt_assert_f(found,
-		     "Failed to find mode with the correct aspect ratio\n");
-
-	fb_id = igt_create_color_pattern_fb(data->drm_fd,
-					    mode->hdisplay, mode->vdisplay,
-					    DRM_FORMAT_XRGB8888,
-					    DRM_FORMAT_MOD_LINEAR,
-					    0, 0, 0, &fb);
-	igt_assert(fb_id > 0);
-
-	enable_output(data, port, output, mode, &fb);
-
-	infoframe = chamelium_get_last_infoframe(data->chamelium, port,
-						 CHAMELIUM_INFOFRAME_AVI);
-	igt_assert_f(infoframe, "AVI InfoFrame not received\n");
-
-	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
-				 infoframe->payload, infoframe->payload_size);
-	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
-
-	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
-
-	igt_debug("Checking AVI InfoFrame\n");
-	igt_debug("Picture aspect ratio: got %d, expected %d\n",
-		  infoframe_avi.picture_aspect_ratio, frame_ar);
-	igt_debug("Video Identification Code (VIC): got %d, expected %d\n",
-		  infoframe_avi.vic, vic);
-
-	igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar);
-	igt_assert(infoframe_avi.vic == vic);
-
-	chamelium_infoframe_destroy(infoframe);
-	igt_remove_fb(data->drm_fd, &fb);
-	drmModeFreeConnector(connector);
-}
-
-
-/* Playback parameters control the audio signal we synthesize and send */
-#define PLAYBACK_CHANNELS 2
-#define PLAYBACK_SAMPLES 1024
-
-/* Capture paremeters control the audio signal we receive */
-#define CAPTURE_SAMPLES 2048
-
-#define AUDIO_TIMEOUT 2000 /* ms */
-/* A streak of 3 gives confidence that the signal is good. */
-#define MIN_STREAK 3
-
-#define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */
-#define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */
-#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */
-
-/* TODO: enable >48KHz rates, these are not reliable */
-static int test_sampling_rates[] = {
-	32000,
-	44100,
-	48000,
-	/* 88200, */
-	/* 96000, */
-	/* 176400, */
-	/* 192000, */
-};
-
-static int test_sampling_rates_count = sizeof(test_sampling_rates) / sizeof(int);
-
-/* Test frequencies (Hz): a sine signal will be generated for each.
- *
- * Depending on the sampling rate chosen, it might not be possible to properly
- * detect the generated sine (see Nyquist–Shannon sampling theorem).
- * Frequencies that can't be reliably detected will be automatically pruned in
- * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be
- * tested with a 192KHz sampling rate.
- */
-static int test_frequencies[] = {
-	300,
-	600,
-	1200,
-	10000,
-	80000,
-};
-
-static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
-
-static const snd_pcm_format_t test_formats[] = {
-	SND_PCM_FORMAT_S16_LE,
-	SND_PCM_FORMAT_S24_LE,
-	SND_PCM_FORMAT_S32_LE,
-};
-
-static const size_t test_formats_count = sizeof(test_formats) / sizeof(test_formats[0]);
-
-struct audio_state {
-	struct alsa *alsa;
-	struct chamelium *chamelium;
-	struct chamelium_port *port;
-	struct chamelium_stream *stream;
-
-	/* The capture format is only available after capture has started. */
-	struct {
-		snd_pcm_format_t format;
-		int channels;
-		int rate;
-	} playback, capture;
-
-	char *name;
-	struct audio_signal *signal; /* for frequencies test only */
-	int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS];
-
-	size_t recv_pages;
-	int msec;
-
-	int dump_fd;
-	char *dump_path;
-
-	pthread_t thread;
-	atomic_bool run;
-	atomic_bool positive; /* for pulse test only */
-};
-
-static void audio_state_init(struct audio_state *state, data_t *data,
-			     struct alsa *alsa, struct chamelium_port *port,
-			     snd_pcm_format_t format, int channels, int rate)
-{
-	memset(state, 0, sizeof(*state));
-	state->dump_fd = -1;
-
-	state->alsa = alsa;
-	state->chamelium = data->chamelium;
-	state->port = port;
-
-	state->playback.format = format;
-	state->playback.channels = channels;
-	state->playback.rate = rate;
-
-	alsa_configure_output(alsa, format, channels, rate);
-
-	state->stream = chamelium_stream_init();
-	igt_assert_f(state->stream,
-		     "Failed to initialize Chamelium stream client\n");
-}
-
-static void audio_state_fini(struct audio_state *state)
-{
-	chamelium_stream_deinit(state->stream);
-	free(state->name);
-}
-
-static void *run_audio_thread(void *data)
-{
-	struct alsa *alsa = data;
-
-	alsa_run(alsa, -1);
-	return NULL;
-}
-
-static void audio_state_start(struct audio_state *state, const char *name)
-{
-	int ret;
-	bool ok;
-	size_t i, j;
-	enum chamelium_stream_realtime_mode stream_mode;
-	char dump_suffix[64];
-
-	free(state->name);
-	state->name = strdup(name);
-	state->recv_pages = 0;
-	state->msec = 0;
-
-	igt_debug("Starting %s test with playback format %s, "
-		  "sampling rate %d Hz and %d channels\n",
-		  name, snd_pcm_format_name(state->playback.format),
-		  state->playback.rate, state->playback.channels);
-
-	chamelium_start_capturing_audio(state->chamelium, state->port, false);
-
-	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
-	ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode);
-	igt_assert_f(ok, "Failed to start streaming audio capture\n");
-
-	/* Start playing audio */
-	state->run = true;
-	ret = pthread_create(&state->thread, NULL,
-			     run_audio_thread, state->alsa);
-	igt_assert_f(ret == 0, "Failed to start audio playback thread\n");
-
-	/* The Chamelium device only supports this PCM format. */
-	state->capture.format = SND_PCM_FORMAT_S32_LE;
-
-	/* Only after we've started playing audio, we can retrieve the capture
-	 * format used by the Chamelium device. */
-	chamelium_get_audio_format(state->chamelium, state->port,
-				   &state->capture.rate,
-				   &state->capture.channels);
-	if (state->capture.rate == 0) {
-		igt_debug("Audio receiver doesn't indicate the capture "
-			 "sampling rate, assuming it's %d Hz\n",
-			 state->playback.rate);
-		state->capture.rate = state->playback.rate;
-	}
-
-	chamelium_get_audio_channel_mapping(state->chamelium, state->port,
-					    state->channel_mapping);
-	/* Make sure we can capture all channels we send. */
-	for (i = 0; i < state->playback.channels; i++) {
-		ok = false;
-		for (j = 0; j < state->capture.channels; j++) {
-			if (state->channel_mapping[j] == i) {
-				ok = true;
-				break;
-			}
-		}
-		igt_assert_f(ok, "Cannot capture all channels\n");
-	}
-
-	if (igt_frame_dump_is_enabled()) {
-		snprintf(dump_suffix, sizeof(dump_suffix),
-			 "capture-%s-%s-%dch-%dHz",
-			 name, snd_pcm_format_name(state->playback.format),
-			 state->playback.channels, state->playback.rate);
-
-		state->dump_fd = audio_create_wav_file_s32_le(dump_suffix,
-							      state->capture.rate,
-							      state->capture.channels,
-							      &state->dump_path);
-		igt_assert_f(state->dump_fd >= 0,
-			     "Failed to create audio dump file\n");
-	}
-}
-
-static void audio_state_receive(struct audio_state *state,
-				int32_t **recv, size_t *recv_len)
-{
-	bool ok;
-	size_t page_count;
-	size_t recv_size;
-
-	ok = chamelium_stream_receive_realtime_audio(state->stream,
-						     &page_count,
-						     recv, recv_len);
-	igt_assert_f(ok, "Failed to receive audio from stream server\n");
-
-	state->msec = state->recv_pages * *recv_len
-		      / (double) state->capture.channels
-		      / (double) state->capture.rate * 1000;
-	state->recv_pages++;
-
-	if (state->dump_fd >= 0) {
-		recv_size = *recv_len * sizeof(int32_t);
-		igt_assert_f(write(state->dump_fd, *recv, recv_size) == recv_size,
-			     "Failed to write to audio dump file\n");
-	}
-}
-
-static void audio_state_stop(struct audio_state *state, bool success)
-{
-	bool ok;
-	int ret;
-	struct chamelium_audio_file *audio_file;
-	enum igt_log_level log_level;
-
-	igt_debug("Stopping audio playback\n");
-	state->run = false;
-	ret = pthread_join(state->thread, NULL);
-	igt_assert_f(ret == 0, "Failed to join audio playback thread\n");
-
-	ok = chamelium_stream_stop_realtime_audio(state->stream);
-	igt_assert_f(ok, "Failed to stop streaming audio capture\n");
-
-	audio_file = chamelium_stop_capturing_audio(state->chamelium,
-						    state->port);
-	if (audio_file) {
-		igt_debug("Audio file saved on the Chamelium in %s\n",
-			  audio_file->path);
-		chamelium_destroy_audio_file(audio_file);
-	}
-
-	if (state->dump_fd >= 0) {
-		close(state->dump_fd);
-		state->dump_fd = -1;
-
-		if (success) {
-			/* Test succeeded, no need to keep the captured data */
-			unlink(state->dump_path);
-		} else
-			igt_debug("Saved captured audio data to %s\n",
-				  state->dump_path);
-		free(state->dump_path);
-		state->dump_path = NULL;
-	}
-
-	if (success)
-		log_level = IGT_LOG_DEBUG;
-	else
-		log_level = IGT_LOG_CRITICAL;
-
-	igt_log(IGT_LOG_DOMAIN, log_level, "Audio %s test result for format %s, "
-		"sampling rate %d Hz and %d channels: %s\n",
-		state->name, snd_pcm_format_name(state->playback.format),
-		state->playback.rate, state->playback.channels,
-		success ? "ALL GREEN" : "FAILED");
-
-}
-
-static void check_audio_infoframe(struct audio_state *state)
-{
-	struct chamelium_infoframe *infoframe;
-	struct infoframe_audio infoframe_audio;
-	struct infoframe_audio expected = {0};
-	bool ok;
-
-	if (!chamelium_supports_get_last_infoframe(state->chamelium)) {
-		igt_debug("Skipping audio InfoFrame check: "
-			  "Chamelium board doesn't support GetLastInfoFrame\n");
-		return;
-	}
-
-	expected.coding_type = INFOFRAME_AUDIO_CT_PCM;
-	expected.channel_count = state->playback.channels;
-	expected.sampling_freq = state->playback.rate;
-	expected.sample_size = snd_pcm_format_width(state->playback.format);
-
-	infoframe = chamelium_get_last_infoframe(state->chamelium, state->port,
-						 CHAMELIUM_INFOFRAME_AUDIO);
-	if (infoframe == NULL && state->playback.channels <= 2) {
-		/* Audio InfoFrames are optional for mono and stereo audio */
-		igt_debug("Skipping audio InfoFrame check: "
-			  "no InfoFrame received\n");
-		return;
-	}
-	igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n");
-
-	ok = infoframe_audio_parse(&infoframe_audio, infoframe->version,
-				   infoframe->payload, infoframe->payload_size);
-	chamelium_infoframe_destroy(infoframe);
-	igt_assert_f(ok, "failed to parse audio InfoFrame\n");
-
-	igt_debug("Checking audio InfoFrame:\n");
-	igt_debug("coding_type: got %d, expected %d\n",
-		  infoframe_audio.coding_type, expected.coding_type);
-	igt_debug("channel_count: got %d, expected %d\n",
-		  infoframe_audio.channel_count, expected.channel_count);
-	igt_debug("sampling_freq: got %d, expected %d\n",
-		  infoframe_audio.sampling_freq, expected.sampling_freq);
-	igt_debug("sample_size: got %d, expected %d\n",
-		  infoframe_audio.sample_size, expected.sample_size);
-
-	if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED)
-		igt_assert(infoframe_audio.coding_type == expected.coding_type);
-	if (infoframe_audio.channel_count >= 0)
-		igt_assert(infoframe_audio.channel_count == expected.channel_count);
-	if (infoframe_audio.sampling_freq >= 0)
-		igt_assert(infoframe_audio.sampling_freq == expected.sampling_freq);
-	if (infoframe_audio.sample_size >= 0)
-		igt_assert(infoframe_audio.sample_size == expected.sample_size);
-}
-
-static int
-audio_output_frequencies_callback(void *data, void *buffer, int samples)
-{
-	struct audio_state *state = data;
-	double *tmp;
-	size_t len;
-
-	len = samples * state->playback.channels;
-	tmp = malloc(len * sizeof(double));
-	audio_signal_fill(state->signal, tmp, samples);
-	audio_convert_to(buffer, tmp, len, state->playback.format);
-	free(tmp);
-
-	return state->run ? 0 : -1;
-}
-
-static bool test_audio_frequencies(struct audio_state *state)
-{
-	int freq, step;
-	int32_t *recv, *buf;
-	double *channel;
-	size_t i, j, streak;
-	size_t recv_len, buf_len, buf_cap, channel_len;
-	bool success;
-	int capture_chan;
-
-	state->signal = audio_signal_init(state->playback.channels,
-					  state->playback.rate);
-	igt_assert_f(state->signal, "Failed to initialize audio signal\n");
-
-	/* We'll choose different frequencies per channel to make sure they are
-	 * independent from each other. To do so, we'll add a different offset
-	 * to the base frequencies for each channel. We need to choose a big
-	 * enough offset so that we're sure to detect mixed up channels. We
-	 * choose an offset of two 2 bins in the final FFT to enforce a clear
-	 * difference.
-	 *
-	 * Note that we assume capture_rate == playback_rate. We'll assert this
-	 * later on. We cannot retrieve the capture rate before starting
-	 * playing audio, so we don't really have the choice.
-	 */
-	step = 2 * state->playback.rate / CAPTURE_SAMPLES;
-	for (i = 0; i < test_frequencies_count; i++) {
-		for (j = 0; j < state->playback.channels; j++) {
-			freq = test_frequencies[i] + j * step;
-			audio_signal_add_frequency(state->signal, freq, j);
-		}
-	}
-	audio_signal_synthesize(state->signal);
-
-	alsa_register_output_callback(state->alsa,
-				      audio_output_frequencies_callback, state,
-				      PLAYBACK_SAMPLES);
-
-	audio_state_start(state, "frequencies");
-
-	igt_assert_f(state->capture.rate == state->playback.rate,
-		     "Capture rate (%dHz) doesn't match playback rate (%dHz)\n",
-		     state->capture.rate, state->playback.rate);
-
-	/* Needs to be a multiple of 128, because that's the number of samples
-	 * we get per channel each time we receive an audio page from the
-	 * Chamelium device.
-	 *
-	 * Additionally, this value needs to be high enough to guarantee we
-	 * capture a full period of each sine we generate. If we capture 2048
-	 * samples at a 192KHz sampling rate, we get a full period for a >94Hz
-	 * sines. For lower sampling rates, the capture duration will be
-	 * longer.
-	 */
-	channel_len = CAPTURE_SAMPLES;
-	channel = malloc(sizeof(double) * channel_len);
-
-	buf_cap = state->capture.channels * channel_len;
-	buf = malloc(sizeof(int32_t) * buf_cap);
-	buf_len = 0;
-
-	recv = NULL;
-	recv_len = 0;
-
-	success = false;
-	streak = 0;
-	while (!success && state->msec < AUDIO_TIMEOUT) {
-		audio_state_receive(state, &recv, &recv_len);
-
-		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
-		buf_len += recv_len;
-
-		if (buf_len < buf_cap)
-			continue;
-		igt_assert(buf_len == buf_cap);
-
-		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
-
-		for (j = 0; j < state->playback.channels; j++) {
-			capture_chan = state->channel_mapping[j];
-			igt_assert(capture_chan >= 0);
-			igt_debug("Processing channel %zu (captured as "
-				  "channel %d)\n", j, capture_chan);
-
-			audio_extract_channel_s32_le(channel, channel_len,
-						     buf, buf_len,
-						     state->capture.channels,
-						     capture_chan);
-
-			if (audio_signal_detect(state->signal,
-						state->capture.rate, j,
-						channel, channel_len))
-				streak++;
-			else
-				streak = 0;
-		}
-
-		buf_len = 0;
-
-		success = streak == MIN_STREAK * state->playback.channels;
-	}
-
-	audio_state_stop(state, success);
-
-	free(recv);
-	free(buf);
-	free(channel);
-	audio_signal_fini(state->signal);
-
-	check_audio_infoframe(state);
-
-	return success;
-}
-
-static int audio_output_flatline_callback(void *data, void *buffer,
-					     int samples)
-{
-	struct audio_state *state = data;
-	double *tmp;
-	size_t len, i;
-
-	len = samples * state->playback.channels;
-	tmp = malloc(len * sizeof(double));
-	for (i = 0; i < len; i++)
-		tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE;
-	audio_convert_to(buffer, tmp, len, state->playback.format);
-	free(tmp);
-
-	return state->run ? 0 : -1;
-}
-
-static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos)
-{
-	double expected, min, max;
-	size_t i;
-	bool ok;
-
-	min = max = NAN;
-	for (i = 0; i < buf_len; i++) {
-		if (isnan(min) || buf[i] < min)
-			min = buf[i];
-		if (isnan(max) || buf[i] > max)
-			max = buf[i];
-	}
-
-	expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE;
-	ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY &&
-	      max <= expected + FLATLINE_AMPLITUDE_ACCURACY);
-	if (ok)
-		igt_debug("Flatline wave amplitude detected\n");
-	else
-		igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n",
-			  min, max);
-	return ok;
-}
-
-static ssize_t detect_falling_edge(double *buf, size_t buf_len)
-{
-	size_t i;
-
-	for (i = 0; i < buf_len; i++) {
-		if (buf[i] < 0)
-			return i;
-	}
-
-	return -1;
-}
-
-/** test_audio_flatline:
- *
- * Send a constant value (one positive, then a negative one) and check that:
- *
- * - The amplitude of the flatline is correct
- * - All channels switch from a positive signal to a negative one at the same
- *   time (ie. all channels are aligned)
- */
-static bool test_audio_flatline(struct audio_state *state)
-{
-	bool success, amp_success, align_success;
-	int32_t *recv;
-	size_t recv_len, i, channel_len;
-	ssize_t j;
-	int streak, capture_chan;
-	double *channel;
-	int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS];
-
-	alsa_register_output_callback(state->alsa,
-				      audio_output_flatline_callback, state,
-				      PLAYBACK_SAMPLES);
-
-	/* Start by sending a positive signal */
-	state->positive = true;
-
-	audio_state_start(state, "flatline");
-
-	for (i = 0; i < state->playback.channels; i++)
-		falling_edges[i] = -1;
-
-	recv = NULL;
-	recv_len = 0;
-	amp_success = false;
-	streak = 0;
-	while (!amp_success && state->msec < AUDIO_TIMEOUT) {
-		audio_state_receive(state, &recv, &recv_len);
-
-		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
-
-		for (i = 0; i < state->playback.channels; i++) {
-			capture_chan = state->channel_mapping[i];
-			igt_assert(capture_chan >= 0);
-			igt_debug("Processing channel %zu (captured as "
-				  "channel %d)\n", i, capture_chan);
-
-			channel_len = audio_extract_channel_s32_le(NULL, 0,
-								   recv, recv_len,
-								   state->capture.channels,
-								   capture_chan);
-			channel = malloc(channel_len * sizeof(double));
-			audio_extract_channel_s32_le(channel, channel_len,
-						     recv, recv_len,
-						     state->capture.channels,
-						     capture_chan);
-
-			/* Check whether the amplitude is fine */
-			if (detect_flatline_amplitude(channel, channel_len,
-						      state->positive))
-				streak++;
-			else
-				streak = 0;
-
-			/* If we're now sending a negative signal, detect the
-			 * falling edge */
-			j = detect_falling_edge(channel, channel_len);
-			if (!state->positive && j >= 0) {
-				falling_edges[i] = recv_len * state->recv_pages
-						   + j;
-			}
-
-			free(channel);
-		}
-
-		amp_success = streak == MIN_STREAK * state->playback.channels;
-
-		if (amp_success && state->positive) {
-			/* Switch to a negative signal after we've detected the
-			 * positive one. */
-			state->positive = false;
-			amp_success = false;
-			streak = 0;
-			igt_debug("Switching to negative square wave\n");
-		}
-	}
-
-	/* Check alignment between all channels by comparing the index of the
-	 * falling edge. */
-	align_success = true;
-	for (i = 0; i < state->playback.channels; i++) {
-		if (falling_edges[i] < 0) {
-			igt_critical("Falling edge not detected for channel %zu\n",
-				     i);
-			align_success = false;
-			continue;
-		}
-
-		if (abs(falling_edges[0] - falling_edges[i]) >
-		    FLATLINE_ALIGN_ACCURACY) {
-			igt_critical("Channel alignment mismatch: "
-				     "channel 0 has a falling edge at index %d "
-				     "while channel %zu has index %d\n",
-				     falling_edges[0], i, falling_edges[i]);
-			align_success = false;
-		}
-	}
-
-	success = amp_success && align_success;
-	audio_state_stop(state, success);
-
-	free(recv);
-
-	return success;
-}
-
-static bool check_audio_configuration(struct alsa *alsa, snd_pcm_format_t format,
-				      int channels, int sampling_rate)
-{
-	if (!alsa_test_output_configuration(alsa, format, channels,
-					    sampling_rate)) {
-		igt_debug("Skipping test with format %s, sampling rate %d Hz "
-			  "and %d channels because at least one of the "
-			  "selected output devices doesn't support this "
-			  "configuration\n",
-			  snd_pcm_format_name(format),
-			  sampling_rate, channels);
-		return false;
-	}
-	/* TODO: the Chamelium device sends a malformed signal for some audio
-	 * configurations. See crbug.com/950917 */
-	if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) ||
-			channels > 2) {
-		igt_debug("Skipping test with format %s, sampling rate %d Hz "
-			  "and %d channels because the Chamelium device "
-			  "doesn't support this configuration\n",
-			  snd_pcm_format_name(format),
-			  sampling_rate, channels);
-		return false;
-	}
-	return true;
-}
-
-static const char test_display_audio_desc[] =
-	"Playback various audio signals with various audio formats/rates, "
-	"capture them and check they are correct";
-static void
-test_display_audio(data_t *data, struct chamelium_port *port,
-		   const char *audio_device, enum igt_custom_edid_type edid)
-{
-	bool run, success;
-	struct alsa *alsa;
-	int ret;
-	igt_output_t *output;
-	igt_plane_t *primary;
-	struct igt_fb fb;
-	drmModeModeInfo *mode;
-	drmModeConnector *connector;
-	int fb_id, i, j;
-	int channels, sampling_rate;
-	snd_pcm_format_t format;
-	struct audio_state state;
-
-	igt_require(alsa_has_exclusive_access());
-
-	/* Old Chamelium devices need an update for DisplayPort audio and
-	 * chamelium_get_audio_format support. */
-	igt_require(chamelium_has_audio_support(data->chamelium, port));
-
-	alsa = alsa_init();
-	igt_assert(alsa);
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	output = prepare_output(data, port, edid);
-	connector = chamelium_port_get_connector(data->chamelium, port, false);
-	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-	igt_assert(primary);
-
-	/* Enable the output because the receiver won't try to receive audio if
-	 * it doesn't receive video. */
-	igt_assert(connector->count_modes > 0);
-	mode = &connector->modes[0];
-
-	fb_id = igt_create_color_pattern_fb(data->drm_fd,
-					    mode->hdisplay, mode->vdisplay,
-					    DRM_FORMAT_XRGB8888,
-					    DRM_FORMAT_MOD_LINEAR,
-					    0, 0, 0, &fb);
-	igt_assert(fb_id > 0);
-
-	enable_output(data, port, output, mode, &fb);
-
-	run = false;
-	success = true;
-	for (i = 0; i < test_sampling_rates_count; i++) {
-		for (j = 0; j < test_formats_count; j++) {
-			ret = alsa_open_output(alsa, audio_device);
-			igt_assert_f(ret >= 0, "Failed to open ALSA output\n");
-
-			/* TODO: playback on all 8 available channels (this
-			 * isn't supported by Chamelium devices yet, see
-			 * https://crbug.com/950917) */
-			format = test_formats[j];
-			channels = PLAYBACK_CHANNELS;
-			sampling_rate = test_sampling_rates[i];
-
-			if (!check_audio_configuration(alsa, format, channels,
-						       sampling_rate))
-				continue;
-
-			run = true;
-
-			audio_state_init(&state, data, alsa, port,
-					 format, channels, sampling_rate);
-			success &= test_audio_frequencies(&state);
-			success &= test_audio_flatline(&state);
-			audio_state_fini(&state);
-
-			alsa_close_output(alsa);
-		}
-	}
-
-	/* Make sure we tested at least one frequency and format. */
-	igt_assert(run);
-	/* Make sure all runs were successful. */
-	igt_assert(success);
-
-	igt_remove_fb(data->drm_fd, &fb);
-
-	drmModeFreeConnector(connector);
-
-	free(alsa);
-}
-
-static const char test_display_audio_edid_desc[] =
-	"Plug a connector with an EDID suitable for audio, check ALSA's "
-	"EDID-Like Data reports the correct audio parameters";
-static void
-test_display_audio_edid(data_t *data, struct chamelium_port *port,
-			enum igt_custom_edid_type edid)
-{
-	igt_output_t *output;
-	igt_plane_t *primary;
-	struct igt_fb fb;
-	drmModeModeInfo *mode;
-	drmModeConnector *connector;
-	int fb_id;
-	struct eld_entry eld;
-	struct eld_sad *sad;
-
-	igt_require(eld_is_supported());
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	output = prepare_output(data, port, edid);
-	connector = chamelium_port_get_connector(data->chamelium, port, false);
-	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-	igt_assert(primary);
-
-	/* Enable the output because audio cannot be played on inactive
-	 * connectors. */
-	igt_assert(connector->count_modes > 0);
-	mode = &connector->modes[0];
-
-	fb_id = igt_create_color_pattern_fb(data->drm_fd,
-					    mode->hdisplay, mode->vdisplay,
-					    DRM_FORMAT_XRGB8888,
-					    DRM_FORMAT_MOD_LINEAR,
-					    0, 0, 0, &fb);
-	igt_assert(fb_id > 0);
-
-	enable_output(data, port, output, mode, &fb);
-
-	igt_assert(eld_get_igt(&eld));
-	igt_assert(eld.sads_len == 1);
-
-	sad = &eld.sads[0];
-	igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM);
-	igt_assert(sad->channels == 2);
-	igt_assert(sad->rates == (CEA_SAD_SAMPLING_RATE_32KHZ |
-		   CEA_SAD_SAMPLING_RATE_44KHZ | CEA_SAD_SAMPLING_RATE_48KHZ));
-	igt_assert(sad->bits == (CEA_SAD_SAMPLE_SIZE_16 |
-		   CEA_SAD_SAMPLE_SIZE_20 | CEA_SAD_SAMPLE_SIZE_24));
-
-	igt_remove_fb(data->drm_fd, &fb);
-
-	drmModeFreeConnector(connector);
-}
-
-static void randomize_plane_stride(data_t *data,
-				   uint32_t width, uint32_t height,
-				   uint32_t format, uint64_t modifier,
-				   size_t *stride)
-{
-	size_t stride_min;
-	uint32_t max_tile_w = 4, tile_w, tile_h;
-	int i;
-	struct igt_fb dummy;
-
-	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
-
-	/* Randomize the stride to less than twice the minimum. */
-	*stride = (rand() % stride_min) + stride_min;
-
-	/*
-	 * Create a dummy FB to determine bpp for each plane, and calculate
-	 * the maximum tile width from that.
-	 */
-	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
-	for (i = 0; i < dummy.num_planes; i++) {
-		igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i], &tile_w, &tile_h);
-
-		if (tile_w > max_tile_w)
-			max_tile_w = tile_w;
-	}
-	igt_remove_fb(data->drm_fd, &dummy);
-
-	/*
-	 * Pixman requires the stride to be aligned to 32-bits, which is
-	 * reflected in the initial value of max_tile_w and the hw
-	 * may require a multiple of tile width, choose biggest of the 2.
-	 */
-	*stride = ALIGN(*stride, max_tile_w);
-}
-
-static void update_tiled_modifier(igt_plane_t *plane, uint32_t width,
-				  uint32_t height, uint32_t format,
-				  uint64_t *modifier)
-{
-	if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) {
-		/* Randomize the column height to less than twice the minimum. */
-		size_t column_height = (rand() % height) + height;
-
-		igt_debug("Selecting VC4 SAND256 tiling with column height %ld\n",
-			  column_height);
-
-		*modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(column_height);
-	}
-}
-
-static void randomize_plane_setup(data_t *data, igt_plane_t *plane,
-				  drmModeModeInfo *mode,
-				  uint32_t *width, uint32_t *height,
-				  uint32_t *format, uint64_t *modifier,
-				  bool allow_yuv)
-{
-	int min_dim;
-	uint32_t idx[plane->format_mod_count];
-	unsigned int count = 0;
-	unsigned int i;
-
-	/* First pass to count the supported formats. */
-	for (i = 0; i < plane->format_mod_count; i++)
-		if (igt_fb_supported_format(plane->formats[i]) &&
-		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
-			idx[count++] = i;
-
-	igt_assert(count > 0);
-
-	i = idx[rand() % count];
-	*format = plane->formats[i];
-	*modifier = plane->modifiers[i];
-
-	update_tiled_modifier(plane, *width, *height, *format, modifier);
-
-	/*
-	 * Randomize width and height in the mode dimensions range.
-	 *
-	 * Restrict to a min of 2 * min_dim, this way src_w/h are always at
-	 * least min_dim, because src_w = width - (rand % w / 2).
-	 *
-	 * Use a minimum dimension of 16 for YUV, because planar YUV
-	 * subsamples the UV plane.
-	 */
-	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
-
-	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
-	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
-}
-
-static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h,
-			    uint32_t src_x, uint32_t src_y, uint32_t crtc_w,
-			    uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y,
-			    struct igt_fb *fb)
-{
-	igt_plane_set_fb(plane, fb);
-
-	igt_plane_set_position(plane, crtc_x, crtc_y);
-	igt_plane_set_size(plane, crtc_w, crtc_h);
-
-	igt_fb_set_position(fb, plane, src_x, src_y);
-	igt_fb_set_size(fb, plane, src_w, src_h);
-}
-
-static void randomize_plane_coordinates(data_t *data, igt_plane_t *plane,
-					drmModeModeInfo *mode,
-					struct igt_fb *fb,
-					uint32_t *src_w, uint32_t *src_h,
-					uint32_t *src_x, uint32_t *src_y,
-					uint32_t *crtc_w, uint32_t *crtc_h,
-					int32_t *crtc_x, int32_t *crtc_y,
-					bool allow_scaling)
-{
-	bool is_yuv = igt_format_is_yuv(fb->drm_format);
-	uint32_t width = fb->width, height = fb->height;
-	double ratio;
-	int ret;
-
-	/* Randomize source offset in the first half of the original size. */
-	*src_x = rand() % (width / 2);
-	*src_y = rand() % (height / 2);
-
-	/* The source size only includes the active source area. */
-	*src_w = width - *src_x;
-	*src_h = height - *src_y;
-
-	if (allow_scaling) {
-		*crtc_w = (rand() % mode->hdisplay) + 1;
-		*crtc_h = (rand() % mode->vdisplay) + 1;
-
-		/*
-		 * Don't bother with scaling if dimensions are quite close in
-		 * order to get non-scaling cases more frequently. Also limit
-		 * scaling to 3x to avoid agressive filtering that makes
-		 * comparison less reliable, and don't go above 2x downsampling
-		 * to avoid possible hw limitations.
-		 */
-
-		ratio = ((double) *crtc_w / *src_w);
-		if (ratio < 0.5)
-			*src_w = *crtc_w * 2;
-		else if (ratio > 0.8 && ratio < 1.2)
-			*crtc_w = *src_w;
-		else if (ratio > 3.0)
-			*crtc_w = *src_w * 3;
-
-		ratio = ((double) *crtc_h / *src_h);
-		if (ratio < 0.5)
-			*src_h = *crtc_h * 2;
-		else if (ratio > 0.8 && ratio < 1.2)
-			*crtc_h = *src_h;
-		else if (ratio > 3.0)
-			*crtc_h = *src_h * 3;
-	} else {
-		*crtc_w = *src_w;
-		*crtc_h = *src_h;
-	}
-
-	if (*crtc_w != *src_w || *crtc_h != *src_h) {
-		/*
-		 * When scaling is involved, make sure to not go off-bounds or
-		 * scaled clipping may result in decimal dimensions, that most
-		 * drivers don't support.
-		 */
-		if (*crtc_w < mode->hdisplay)
-			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
-		else
-			*crtc_x = 0;
-
-		if (*crtc_h < mode->vdisplay)
-			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
-		else
-			*crtc_y = 0;
-	} else {
-		/*
-		 * Randomize the on-crtc position and allow the plane to go
-		 * off-display by less than half of its on-crtc dimensions.
-		 */
-		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
-		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
-	}
-
-	configure_plane(plane, *src_w, *src_h, *src_x, *src_y,
-			*crtc_w, *crtc_h, *crtc_x, *crtc_y, fb);
-	ret = igt_display_try_commit_atomic(&data->display,
-					    DRM_MODE_ATOMIC_TEST_ONLY |
-					    DRM_MODE_ATOMIC_ALLOW_MODESET,
-					    NULL);
-	if (!ret)
-		return;
-
-	/* Coordinates are logged in the dumped debug log, so only report w/h on failure here. */
-	igt_assert_f(ret != -ENOSPC,"Failure in testcase, invalid coordinates on a %ux%u fb\n", width, height);
-
-	/* Make YUV coordinates a multiple of 2 and retry the math. */
-	if (is_yuv) {
-		*src_x &= ~1;
-		*src_y &= ~1;
-		*src_w &= ~1;
-		*src_h &= ~1;
-		/* To handle 1:1 scaling, clear crtc_w/h too. */
-		*crtc_w &= ~1;
-		*crtc_h &= ~1;
-
-		if (*crtc_x < 0 && (*crtc_x & 1))
-			(*crtc_x)++;
-		else
-			*crtc_x &= ~1;
-
-		/* If negative, round up to 0 instead of down */
-		if (*crtc_y < 0 && (*crtc_y & 1))
-			(*crtc_y)++;
-		else
-			*crtc_y &= ~1;
-
-		configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
-				*crtc_h, *crtc_x, *crtc_y, fb);
-		ret = igt_display_try_commit_atomic(&data->display,
-						DRM_MODE_ATOMIC_TEST_ONLY |
-						DRM_MODE_ATOMIC_ALLOW_MODESET,
-						NULL);
-		if (!ret)
-			return;
-	}
-
-	igt_assert(!ret || allow_scaling);
-	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
-		  ((double) *crtc_w / *src_w), ((double) *crtc_h / *src_h));
-
-	*crtc_w = *src_w;
-	*crtc_h = *src_h;
-
-	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
-			*crtc_h, *crtc_x, *crtc_y, fb);
-	igt_display_commit_atomic(&data->display,
-				  DRM_MODE_ATOMIC_TEST_ONLY |
-				  DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
-}
-
-static void blit_plane_cairo(data_t *data, cairo_surface_t *result,
-			     uint32_t src_w, uint32_t src_h,
-			     uint32_t src_x, uint32_t src_y,
-			     uint32_t crtc_w, uint32_t crtc_h,
-			     int32_t crtc_x, int32_t crtc_y,
-			     struct igt_fb *fb)
-{
-	cairo_surface_t *surface;
-	cairo_surface_t *clipped_surface;
-	cairo_t *cr;
-
-	surface = igt_get_cairo_surface(data->drm_fd, fb);
-
-	if (src_x || src_y) {
-		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
-							     src_w, src_h);
-
-		cr = cairo_create(clipped_surface);
-
-		cairo_translate(cr, -1. * src_x, -1. * src_y);
-
-		cairo_set_source_surface(cr, surface, 0, 0);
-
-		cairo_paint(cr);
-		cairo_surface_flush(clipped_surface);
-
-		cairo_destroy(cr);
-	} else {
-		clipped_surface = surface;
-	}
-
-	cr = cairo_create(result);
-
-	cairo_translate(cr, crtc_x, crtc_y);
-
-	if (src_w != crtc_w || src_h != crtc_h) {
-		cairo_scale(cr, (double) crtc_w / src_w,
-			    (double) crtc_h / src_h);
-	}
-
-	cairo_set_source_surface(cr, clipped_surface, 0, 0);
-	cairo_surface_destroy(clipped_surface);
-
-	if (src_w != crtc_w || src_h != crtc_h) {
-		cairo_pattern_set_filter(cairo_get_source(cr),
-					 CAIRO_FILTER_BILINEAR);
-		cairo_pattern_set_extend(cairo_get_source(cr),
-					 CAIRO_EXTEND_NONE);
-	}
-
-	cairo_paint(cr);
-	cairo_surface_flush(result);
-
-	cairo_destroy(cr);
-}
-
-static void prepare_randomized_plane(data_t *data,
-				     drmModeModeInfo *mode,
-				     igt_plane_t *plane,
-				     struct igt_fb *overlay_fb,
-				     unsigned int index,
-				     cairo_surface_t *result_surface,
-				     bool allow_scaling, bool allow_yuv)
-{
-	struct igt_fb pattern_fb;
-	uint32_t overlay_fb_w, overlay_fb_h;
-	uint32_t overlay_src_w, overlay_src_h;
-	uint32_t overlay_src_x, overlay_src_y;
-	int32_t overlay_crtc_x, overlay_crtc_y;
-	uint32_t overlay_crtc_w, overlay_crtc_h;
-	uint32_t format;
-	uint64_t modifier;
-	size_t stride;
-	bool tiled;
-	int fb_id;
-
-	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
-			      &format, &modifier, allow_yuv);
-
-	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
-	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n",
-		  index, overlay_fb_w, overlay_fb_h,
-		  igt_format_str(format), tiled ? "tiled" : "linear");
-
-	/* Get a pattern framebuffer for the overlay plane. */
-	fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h,
-					 DRM_FORMAT_XRGB8888, 32, &pattern_fb);
-	igt_assert(fb_id > 0);
-
-	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h,
-			       format, modifier, &stride);
-
-	igt_debug("Plane %d: stride %ld\n", index, stride);
-
-	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
-					   modifier, stride);
-	igt_assert(fb_id > 0);
-
-	randomize_plane_coordinates(data, plane, mode, overlay_fb,
-				    &overlay_src_w, &overlay_src_h,
-				    &overlay_src_x, &overlay_src_y,
-				    &overlay_crtc_w, &overlay_crtc_h,
-				    &overlay_crtc_x, &overlay_crtc_y,
-				    allow_scaling);
-
-	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index,
-		  overlay_src_w, overlay_src_h);
-	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
-		  overlay_src_x, overlay_src_y);
-	igt_debug("Plane %d: on-crtc size %dx%d\n", index,
-		  overlay_crtc_w, overlay_crtc_h);
-	igt_debug("Plane %d: on-crtc position %dx%d\n", index,
-		  overlay_crtc_x, overlay_crtc_y);
-
-	blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h,
-			 overlay_src_x, overlay_src_y,
-			 overlay_crtc_w, overlay_crtc_h,
-			 overlay_crtc_x, overlay_crtc_y, &pattern_fb);
-
-	/* Remove the original pattern framebuffer. */
-	igt_remove_fb(data->drm_fd, &pattern_fb);
-}
-
-static const char test_display_planes_random_desc[] =
-	"Setup a few overlay planes with random parameters, capture the frame "
-	"and check it matches the expected output";
-static void test_display_planes_random(data_t *data,
-				       struct chamelium_port *port,
-				       enum chamelium_check check)
-{
-	igt_output_t *output;
-	drmModeModeInfo *mode;
-	igt_plane_t *primary_plane;
-	struct igt_fb primary_fb;
-	struct igt_fb result_fb;
-	struct igt_fb *overlay_fbs;
-	igt_crc_t *crc;
-	igt_crc_t *expected_crc;
-	struct chamelium_fb_crc_async_data *fb_crc;
-	unsigned int overlay_planes_max = 0;
-	unsigned int overlay_planes_count;
-	cairo_surface_t *result_surface;
-	int captured_frame_count;
-	bool allow_scaling;
-	bool allow_yuv;
-	unsigned int i;
-	unsigned int fb_id;
-
-	switch (check) {
-	case CHAMELIUM_CHECK_CRC:
-		allow_scaling = false;
-		allow_yuv = false;
-		break;
-	case CHAMELIUM_CHECK_CHECKERBOARD:
-		allow_scaling = true;
-		allow_yuv = true;
-		break;
-	default:
-		igt_assert(false);
-	}
-
-	srand(time(NULL));
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	/* Find the connector and pipe. */
-	output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
-
-	mode = igt_output_get_mode(output);
-
-	/* Get a framebuffer for the primary plane. */
-	primary_plane = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-	igt_assert(primary_plane);
-
-	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
-					 DRM_FORMAT_XRGB8888, 64, &primary_fb);
-	igt_assert(fb_id > 0);
-
-	/* Get a framebuffer for the cairo composition result. */
-	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay,
-			      mode->vdisplay, DRM_FORMAT_XRGB8888,
-			      DRM_FORMAT_MOD_LINEAR, &result_fb);
-	igt_assert(fb_id > 0);
-
-	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
-
-	/* Paint the primary framebuffer on the result surface. */
-	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
-			 &primary_fb);
-
-	/* Configure the primary plane. */
-	igt_plane_set_fb(primary_plane, &primary_fb);
-
-	overlay_planes_max =
-		igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY);
-
-	/* Limit the number of planes to a reasonable scene. */
-	overlay_planes_max = min(overlay_planes_max, 4u);
-
-	overlay_planes_count = (rand() % overlay_planes_max) + 1;
-	igt_debug("Using %d overlay planes\n", overlay_planes_count);
-
-	overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count);
-
-	for (i = 0; i < overlay_planes_count; i++) {
-		struct igt_fb *overlay_fb = &overlay_fbs[i];
-		igt_plane_t *plane =
-			igt_output_get_plane_type_index(output,
-							DRM_PLANE_TYPE_OVERLAY,
-							i);
-		igt_assert(plane);
-
-		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
-					 result_surface, allow_scaling,
-					 allow_yuv);
-	}
-
-	cairo_surface_destroy(result_surface);
-
-	if (check == CHAMELIUM_CHECK_CRC)
-		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
-								&result_fb);
-
-	igt_display_commit2(&data->display, COMMIT_ATOMIC);
-
-	if (check == CHAMELIUM_CHECK_CRC) {
-		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1);
-		crc = chamelium_read_captured_crcs(data->chamelium,
-						   &captured_frame_count);
-
-		igt_assert(captured_frame_count == 1);
-
-		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
-
-		chamelium_assert_crc_eq_or_dump(data->chamelium,
-						expected_crc, crc,
-						&result_fb, 0);
-
-		free(expected_crc);
-		free(crc);
-	} else if (check == CHAMELIUM_CHECK_CHECKERBOARD) {
-		struct chamelium_frame_dump *dump;
-
-		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
-						  0, 0);
-		chamelium_assert_frame_match_or_dump(data->chamelium, port,
-						     dump, &result_fb, check);
-		chamelium_destroy_frame_dump(dump);
-	}
-
-	for (i = 0; i < overlay_planes_count; i++)
-		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
-
-	free(overlay_fbs);
-
-	igt_remove_fb(data->drm_fd, &primary_fb);
-	igt_remove_fb(data->drm_fd, &result_fb);
-}
-
-static const char test_hpd_without_ddc_desc[] =
-	"Disable DDC on a VGA connector, check we still get a uevent on hotplug";
-static void
-test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
-{
-	struct udev_monitor *mon = igt_watch_uevents();
-
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-	igt_flush_uevents(mon);
-
-	/* Disable the DDC on the connector and make sure we still get a
-	 * hotplug
-	 */
-	chamelium_port_set_ddc_state(data->chamelium, port, false);
-	chamelium_plug(data->chamelium, port);
-
-	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
-	igt_assert_eq(chamelium_reprobe_connector(&data->display,
-						  data->chamelium, port),
-						  DRM_MODE_CONNECTED);
-
-	igt_cleanup_uevents(mon);
-}
-
-static const char test_hpd_storm_detect_desc[] =
-	"Trigger a series of hotplugs in a very small timeframe to simulate a"
-	"bad cable, check the kernel falls back to polling to avoid a hotplug "
-	"storm";
-static void
-test_hpd_storm_detect(data_t *data, struct chamelium_port *port, int width)
-{
-	struct udev_monitor *mon;
-	int count = 0;
-
-	igt_require_hpd_storm_ctl(data->drm_fd);
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	igt_hpd_storm_set_threshold(data->drm_fd, 1);
-	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
-	igt_assert(igt_hpd_storm_detected(data->drm_fd));
-
-	mon = igt_watch_uevents();
-	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
-
-	/*
-	 * Polling should have been enabled by the HPD storm at this point,
-	 * so we should only get at most 1 hotplug event
-	 */
-	igt_until_timeout(5)
-		count += igt_hotplug_detected(mon, 1);
-	igt_assert_lt(count, 2);
-
-	igt_cleanup_uevents(mon);
-	igt_hpd_storm_reset(data->drm_fd);
-}
-
-static const char test_hpd_storm_disable_desc[] =
-	"Disable HPD storm detection, trigger a storm and check the kernel "
-	"doesn't detect one";
-static void
-test_hpd_storm_disable(data_t *data, struct chamelium_port *port, int width)
-{
-	igt_require_hpd_storm_ctl(data->drm_fd);
-	igt_modeset_disable_all_outputs(&data->display);
-	chamelium_reset_state(&data->display, data->chamelium,
-			      port, data->ports, data->port_count);
-
-	igt_hpd_storm_set_threshold(data->drm_fd, 0);
-	chamelium_fire_hpd_pulses(data->chamelium, port,
-				  width, 10);
-	igt_assert(!igt_hpd_storm_detected(data->drm_fd));
-
-	igt_hpd_storm_reset(data->drm_fd);
-}
-
-static const char igt_edid_stress_resolution_desc[] =
-	"Stress test the DUT by testing multiple EDIDs, one right after the other,"
-	"and ensure their validity by check the real screen resolution vs the"
-	"advertised mode resultion.";
-static void edid_stress_resolution(data_t *data, struct chamelium_port *port,
-				   monitor_edid edids_list[],
-				   size_t edids_list_len)
-{
-	int i;
-	struct chamelium *chamelium = data->chamelium;
-	struct udev_monitor *mon = igt_watch_uevents();
-
-	for (i = 0; i < edids_list_len; ++i) {
-		struct chamelium_edid *chamelium_edid;
-		drmModeModeInfo mode;
-		struct igt_fb fb = { 0 };
-		igt_output_t *output;
-		enum pipe pipe;
-		bool is_video_stable;
-		int screen_res_w, screen_res_h;
-
-		monitor_edid *edid = &edids_list[i];
-		igt_info("Testing out the EDID for %s\n",
-			 monitor_edid_get_name(edid));
-
-		/* Getting and Setting the EDID on Chamelium. */
-		chamelium_edid =
-			get_chameleon_edid_from_monitor_edid(chamelium, edid);
-		chamelium_port_set_edid(data->chamelium, port, chamelium_edid);
-		free_chamelium_edid_from_monitor_edid(chamelium_edid);
-
-		igt_flush_uevents(mon);
-		chamelium_plug(chamelium, port);
-		wait_for_connector_after_hotplug(data, mon, port,
-						 DRM_MODE_CONNECTED);
-		igt_flush_uevents(mon);
-
-		/* Setting an output on the screen to turn it on. */
-		mode = get_mode_for_port(chamelium, port);
-		create_fb_for_mode(data, &fb, &mode);
-		output = get_output_for_port(data, port);
-		pipe = get_pipe_for_output(&data->display, output);
-		igt_output_set_pipe(output, pipe);
-		enable_output(data, port, output, &mode, &fb);
-
-		/* Capture the screen resolution and verify. */
-		is_video_stable = chamelium_port_wait_video_input_stable(
-			chamelium, port, 5);
-		igt_assert(is_video_stable);
-
-		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
-					      &screen_res_h);
-		igt_assert(screen_res_w == fb.width);
-		igt_assert(screen_res_h == fb.height);
-
-		// Clean up
-		igt_remove_fb(data->drm_fd, &fb);
-		igt_modeset_disable_all_outputs(&data->display);
-		chamelium_unplug(chamelium, port);
-	}
-
-	chamelium_reset_state(&data->display, data->chamelium, port,
-			      data->ports, data->port_count);
-}
-
-static const char igt_edid_resolution_list_desc[] =
-	"Get an EDID with many modes of different configurations, set them on the screen and check the"
-	" screen resolution matches the mode resolution.";
-
-static void edid_resolution_list(data_t *data, struct chamelium_port *port)
-{
-	struct chamelium *chamelium = data->chamelium;
-	struct udev_monitor *mon = igt_watch_uevents();
-	drmModeConnector *connector;
-	drmModeModeInfoPtr modes;
-	int count_modes;
-	int i;
-	igt_output_t *output;
-	enum pipe pipe;
-
-	chamelium_unplug(chamelium, port);
-	set_edid(data, port, IGT_CUSTOM_EDID_FULL);
-
-	igt_flush_uevents(mon);
-	chamelium_plug(chamelium, port);
-	wait_for_connector_after_hotplug(data, mon, port, DRM_MODE_CONNECTED);
-	igt_flush_uevents(mon);
-
-	connector = chamelium_port_get_connector(chamelium, port, true);
-	modes = connector->modes;
-	count_modes = connector->count_modes;
-
-	output = get_output_for_port(data, port);
-	pipe = get_pipe_for_output(&data->display, output);
-	igt_output_set_pipe(output, pipe);
-
-	for (i = 0; i < count_modes; ++i)
-		igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh);
-
-	for (i = 0; i < count_modes; ++i) {
-		struct igt_fb fb = { 0 };
-		bool is_video_stable;
-		int screen_res_w, screen_res_h;
-
-		igt_info("Testing #%d %s %uHz\n", i, modes[i].name,
-			 modes[i].vrefresh);
-
-		/* Set the screen mode with the one we chose. */
-		create_fb_for_mode(data, &fb, &modes[i]);
-		enable_output(data, port, output, &modes[i], &fb);
-		is_video_stable = chamelium_port_wait_video_input_stable(
-			chamelium, port, 10);
-		igt_assert(is_video_stable);
-
-		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
-					      &screen_res_h);
-		igt_assert_eq(screen_res_w, modes[i].hdisplay);
-		igt_assert_eq(screen_res_h, modes[i].vdisplay);
-
-		igt_remove_fb(data->drm_fd, &fb);
-	}
-
-	igt_modeset_disable_all_outputs(&data->display);
-	drmModeFreeConnector(connector);
-}
-
-#define for_each_port(p, port)            \
-	for (p = 0, port = data.ports[p]; \
-	     p < data.port_count;         \
-	     p++, port = data.ports[p])
-
-#define connector_subtest(name__, type__)                    \
-	igt_subtest(name__)                                  \
-		for_each_port(p, port)                       \
-			if (chamelium_port_get_type(port) == \
-			    DRM_MODE_CONNECTOR_ ## type__)
-
-#define connector_dynamic_subtest(name__, type__)            \
-	igt_subtest_with_dynamic(name__)                     \
-		for_each_port(p, port)                       \
-			if (chamelium_port_get_type(port) == \
-			    DRM_MODE_CONNECTOR_ ## type__)
-
-
-static data_t data;
-
-IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board");
-igt_main
-{
-	struct chamelium_port *port;
-	int p;
-	size_t i;
-
-	igt_fixture {
-		/* So fbcon doesn't try to reprobe things itself */
-		kmstest_set_vt_graphics_mode();
-
-		data.drm_fd = drm_open_driver_master(DRIVER_ANY);
-		igt_display_require(&data.display, data.drm_fd);
-		igt_require(data.display.is_atomic);
-
-		/*
-		 * XXX: disabling modeset, can be removed when
-		 * igt_display_require will start doing this for us
-		 */
-		igt_display_commit2(&data.display, COMMIT_ATOMIC);
-
-		/* we need to initalize chamelium after igt_display_require */
-		data.chamelium = chamelium_init(data.drm_fd, &data.display);
-		igt_require(data.chamelium);
-
-		data.ports = chamelium_get_ports(data.chamelium,
-						 &data.port_count);
-
-		for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) {
-			data.edids[i] = chamelium_new_edid(data.chamelium,
-							   igt_kms_get_custom_edid(i));
-		}
-	}
-
-	igt_describe("DisplayPort tests");
-	igt_subtest_group {
-		igt_fixture {
-			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_DisplayPort,
-							    data.port_count, 1);
-		}
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("dp-hpd", DisplayPort)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_DP_HDMI,
-				     TEST_MODESET_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("dp-hpd-fast", DisplayPort)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("dp-hpd-enable-disable-mode", DisplayPort)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_ON_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("dp-hpd-with-enabled-mode", DisplayPort)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_ON);
-
-		igt_describe(igt_custom_edid_type_read_desc);
-		connector_subtest("dp-edid-read", DisplayPort) {
-			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
-			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
-		}
-
-		igt_describe(igt_edid_stress_resolution_desc);
-		connector_subtest("dp-edid-stress-resolution-4k", DisplayPort)
-			edid_stress_resolution(&data, port, DP_EDIDS_4K,
-					       ARRAY_SIZE(DP_EDIDS_4K));
-
-		igt_describe(igt_edid_stress_resolution_desc);
-		connector_subtest("dp-edid-stress-resolution-non-4k",
-				  DisplayPort)
-			edid_stress_resolution(&data, port, DP_EDIDS_NON_4K,
-					       ARRAY_SIZE(DP_EDIDS_NON_4K));
-
-		igt_describe(igt_edid_resolution_list_desc);
-		connector_subtest("dp-edid-resolution-list", DisplayPort)
-			edid_resolution_list(&data, port);
-
-		igt_describe(test_suspend_resume_hpd_desc);
-		connector_subtest("dp-hpd-after-suspend", DisplayPort)
-			test_suspend_resume_hpd(&data, port,
-						SUSPEND_STATE_MEM,
-						SUSPEND_TEST_NONE);
-
-		igt_describe(test_suspend_resume_hpd_desc);
-		connector_subtest("dp-hpd-after-hibernate", DisplayPort)
-			test_suspend_resume_hpd(&data, port,
-						SUSPEND_STATE_DISK,
-						SUSPEND_TEST_DEVICES);
-
-		igt_describe(test_hpd_storm_detect_desc);
-		connector_subtest("dp-hpd-storm", DisplayPort)
-			test_hpd_storm_detect(&data, port,
-					      HPD_STORM_PULSE_INTERVAL_DP);
-
-		igt_describe(test_hpd_storm_disable_desc);
-		connector_subtest("dp-hpd-storm-disable", DisplayPort)
-			test_hpd_storm_disable(&data, port,
-					       HPD_STORM_PULSE_INTERVAL_DP);
-
-		igt_describe(test_suspend_resume_edid_change_desc);
-		connector_subtest("dp-edid-change-during-suspend", DisplayPort)
-			test_suspend_resume_edid_change(&data, port,
-							SUSPEND_STATE_MEM,
-							SUSPEND_TEST_NONE,
-							IGT_CUSTOM_EDID_BASE,
-							IGT_CUSTOM_EDID_ALT);
-
-		igt_describe(test_suspend_resume_edid_change_desc);
-		connector_subtest("dp-edid-change-during-hibernate", DisplayPort)
-			test_suspend_resume_edid_change(&data, port,
-							SUSPEND_STATE_DISK,
-							SUSPEND_TEST_DEVICES,
-							IGT_CUSTOM_EDID_BASE,
-							IGT_CUSTOM_EDID_ALT);
-
-		igt_describe(test_display_all_modes_desc);
-		connector_subtest("dp-crc-single", DisplayPort)
-			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
-					       CHAMELIUM_CHECK_CRC, 1);
-
-		igt_describe(test_display_one_mode_desc);
-		connector_subtest("dp-crc-fast", DisplayPort)
-			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
-					      CHAMELIUM_CHECK_CRC, 1);
-
-		igt_describe(test_display_all_modes_desc);
-		connector_subtest("dp-crc-multiple", DisplayPort)
-			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
-					       CHAMELIUM_CHECK_CRC, 3);
-
-		igt_describe(test_display_frame_dump_desc);
-		connector_subtest("dp-frame-dump", DisplayPort)
-			test_display_frame_dump(&data, port);
-
-		igt_describe(test_mode_timings_desc);
-		connector_subtest("dp-mode-timings", DisplayPort)
-			test_mode_timings(&data, port);
-
-		igt_describe(test_display_audio_desc);
-		connector_subtest("dp-audio", DisplayPort)
-			test_display_audio(&data, port, "HDMI",
-					   IGT_CUSTOM_EDID_DP_AUDIO);
-
-		igt_describe(test_display_audio_edid_desc);
-		connector_subtest("dp-audio-edid", DisplayPort)
-			test_display_audio_edid(&data, port,
-						IGT_CUSTOM_EDID_DP_AUDIO);
-
-		igt_describe(test_hotplug_for_each_pipe_desc);
-		connector_subtest("dp-hpd-for-each-pipe", DisplayPort)
-			test_hotplug_for_each_pipe(&data, port);
-	}
-
-	igt_describe("HDMI tests");
-	igt_subtest_group {
-		igt_fixture {
-			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_HDMIA,
-							    data.port_count, 1);
-		}
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("hdmi-hpd", HDMIA)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_DP_HDMI,
-				     TEST_MODESET_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("hdmi-hpd-fast", HDMIA)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_ON_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_ON);
-
-		igt_describe(igt_custom_edid_type_read_desc);
-		connector_subtest("hdmi-edid-read", HDMIA) {
-			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
-			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
-		}
-
-		igt_describe(igt_edid_stress_resolution_desc);
-		connector_subtest("hdmi-edid-stress-resolution-4k", HDMIA)
-			edid_stress_resolution(&data, port, HDMI_EDIDS_4K,
-					       ARRAY_SIZE(HDMI_EDIDS_4K));
-
-		igt_describe(igt_edid_stress_resolution_desc);
-		connector_subtest("hdmi-edid-stress-resolution-non-4k", HDMIA)
-			edid_stress_resolution(&data, port, HDMI_EDIDS_NON_4K,
-					       ARRAY_SIZE(HDMI_EDIDS_NON_4K));
-
-		igt_describe(test_suspend_resume_hpd_desc);
-		connector_subtest("hdmi-hpd-after-suspend", HDMIA)
-			test_suspend_resume_hpd(&data, port,
-						SUSPEND_STATE_MEM,
-						SUSPEND_TEST_NONE);
-
-		igt_describe(test_suspend_resume_hpd_desc);
-		connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
-			test_suspend_resume_hpd(&data, port,
-						SUSPEND_STATE_DISK,
-						SUSPEND_TEST_DEVICES);
-
-		igt_describe(test_hpd_storm_detect_desc);
-		connector_subtest("hdmi-hpd-storm", HDMIA)
-			test_hpd_storm_detect(&data, port,
-					      HPD_STORM_PULSE_INTERVAL_HDMI);
-
-		igt_describe(test_hpd_storm_disable_desc);
-		connector_subtest("hdmi-hpd-storm-disable", HDMIA)
-			test_hpd_storm_disable(&data, port,
-					       HPD_STORM_PULSE_INTERVAL_HDMI);
-
-		igt_describe(test_suspend_resume_edid_change_desc);
-		connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
-			test_suspend_resume_edid_change(&data, port,
-							SUSPEND_STATE_MEM,
-							SUSPEND_TEST_NONE,
-							IGT_CUSTOM_EDID_BASE,
-							IGT_CUSTOM_EDID_ALT);
-
-		igt_describe(test_suspend_resume_edid_change_desc);
-		connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
-			test_suspend_resume_edid_change(&data, port,
-							SUSPEND_STATE_DISK,
-							SUSPEND_TEST_DEVICES,
-							IGT_CUSTOM_EDID_BASE,
-							IGT_CUSTOM_EDID_ALT);
-
-		igt_describe(test_display_all_modes_desc);
-		connector_subtest("hdmi-crc-single", HDMIA)
-			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
-					       CHAMELIUM_CHECK_CRC, 1);
-
-		igt_describe(test_display_one_mode_desc);
-		connector_subtest("hdmi-crc-fast", HDMIA)
-			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
-					      CHAMELIUM_CHECK_CRC, 1);
-
-		igt_describe(test_display_all_modes_desc);
-		connector_subtest("hdmi-crc-multiple", HDMIA)
-			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
-					       CHAMELIUM_CHECK_CRC, 3);
-
-		igt_describe(test_display_one_mode_desc);
-		connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA) {
-			int k;
-			igt_output_t *output;
-			igt_plane_t *primary;
-
-			output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE);
-			primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-			igt_assert(primary);
-
-			for (k = 0; k < primary->format_mod_count; k++) {
-				if (!igt_fb_supported_format(primary->formats[k]))
-					continue;
-
-				if (igt_format_is_yuv(primary->formats[k]))
-					continue;
-
-				if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
-					continue;
-
-				igt_dynamic_f("%s", igt_format_str(primary->formats[k]))
-					test_display_one_mode(&data, port, primary->formats[k],
-							      CHAMELIUM_CHECK_CRC, 1);
-			}
-		}
-
-		igt_describe(test_display_planes_random_desc);
-		connector_subtest("hdmi-crc-planes-random", HDMIA)
-			test_display_planes_random(&data, port,
-						   CHAMELIUM_CHECK_CRC);
-
-		igt_describe(test_display_one_mode_desc);
-		connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA) {
-			int k;
-			igt_output_t *output;
-			igt_plane_t *primary;
-
-			output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE);
-			primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
-			igt_assert(primary);
-
-			for (k = 0; k < primary->format_mod_count; k++) {
-				if (!igt_fb_supported_format(primary->formats[k]))
-					continue;
-
-				if (!igt_format_is_yuv(primary->formats[k]))
-					continue;
-
-				if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
-					continue;
-
-				igt_dynamic_f("%s", igt_format_str(primary->formats[k]))
-					test_display_one_mode(&data, port, primary->formats[k],
-							      CHAMELIUM_CHECK_CHECKERBOARD, 1);
-			}
-		}
-
-		igt_describe(test_display_planes_random_desc);
-		connector_subtest("hdmi-cmp-planes-random", HDMIA)
-			test_display_planes_random(&data, port,
-						   CHAMELIUM_CHECK_CHECKERBOARD);
-
-		igt_describe(test_display_frame_dump_desc);
-		connector_subtest("hdmi-frame-dump", HDMIA)
-			test_display_frame_dump(&data, port);
-
-		igt_describe(test_mode_timings_desc);
-		connector_subtest("hdmi-mode-timings", HDMIA)
-			test_mode_timings(&data, port);
-
-		igt_describe(test_display_audio_desc);
-		connector_subtest("hdmi-audio", HDMIA)
-			test_display_audio(&data, port, "HDMI",
-					   IGT_CUSTOM_EDID_HDMI_AUDIO);
-
-		igt_describe(test_display_audio_edid_desc);
-		connector_subtest("hdmi-audio-edid", HDMIA)
-			test_display_audio_edid(&data, port,
-						IGT_CUSTOM_EDID_HDMI_AUDIO);
-
-		igt_describe(test_display_aspect_ratio_desc);
-		connector_subtest("hdmi-aspect-ratio", HDMIA)
-			test_display_aspect_ratio(&data, port);
-
-		igt_describe(test_hotplug_for_each_pipe_desc);
-		connector_subtest("hdmi-hpd-for-each-pipe", HDMIA)
-			test_hotplug_for_each_pipe(&data, port);
-	}
-
-	igt_describe("VGA tests");
-	igt_subtest_group {
-		igt_fixture {
-			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_VGA,
-							    data.port_count, 1);
-		}
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("vga-hpd", VGA)
-			test_hotplug(&data, port, HPD_TOGGLE_COUNT_VGA,
-				     TEST_MODESET_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("vga-hpd-fast", VGA)
-			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("vga-hpd-enable-disable-mode", VGA)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_ON_OFF);
-
-		igt_describe(test_basic_hotplug_desc);
-		connector_subtest("vga-hpd-with-enabled-mode", VGA)
-			test_hotplug(&data, port,
-				     HPD_TOGGLE_COUNT_FAST,
-				     TEST_MODESET_ON);
-
-		igt_describe(igt_custom_edid_type_read_desc);
-		connector_subtest("vga-edid-read", VGA) {
-			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
-			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
-		}
-
-		igt_describe(test_suspend_resume_hpd_desc);
-		connector_subtest("vga-hpd-after-suspend", VGA)
-			test_suspend_resume_hpd(&data, port,
-						SUSPEND_STATE_MEM,
-						SUSPEND_TEST_NONE);
-
-		igt_describe(test_suspend_resume_hpd_desc);
-		connector_subtest("vga-hpd-after-hibernate", VGA)
-			test_suspend_resume_hpd(&data, port,
-						SUSPEND_STATE_DISK,
-						SUSPEND_TEST_DEVICES);
-
-		igt_describe(test_hpd_without_ddc_desc);
-		connector_subtest("vga-hpd-without-ddc", VGA)
-			test_hpd_without_ddc(&data, port);
-
-		igt_describe(test_display_all_modes_desc);
-		connector_subtest("vga-frame-dump", VGA)
-			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
-					       CHAMELIUM_CHECK_ANALOG, 1);
-	}
-
-	igt_describe("Tests that operate on all connectors");
-	igt_subtest_group {
-
-		igt_fixture {
-			igt_require(data.port_count);
-		}
-
-		igt_describe(test_suspend_resume_hpd_common_desc);
-		igt_subtest("common-hpd-after-suspend")
-			test_suspend_resume_hpd_common(&data,
-						       SUSPEND_STATE_MEM,
-						       SUSPEND_TEST_NONE);
-
-		igt_describe(test_suspend_resume_hpd_common_desc);
-		igt_subtest("common-hpd-after-hibernate")
-			test_suspend_resume_hpd_common(&data,
-						       SUSPEND_STATE_DISK,
-						       SUSPEND_TEST_DEVICES);
-	}
-
-	igt_describe(test_hotplug_for_each_pipe_desc);
-	connector_subtest("vga-hpd-for-each-pipe", VGA)
-		test_hotplug_for_each_pipe(&data, port);
-
-	igt_fixture {
-		igt_display_fini(&data.display);
-		close(data.drm_fd);
-	}
-}
diff --git a/tests/chamelium/kms_chamelium_audio.c b/tests/chamelium/kms_chamelium_audio.c
new file mode 100644
index 00000000..4d13744c
--- /dev/null
+++ b/tests/chamelium/kms_chamelium_audio.c
@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: MIT
+/*
+ * A Chamelium test for testing the Audio functionality.
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Authors: Mark Yacoub <markyacoub@chromium.org>
+ */
+
+#include "igt_eld.h"
+#include "igt_infoframe.h"
+#include "kms_chamelium_helper.h"
+
+/* Playback parameters control the audio signal we synthesize and send */
+#define PLAYBACK_CHANNELS 2
+#define PLAYBACK_SAMPLES 1024
+
+/* Capture paremeters control the audio signal we receive */
+#define CAPTURE_SAMPLES 2048
+
+#define AUDIO_TIMEOUT 2000 /* ms */
+/* A streak of 3 gives confidence that the signal is good. */
+#define MIN_STREAK 3
+
+#define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */
+#define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */
+#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */
+
+struct audio_state {
+	struct alsa *alsa;
+	struct chamelium *chamelium;
+	struct chamelium_port *port;
+	struct chamelium_stream *stream;
+
+	/* The capture format is only available after capture has started. */
+	struct {
+		snd_pcm_format_t format;
+		int channels;
+		int rate;
+	} playback, capture;
+
+	char *name;
+	struct audio_signal *signal; /* for frequencies test only */
+	int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS];
+
+	size_t recv_pages;
+	int msec;
+
+	int dump_fd;
+	char *dump_path;
+
+	pthread_t thread;
+	atomic_bool run;
+	atomic_bool positive; /* for pulse test only */
+};
+
+/* TODO: enable >48KHz rates, these are not reliable */
+static int test_sampling_rates[] = {
+	32000, 44100, 48000,
+	/* 88200, */
+	/* 96000, */
+	/* 176400, */
+	/* 192000, */
+};
+
+static int test_sampling_rates_count =
+	sizeof(test_sampling_rates) / sizeof(int);
+
+/* Test frequencies (Hz): a sine signal will be generated for each.
+ *
+ * Depending on the sampling rate chosen, it might not be possible to properly
+ * detect the generated sine (see Nyquist–Shannon sampling theorem).
+ * Frequencies that can't be reliably detected will be automatically pruned in
+ * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be
+ * tested with a 192KHz sampling rate.
+ */
+static int test_frequencies[] = {
+	300, 600, 1200, 10000, 80000,
+};
+
+static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
+
+static const snd_pcm_format_t test_formats[] = {
+	SND_PCM_FORMAT_S16_LE,
+	SND_PCM_FORMAT_S24_LE,
+	SND_PCM_FORMAT_S32_LE,
+};
+
+static const size_t test_formats_count =
+	sizeof(test_formats) / sizeof(test_formats[0]);
+
+static void audio_state_init(struct audio_state *state, chamelium_data_t *data,
+			     struct alsa *alsa, struct chamelium_port *port,
+			     snd_pcm_format_t format, int channels, int rate)
+{
+	memset(state, 0, sizeof(*state));
+	state->dump_fd = -1;
+
+	state->alsa = alsa;
+	state->chamelium = data->chamelium;
+	state->port = port;
+
+	state->playback.format = format;
+	state->playback.channels = channels;
+	state->playback.rate = rate;
+
+	alsa_configure_output(alsa, format, channels, rate);
+
+	state->stream = chamelium_stream_init();
+	igt_assert_f(state->stream,
+		     "Failed to initialize Chamelium stream client\n");
+}
+
+static void audio_state_fini(struct audio_state *state)
+{
+	chamelium_stream_deinit(state->stream);
+	free(state->name);
+}
+
+static void *run_audio_thread(void *data)
+{
+	struct alsa *alsa = data;
+
+	alsa_run(alsa, -1);
+	return NULL;
+}
+
+static void audio_state_start(struct audio_state *state, const char *name)
+{
+	int ret;
+	bool ok;
+	size_t i, j;
+	enum chamelium_stream_realtime_mode stream_mode;
+	char dump_suffix[64];
+
+	free(state->name);
+	state->name = strdup(name);
+	state->recv_pages = 0;
+	state->msec = 0;
+
+	igt_debug("Starting %s test with playback format %s, "
+		  "sampling rate %d Hz and %d channels\n",
+		  name, snd_pcm_format_name(state->playback.format),
+		  state->playback.rate, state->playback.channels);
+
+	chamelium_start_capturing_audio(state->chamelium, state->port, false);
+
+	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
+	ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode);
+	igt_assert_f(ok, "Failed to start streaming audio capture\n");
+
+	/* Start playing audio */
+	state->run = true;
+	ret = pthread_create(&state->thread, NULL, run_audio_thread,
+			     state->alsa);
+	igt_assert_f(ret == 0, "Failed to start audio playback thread\n");
+
+	/* The Chamelium device only supports this PCM format. */
+	state->capture.format = SND_PCM_FORMAT_S32_LE;
+
+	/* Only after we've started playing audio, we can retrieve the capture
+	 * format used by the Chamelium device. */
+	chamelium_get_audio_format(state->chamelium, state->port,
+				   &state->capture.rate,
+				   &state->capture.channels);
+	if (state->capture.rate == 0) {
+		igt_debug("Audio receiver doesn't indicate the capture "
+			  "sampling rate, assuming it's %d Hz\n",
+			  state->playback.rate);
+		state->capture.rate = state->playback.rate;
+	}
+
+	chamelium_get_audio_channel_mapping(state->chamelium, state->port,
+					    state->channel_mapping);
+	/* Make sure we can capture all channels we send. */
+	for (i = 0; i < state->playback.channels; i++) {
+		ok = false;
+		for (j = 0; j < state->capture.channels; j++) {
+			if (state->channel_mapping[j] == i) {
+				ok = true;
+				break;
+			}
+		}
+		igt_assert_f(ok, "Cannot capture all channels\n");
+	}
+
+	if (igt_frame_dump_is_enabled()) {
+		snprintf(dump_suffix, sizeof(dump_suffix),
+			 "capture-%s-%s-%dch-%dHz", name,
+			 snd_pcm_format_name(state->playback.format),
+			 state->playback.channels, state->playback.rate);
+
+		state->dump_fd = audio_create_wav_file_s32_le(
+			dump_suffix, state->capture.rate,
+			state->capture.channels, &state->dump_path);
+		igt_assert_f(state->dump_fd >= 0,
+			     "Failed to create audio dump file\n");
+	}
+}
+
+static void audio_state_receive(struct audio_state *state, int32_t **recv,
+				size_t *recv_len)
+{
+	bool ok;
+	size_t page_count;
+	size_t recv_size;
+
+	ok = chamelium_stream_receive_realtime_audio(state->stream, &page_count,
+						     recv, recv_len);
+	igt_assert_f(ok, "Failed to receive audio from stream server\n");
+
+	state->msec = state->recv_pages * *recv_len /
+		      (double)state->capture.channels /
+		      (double)state->capture.rate * 1000;
+	state->recv_pages++;
+
+	if (state->dump_fd >= 0) {
+		recv_size = *recv_len * sizeof(int32_t);
+		igt_assert_f(write(state->dump_fd, *recv, recv_size) ==
+				     recv_size,
+			     "Failed to write to audio dump file\n");
+	}
+}
+
+static void audio_state_stop(struct audio_state *state, bool success)
+{
+	bool ok;
+	int ret;
+	struct chamelium_audio_file *audio_file;
+	enum igt_log_level log_level;
+
+	igt_debug("Stopping audio playback\n");
+	state->run = false;
+	ret = pthread_join(state->thread, NULL);
+	igt_assert_f(ret == 0, "Failed to join audio playback thread\n");
+
+	ok = chamelium_stream_stop_realtime_audio(state->stream);
+	igt_assert_f(ok, "Failed to stop streaming audio capture\n");
+
+	audio_file =
+		chamelium_stop_capturing_audio(state->chamelium, state->port);
+	if (audio_file) {
+		igt_debug("Audio file saved on the Chamelium in %s\n",
+			  audio_file->path);
+		chamelium_destroy_audio_file(audio_file);
+	}
+
+	if (state->dump_fd >= 0) {
+		close(state->dump_fd);
+		state->dump_fd = -1;
+
+		if (success) {
+			/* Test succeeded, no need to keep the captured data */
+			unlink(state->dump_path);
+		} else
+			igt_debug("Saved captured audio data to %s\n",
+				  state->dump_path);
+		free(state->dump_path);
+		state->dump_path = NULL;
+	}
+
+	if (success)
+		log_level = IGT_LOG_DEBUG;
+	else
+		log_level = IGT_LOG_CRITICAL;
+
+	igt_log(IGT_LOG_DOMAIN, log_level,
+		"Audio %s test result for format %s, "
+		"sampling rate %d Hz and %d channels: %s\n",
+		state->name, snd_pcm_format_name(state->playback.format),
+		state->playback.rate, state->playback.channels,
+		success ? "ALL GREEN" : "FAILED");
+}
+
+static void check_audio_infoframe(struct audio_state *state)
+{
+	struct chamelium_infoframe *infoframe;
+	struct infoframe_audio infoframe_audio;
+	struct infoframe_audio expected = { 0 };
+	bool ok;
+
+	if (!chamelium_supports_get_last_infoframe(state->chamelium)) {
+		igt_debug("Skipping audio InfoFrame check: "
+			  "Chamelium board doesn't support GetLastInfoFrame\n");
+		return;
+	}
+
+	expected.coding_type = INFOFRAME_AUDIO_CT_PCM;
+	expected.channel_count = state->playback.channels;
+	expected.sampling_freq = state->playback.rate;
+	expected.sample_size = snd_pcm_format_width(state->playback.format);
+
+	infoframe = chamelium_get_last_infoframe(state->chamelium, state->port,
+						 CHAMELIUM_INFOFRAME_AUDIO);
+	if (infoframe == NULL && state->playback.channels <= 2) {
+		/* Audio InfoFrames are optional for mono and stereo audio */
+		igt_debug("Skipping audio InfoFrame check: "
+			  "no InfoFrame received\n");
+		return;
+	}
+	igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n");
+
+	ok = infoframe_audio_parse(&infoframe_audio, infoframe->version,
+				   infoframe->payload, infoframe->payload_size);
+	chamelium_infoframe_destroy(infoframe);
+	igt_assert_f(ok, "failed to parse audio InfoFrame\n");
+
+	igt_debug("Checking audio InfoFrame:\n");
+	igt_debug("coding_type: got %d, expected %d\n",
+		  infoframe_audio.coding_type, expected.coding_type);
+	igt_debug("channel_count: got %d, expected %d\n",
+		  infoframe_audio.channel_count, expected.channel_count);
+	igt_debug("sampling_freq: got %d, expected %d\n",
+		  infoframe_audio.sampling_freq, expected.sampling_freq);
+	igt_debug("sample_size: got %d, expected %d\n",
+		  infoframe_audio.sample_size, expected.sample_size);
+
+	if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED)
+		igt_assert(infoframe_audio.coding_type == expected.coding_type);
+	if (infoframe_audio.channel_count >= 0)
+		igt_assert(infoframe_audio.channel_count ==
+			   expected.channel_count);
+	if (infoframe_audio.sampling_freq >= 0)
+		igt_assert(infoframe_audio.sampling_freq ==
+			   expected.sampling_freq);
+	if (infoframe_audio.sample_size >= 0)
+		igt_assert(infoframe_audio.sample_size == expected.sample_size);
+}
+
+static int audio_output_frequencies_callback(void *data, void *buffer,
+					     int samples)
+{
+	struct audio_state *state = data;
+	double *tmp;
+	size_t len;
+
+	len = samples * state->playback.channels;
+	tmp = malloc(len * sizeof(double));
+	audio_signal_fill(state->signal, tmp, samples);
+	audio_convert_to(buffer, tmp, len, state->playback.format);
+	free(tmp);
+
+	return state->run ? 0 : -1;
+}
+
+static bool test_audio_frequencies(struct audio_state *state)
+{
+	int freq, step;
+	int32_t *recv, *buf;
+	double *channel;
+	size_t i, j, streak;
+	size_t recv_len, buf_len, buf_cap, channel_len;
+	bool success;
+	int capture_chan;
+
+	state->signal = audio_signal_init(state->playback.channels,
+					  state->playback.rate);
+	igt_assert_f(state->signal, "Failed to initialize audio signal\n");
+
+	/* We'll choose different frequencies per channel to make sure they are
+	 * independent from each other. To do so, we'll add a different offset
+	 * to the base frequencies for each channel. We need to choose a big
+	 * enough offset so that we're sure to detect mixed up channels. We
+	 * choose an offset of two 2 bins in the final FFT to enforce a clear
+	 * difference.
+	 *
+	 * Note that we assume capture_rate == playback_rate. We'll assert this
+	 * later on. We cannot retrieve the capture rate before starting
+	 * playing audio, so we don't really have the choice.
+	 */
+	step = 2 * state->playback.rate / CAPTURE_SAMPLES;
+	for (i = 0; i < test_frequencies_count; i++) {
+		for (j = 0; j < state->playback.channels; j++) {
+			freq = test_frequencies[i] + j * step;
+			audio_signal_add_frequency(state->signal, freq, j);
+		}
+	}
+	audio_signal_synthesize(state->signal);
+
+	alsa_register_output_callback(state->alsa,
+				      audio_output_frequencies_callback, state,
+				      PLAYBACK_SAMPLES);
+
+	audio_state_start(state, "frequencies");
+
+	igt_assert_f(state->capture.rate == state->playback.rate,
+		     "Capture rate (%dHz) doesn't match playback rate (%dHz)\n",
+		     state->capture.rate, state->playback.rate);
+
+	/* Needs to be a multiple of 128, because that's the number of samples
+	 * we get per channel each time we receive an audio page from the
+	 * Chamelium device.
+	 *
+	 * Additionally, this value needs to be high enough to guarantee we
+	 * capture a full period of each sine we generate. If we capture 2048
+	 * samples at a 192KHz sampling rate, we get a full period for a >94Hz
+	 * sines. For lower sampling rates, the capture duration will be
+	 * longer.
+	 */
+	channel_len = CAPTURE_SAMPLES;
+	channel = malloc(sizeof(double) * channel_len);
+
+	buf_cap = state->capture.channels * channel_len;
+	buf = malloc(sizeof(int32_t) * buf_cap);
+	buf_len = 0;
+
+	recv = NULL;
+	recv_len = 0;
+
+	success = false;
+	streak = 0;
+	while (!success && state->msec < AUDIO_TIMEOUT) {
+		audio_state_receive(state, &recv, &recv_len);
+
+		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
+		buf_len += recv_len;
+
+		if (buf_len < buf_cap)
+			continue;
+		igt_assert(buf_len == buf_cap);
+
+		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
+
+		for (j = 0; j < state->playback.channels; j++) {
+			capture_chan = state->channel_mapping[j];
+			igt_assert(capture_chan >= 0);
+			igt_debug("Processing channel %zu (captured as "
+				  "channel %d)\n",
+				  j, capture_chan);
+
+			audio_extract_channel_s32_le(channel, channel_len, buf,
+						     buf_len,
+						     state->capture.channels,
+						     capture_chan);
+
+			if (audio_signal_detect(state->signal,
+						state->capture.rate, j, channel,
+						channel_len))
+				streak++;
+			else
+				streak = 0;
+		}
+
+		buf_len = 0;
+
+		success = streak == MIN_STREAK * state->playback.channels;
+	}
+
+	audio_state_stop(state, success);
+
+	free(recv);
+	free(buf);
+	free(channel);
+	audio_signal_fini(state->signal);
+
+	check_audio_infoframe(state);
+
+	return success;
+}
+
+static int audio_output_flatline_callback(void *data, void *buffer, int samples)
+{
+	struct audio_state *state = data;
+	double *tmp;
+	size_t len, i;
+
+	len = samples * state->playback.channels;
+	tmp = malloc(len * sizeof(double));
+	for (i = 0; i < len; i++)
+		tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE;
+	audio_convert_to(buffer, tmp, len, state->playback.format);
+	free(tmp);
+
+	return state->run ? 0 : -1;
+}
+
+static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos)
+{
+	double expected, min, max;
+	size_t i;
+	bool ok;
+
+	min = max = NAN;
+	for (i = 0; i < buf_len; i++) {
+		if (isnan(min) || buf[i] < min)
+			min = buf[i];
+		if (isnan(max) || buf[i] > max)
+			max = buf[i];
+	}
+
+	expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE;
+	ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY &&
+	      max <= expected + FLATLINE_AMPLITUDE_ACCURACY);
+	if (ok)
+		igt_debug("Flatline wave amplitude detected\n");
+	else
+		igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n",
+			  min, max);
+	return ok;
+}
+
+static ssize_t detect_falling_edge(double *buf, size_t buf_len)
+{
+	size_t i;
+
+	for (i = 0; i < buf_len; i++) {
+		if (buf[i] < 0)
+			return i;
+	}
+
+	return -1;
+}
+
+/** test_audio_flatline:
+ *
+ * Send a constant value (one positive, then a negative one) and check that:
+ *
+ * - The amplitude of the flatline is correct
+ * - All channels switch from a positive signal to a negative one at the same
+ *   time (ie. all channels are aligned)
+ */
+static bool test_audio_flatline(struct audio_state *state)
+{
+	bool success, amp_success, align_success;
+	int32_t *recv;
+	size_t recv_len, i, channel_len;
+	ssize_t j;
+	int streak, capture_chan;
+	double *channel;
+	int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS];
+
+	alsa_register_output_callback(state->alsa,
+				      audio_output_flatline_callback, state,
+				      PLAYBACK_SAMPLES);
+
+	/* Start by sending a positive signal */
+	state->positive = true;
+
+	audio_state_start(state, "flatline");
+
+	for (i = 0; i < state->playback.channels; i++)
+		falling_edges[i] = -1;
+
+	recv = NULL;
+	recv_len = 0;
+	amp_success = false;
+	streak = 0;
+	while (!amp_success && state->msec < AUDIO_TIMEOUT) {
+		audio_state_receive(state, &recv, &recv_len);
+
+		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
+
+		for (i = 0; i < state->playback.channels; i++) {
+			capture_chan = state->channel_mapping[i];
+			igt_assert(capture_chan >= 0);
+			igt_debug("Processing channel %zu (captured as "
+				  "channel %d)\n",
+				  i, capture_chan);
+
+			channel_len = audio_extract_channel_s32_le(
+				NULL, 0, recv, recv_len,
+				state->capture.channels, capture_chan);
+			channel = malloc(channel_len * sizeof(double));
+			audio_extract_channel_s32_le(channel, channel_len, recv,
+						     recv_len,
+						     state->capture.channels,
+						     capture_chan);
+
+			/* Check whether the amplitude is fine */
+			if (detect_flatline_amplitude(channel, channel_len,
+						      state->positive))
+				streak++;
+			else
+				streak = 0;
+
+			/* If we're now sending a negative signal, detect the
+			 * falling edge */
+			j = detect_falling_edge(channel, channel_len);
+			if (!state->positive && j >= 0) {
+				falling_edges[i] =
+					recv_len * state->recv_pages + j;
+			}
+
+			free(channel);
+		}
+
+		amp_success = streak == MIN_STREAK * state->playback.channels;
+
+		if (amp_success && state->positive) {
+			/* Switch to a negative signal after we've detected the
+			 * positive one. */
+			state->positive = false;
+			amp_success = false;
+			streak = 0;
+			igt_debug("Switching to negative square wave\n");
+		}
+	}
+
+	/* Check alignment between all channels by comparing the index of the
+	 * falling edge. */
+	align_success = true;
+	for (i = 0; i < state->playback.channels; i++) {
+		if (falling_edges[i] < 0) {
+			igt_critical(
+				"Falling edge not detected for channel %zu\n",
+				i);
+			align_success = false;
+			continue;
+		}
+
+		if (abs(falling_edges[0] - falling_edges[i]) >
+		    FLATLINE_ALIGN_ACCURACY) {
+			igt_critical("Channel alignment mismatch: "
+				     "channel 0 has a falling edge at index %d "
+				     "while channel %zu has index %d\n",
+				     falling_edges[0], i, falling_edges[i]);
+			align_success = false;
+		}
+	}
+
+	success = amp_success && align_success;
+	audio_state_stop(state, success);
+
+	free(recv);
+
+	return success;
+}
+
+static bool check_audio_configuration(struct alsa *alsa,
+				      snd_pcm_format_t format, int channels,
+				      int sampling_rate)
+{
+	if (!alsa_test_output_configuration(alsa, format, channels,
+					    sampling_rate)) {
+		igt_debug("Skipping test with format %s, sampling rate %d Hz "
+			  "and %d channels because at least one of the "
+			  "selected output devices doesn't support this "
+			  "configuration\n",
+			  snd_pcm_format_name(format), sampling_rate, channels);
+		return false;
+	}
+	/* TODO: the Chamelium device sends a malformed signal for some audio
+	 * configurations. See crbug.com/950917 */
+	if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) ||
+	    channels > 2) {
+		igt_debug("Skipping test with format %s, sampling rate %d Hz "
+			  "and %d channels because the Chamelium device "
+			  "doesn't support this configuration\n",
+			  snd_pcm_format_name(format), sampling_rate, channels);
+		return false;
+	}
+	return true;
+}
+
+static const char test_display_audio_desc[] =
+	"Playback various audio signals with various audio formats/rates, "
+	"capture them and check they are correct";
+static void test_display_audio(chamelium_data_t *data,
+			       struct chamelium_port *port,
+			       const char *audio_device,
+			       enum igt_custom_edid_type edid)
+{
+	bool run, success;
+	struct alsa *alsa;
+	int ret;
+	igt_output_t *output;
+	igt_plane_t *primary;
+	struct igt_fb fb;
+	drmModeModeInfo *mode;
+	drmModeConnector *connector;
+	int fb_id, i, j;
+	int channels, sampling_rate;
+	snd_pcm_format_t format;
+	struct audio_state state;
+
+	igt_require(alsa_has_exclusive_access());
+
+	/* Old Chamelium devices need an update for DisplayPort audio and
+	 * chamelium_get_audio_format support. */
+	igt_require(chamelium_has_audio_support(data->chamelium, port));
+
+	alsa = alsa_init();
+	igt_assert(alsa);
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	output = chamelium_prepare_output(data, port, edid);
+	connector = chamelium_port_get_connector(data->chamelium, port, false);
+	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	igt_assert(primary);
+
+	/* Enable the output because the receiver won't try to receive audio if
+	 * it doesn't receive video. */
+	igt_assert(connector->count_modes > 0);
+	mode = &connector->modes[0];
+
+	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
+					    mode->vdisplay, DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
+					    &fb);
+	igt_assert(fb_id > 0);
+
+	chamelium_enable_output(data, port, output, mode, &fb);
+
+	run = false;
+	success = true;
+	for (i = 0; i < test_sampling_rates_count; i++) {
+		for (j = 0; j < test_formats_count; j++) {
+			ret = alsa_open_output(alsa, audio_device);
+			igt_assert_f(ret >= 0, "Failed to open ALSA output\n");
+
+			/* TODO: playback on all 8 available channels (this
+			 * isn't supported by Chamelium devices yet, see
+			 * https://crbug.com/950917) */
+			format = test_formats[j];
+			channels = PLAYBACK_CHANNELS;
+			sampling_rate = test_sampling_rates[i];
+
+			if (!check_audio_configuration(alsa, format, channels,
+						       sampling_rate))
+				continue;
+
+			run = true;
+
+			audio_state_init(&state, data, alsa, port, format,
+					 channels, sampling_rate);
+			success &= test_audio_frequencies(&state);
+			success &= test_audio_flatline(&state);
+			audio_state_fini(&state);
+
+			alsa_close_output(alsa);
+		}
+	}
+
+	/* Make sure we tested at least one frequency and format. */
+	igt_assert(run);
+	/* Make sure all runs were successful. */
+	igt_assert(success);
+
+	igt_remove_fb(data->drm_fd, &fb);
+
+	drmModeFreeConnector(connector);
+
+	free(alsa);
+}
+
+static const char test_display_audio_edid_desc[] =
+	"Plug a connector with an EDID suitable for audio, check ALSA's "
+	"EDID-Like Data reports the correct audio parameters";
+static void test_display_audio_edid(chamelium_data_t *data,
+				    struct chamelium_port *port,
+				    enum igt_custom_edid_type edid)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	struct igt_fb fb;
+	drmModeModeInfo *mode;
+	drmModeConnector *connector;
+	int fb_id;
+	struct eld_entry eld;
+	struct eld_sad *sad;
+
+	igt_require(eld_is_supported());
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	output = chamelium_prepare_output(data, port, edid);
+	connector = chamelium_port_get_connector(data->chamelium, port, false);
+	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	igt_assert(primary);
+
+	/* Enable the output because audio cannot be played on inactive
+	 * connectors. */
+	igt_assert(connector->count_modes > 0);
+	mode = &connector->modes[0];
+
+	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
+					    mode->vdisplay, DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
+					    &fb);
+	igt_assert(fb_id > 0);
+
+	chamelium_enable_output(data, port, output, mode, &fb);
+
+	igt_assert(eld_get_igt(&eld));
+	igt_assert(eld.sads_len == 1);
+
+	sad = &eld.sads[0];
+	igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM);
+	igt_assert(sad->channels == 2);
+	igt_assert(sad->rates ==
+		   (CEA_SAD_SAMPLING_RATE_32KHZ | CEA_SAD_SAMPLING_RATE_44KHZ |
+		    CEA_SAD_SAMPLING_RATE_48KHZ));
+	igt_assert(sad->bits ==
+		   (CEA_SAD_SAMPLE_SIZE_16 | CEA_SAD_SAMPLE_SIZE_20 |
+		    CEA_SAD_SAMPLE_SIZE_24));
+
+	igt_remove_fb(data->drm_fd, &fb);
+
+	drmModeFreeConnector(connector);
+}
+
+IGT_TEST_DESCRIPTION("Testing Audio with a Chamelium board");
+igt_main
+{
+	chamelium_data_t data;
+	struct chamelium_port *port;
+	int p;
+
+	igt_fixture {
+		chamelium_init_test(&data);
+	}
+
+	igt_describe("DisplayPort tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_display_audio_desc);
+		connector_subtest("dp-audio", DisplayPort) test_display_audio(
+			&data, port, "HDMI", IGT_CUSTOM_EDID_DP_AUDIO);
+
+		igt_describe(test_display_audio_edid_desc);
+		connector_subtest("dp-audio-edid", DisplayPort)
+			test_display_audio_edid(&data, port,
+						IGT_CUSTOM_EDID_DP_AUDIO);
+	}
+
+	igt_describe("HDMI tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_HDMIA,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_display_audio_desc);
+		connector_subtest("hdmi-audio", HDMIA) test_display_audio(
+			&data, port, "HDMI", IGT_CUSTOM_EDID_HDMI_AUDIO);
+
+		igt_describe(test_display_audio_edid_desc);
+		connector_subtest("hdmi-audio-edid", HDMIA)
+			test_display_audio_edid(&data, port,
+						IGT_CUSTOM_EDID_HDMI_AUDIO);
+	}
+
+	igt_fixture {
+		igt_display_fini(&data.display);
+		close(data.drm_fd);
+	}
+}
diff --git a/tests/chamelium/kms_color_chamelium.c b/tests/chamelium/kms_chamelium_color.c
similarity index 100%
rename from tests/chamelium/kms_color_chamelium.c
rename to tests/chamelium/kms_chamelium_color.c
diff --git a/tests/chamelium/kms_chamelium_edid.c b/tests/chamelium/kms_chamelium_edid.c
new file mode 100644
index 00000000..c9e15f41
--- /dev/null
+++ b/tests/chamelium/kms_chamelium_edid.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: MIT
+/*
+ * A Chamelium test for testing the EDID functionality.
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Authors: Mark Yacoub <markyacoub@chromium.org>
+ */
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdatomic.h>
+#include <xf86drmMode.h>
+
+#include "config.h"
+#include "igt.h"
+#include "igt_chamelium.h"
+#include "igt_edid.h"
+#include "igt_eld.h"
+#include "igt_vc4.h"
+#include "igt_infoframe.h"
+#include "kms_chamelium_helper.h"
+#include "monitor_edids/dp_edids.h"
+#include "monitor_edids/hdmi_edids.h"
+#include "monitor_edids/monitor_edids_helper.h"
+
+#define MODE_CLOCK_ACCURACY 0.05 /* 5% */
+
+static void get_connectors_link_status_failed(chamelium_data_t *data,
+					      bool *link_status_failed)
+{
+	drmModeConnector *connector;
+	uint64_t link_status;
+	drmModePropertyPtr prop;
+	int p;
+
+	for (p = 0; p < data->port_count; p++) {
+		connector = chamelium_port_get_connector(data->chamelium,
+							 data->ports[p], false);
+
+		igt_assert(kmstest_get_property(
+			data->drm_fd, connector->connector_id,
+			DRM_MODE_OBJECT_CONNECTOR, "link-status", NULL,
+			&link_status, &prop));
+
+		link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD;
+
+		drmModeFreeProperty(prop);
+		drmModeFreeConnector(connector);
+	}
+}
+
+static void check_mode(struct chamelium *chamelium, struct chamelium_port *port,
+		       drmModeModeInfo *mode)
+{
+	struct chamelium_video_params video_params = { 0 };
+	double mode_clock;
+	int mode_hsync_offset, mode_vsync_offset;
+	int mode_hsync_width, mode_vsync_width;
+	int mode_hsync_polarity, mode_vsync_polarity;
+
+	chamelium_port_get_video_params(chamelium, port, &video_params);
+
+	mode_clock = (double)mode->clock / 1000;
+
+	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) {
+		/* this is what chamelium understands as offsets for DP */
+		mode_hsync_offset = mode->htotal - mode->hsync_start;
+		mode_vsync_offset = mode->vtotal - mode->vsync_start;
+	} else {
+		/* and this is what they are for other connectors */
+		mode_hsync_offset = mode->hsync_start - mode->hdisplay;
+		mode_vsync_offset = mode->vsync_start - mode->vdisplay;
+	}
+
+	mode_hsync_width = mode->hsync_end - mode->hsync_start;
+	mode_vsync_width = mode->vsync_end - mode->vsync_start;
+
+	mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+	mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+
+	igt_debug("Checking video mode:\n");
+	igt_debug("clock: got %f, expected %f ± %f%%\n", video_params.clock,
+		  mode_clock, MODE_CLOCK_ACCURACY * 100);
+	igt_debug("hactive: got %d, expected %d\n", video_params.hactive,
+		  mode->hdisplay);
+	igt_debug("vactive: got %d, expected %d\n", video_params.vactive,
+		  mode->vdisplay);
+	igt_debug("hsync_offset: got %d, expected %d\n",
+		  video_params.hsync_offset, mode_hsync_offset);
+	igt_debug("vsync_offset: got %d, expected %d\n",
+		  video_params.vsync_offset, mode_vsync_offset);
+	igt_debug("htotal: got %d, expected %d\n", video_params.htotal,
+		  mode->htotal);
+	igt_debug("vtotal: got %d, expected %d\n", video_params.vtotal,
+		  mode->vtotal);
+	igt_debug("hsync_width: got %d, expected %d\n",
+		  video_params.hsync_width, mode_hsync_width);
+	igt_debug("vsync_width: got %d, expected %d\n",
+		  video_params.vsync_width, mode_vsync_width);
+	igt_debug("hsync_polarity: got %d, expected %d\n",
+		  video_params.hsync_polarity, mode_hsync_polarity);
+	igt_debug("vsync_polarity: got %d, expected %d\n",
+		  video_params.vsync_polarity, mode_vsync_polarity);
+
+	if (!isnan(video_params.clock)) {
+		igt_assert(video_params.clock >
+			   mode_clock * (1 - MODE_CLOCK_ACCURACY));
+		igt_assert(video_params.clock <
+			   mode_clock * (1 + MODE_CLOCK_ACCURACY));
+	}
+	igt_assert(video_params.hactive == mode->hdisplay);
+	igt_assert(video_params.vactive == mode->vdisplay);
+	igt_assert(video_params.hsync_offset == mode_hsync_offset);
+	igt_assert(video_params.vsync_offset == mode_vsync_offset);
+	igt_assert(video_params.htotal == mode->htotal);
+	igt_assert(video_params.vtotal == mode->vtotal);
+	igt_assert(video_params.hsync_width == mode_hsync_width);
+	igt_assert(video_params.vsync_width == mode_vsync_width);
+	igt_assert(video_params.hsync_polarity == mode_hsync_polarity);
+	igt_assert(video_params.vsync_polarity == mode_vsync_polarity);
+}
+
+static const char igt_custom_edid_type_read_desc[] =
+	"Make sure the EDID exposed by KMS is the same as the screen's";
+static void igt_custom_edid_type_read(chamelium_data_t *data,
+				      struct chamelium_port *port,
+				      enum igt_custom_edid_type edid)
+{
+	drmModePropertyBlobPtr edid_blob = NULL;
+	drmModeConnector *connector;
+	size_t raw_edid_size;
+	const struct edid *raw_edid;
+	uint64_t edid_blob_id;
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	chamelium_set_edid(data, port, edid);
+	chamelium_plug(data->chamelium, port);
+	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
+					      port, DRM_MODE_CONNECTED);
+
+	igt_skip_on(chamelium_check_analog_bridge(data, port));
+
+	connector = chamelium_port_get_connector(data->chamelium, port, true);
+	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
+					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
+					&edid_blob_id, NULL));
+	igt_assert(edid_blob_id != 0);
+	edid_blob = drmModeGetPropertyBlob(data->drm_fd, edid_blob_id);
+	igt_assert(edid_blob);
+
+	raw_edid = chamelium_edid_get_raw(data->edids[edid], port);
+	raw_edid_size = edid_get_size(raw_edid);
+	igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0);
+
+	drmModeFreePropertyBlob(edid_blob);
+	drmModeFreeConnector(connector);
+}
+
+static const char igt_edid_stress_resolution_desc[] =
+	"Stress test the DUT by testing multiple EDIDs, one right after the other,"
+	"and ensure their validity by check the real screen resolution vs the"
+	"advertised mode resultion.";
+static void edid_stress_resolution(chamelium_data_t *data,
+				   struct chamelium_port *port,
+				   monitor_edid edids_list[],
+				   size_t edids_list_len)
+{
+	int i;
+	struct chamelium *chamelium = data->chamelium;
+	struct udev_monitor *mon = igt_watch_uevents();
+
+	for (i = 0; i < edids_list_len; ++i) {
+		struct chamelium_edid *chamelium_edid;
+		drmModeModeInfo mode;
+		struct igt_fb fb = { 0 };
+		igt_output_t *output;
+		enum pipe pipe;
+		bool is_video_stable;
+		int screen_res_w, screen_res_h;
+
+		monitor_edid *edid = &edids_list[i];
+		igt_info("Testing out the EDID for %s\n",
+			 monitor_edid_get_name(edid));
+
+		/* Getting and Setting the EDID on Chamelium. */
+		chamelium_edid =
+			get_chameleon_edid_from_monitor_edid(chamelium, edid);
+		chamelium_port_set_edid(data->chamelium, port, chamelium_edid);
+		free_chamelium_edid_from_monitor_edid(chamelium_edid);
+
+		igt_flush_uevents(mon);
+		chamelium_plug(chamelium, port);
+		chamelium_wait_for_connector_after_hotplug(data, mon, port,
+							   DRM_MODE_CONNECTED);
+		igt_flush_uevents(mon);
+
+		/* Setting an output on the screen to turn it on. */
+		mode = chamelium_get_mode_for_port(chamelium, port);
+		chamelium_create_fb_for_mode(data, &fb, &mode);
+		output = chamelium_get_output_for_port(data, port);
+		pipe = chamelium_get_pipe_for_output(&data->display, output);
+		igt_output_set_pipe(output, pipe);
+		chamelium_enable_output(data, port, output, &mode, &fb);
+
+		/* Capture the screen resolution and verify. */
+		is_video_stable = chamelium_port_wait_video_input_stable(
+			chamelium, port, 5);
+		igt_assert(is_video_stable);
+
+		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
+					      &screen_res_h);
+		igt_assert(screen_res_w == fb.width);
+		igt_assert(screen_res_h == fb.height);
+
+		// Clean up
+		igt_remove_fb(data->drm_fd, &fb);
+		igt_modeset_disable_all_outputs(&data->display);
+		chamelium_unplug(chamelium, port);
+	}
+
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+}
+
+static const char igt_edid_resolution_list_desc[] =
+	"Get an EDID with many modes of different configurations, set them on the screen and check the"
+	" screen resolution matches the mode resolution.";
+
+static void edid_resolution_list(chamelium_data_t *data,
+				 struct chamelium_port *port)
+{
+	struct chamelium *chamelium = data->chamelium;
+	struct udev_monitor *mon = igt_watch_uevents();
+	drmModeConnector *connector;
+	drmModeModeInfoPtr modes;
+	int count_modes;
+	int i;
+	igt_output_t *output;
+	enum pipe pipe;
+
+	chamelium_unplug(chamelium, port);
+	chamelium_set_edid(data, port, IGT_CUSTOM_EDID_FULL);
+
+	igt_flush_uevents(mon);
+	chamelium_plug(chamelium, port);
+	chamelium_wait_for_connector_after_hotplug(data, mon, port,
+						   DRM_MODE_CONNECTED);
+	igt_flush_uevents(mon);
+
+	connector = chamelium_port_get_connector(chamelium, port, true);
+	modes = connector->modes;
+	count_modes = connector->count_modes;
+
+	output = chamelium_get_output_for_port(data, port);
+	pipe = chamelium_get_pipe_for_output(&data->display, output);
+	igt_output_set_pipe(output, pipe);
+
+	for (i = 0; i < count_modes; ++i)
+		igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh);
+
+	for (i = 0; i < count_modes; ++i) {
+		struct igt_fb fb = { 0 };
+		bool is_video_stable;
+		int screen_res_w, screen_res_h;
+
+		igt_info("Testing #%d %s %uHz\n", i, modes[i].name,
+			 modes[i].vrefresh);
+
+		/* Set the screen mode with the one we chose. */
+		chamelium_create_fb_for_mode(data, &fb, &modes[i]);
+		chamelium_enable_output(data, port, output, &modes[i], &fb);
+		is_video_stable = chamelium_port_wait_video_input_stable(
+			chamelium, port, 10);
+		igt_assert(is_video_stable);
+
+		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
+					      &screen_res_h);
+		igt_assert_eq(screen_res_w, modes[i].hdisplay);
+		igt_assert_eq(screen_res_h, modes[i].vdisplay);
+
+		igt_remove_fb(data->drm_fd, &fb);
+	}
+
+	igt_modeset_disable_all_outputs(&data->display);
+	drmModeFreeConnector(connector);
+}
+
+static const char test_suspend_resume_edid_change_desc[] =
+	"Simulate a screen being unplugged and another screen being plugged "
+	"during suspend, check that a uevent is sent and connector status is "
+	"updated";
+static void test_suspend_resume_edid_change(chamelium_data_t *data,
+					    struct chamelium_port *port,
+					    enum igt_suspend_state state,
+					    enum igt_suspend_test test,
+					    enum igt_custom_edid_type edid,
+					    enum igt_custom_edid_type alt_edid)
+{
+	struct udev_monitor *mon = igt_watch_uevents();
+	bool link_status_failed[2][data->port_count];
+	int p;
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	/* Catch the event and flush all remaining ones. */
+	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
+	igt_flush_uevents(mon);
+
+	/* First plug in the port */
+	chamelium_set_edid(data, port, edid);
+	chamelium_plug(data->chamelium, port);
+	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
+
+	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
+					      port, DRM_MODE_CONNECTED);
+
+	/*
+	 * Change the edid before we suspend. On resume, the machine should
+	 * notice the EDID change and fire a hotplug event.
+	 */
+	chamelium_set_edid(data, port, alt_edid);
+
+	get_connectors_link_status_failed(data, link_status_failed[0]);
+
+	igt_flush_uevents(mon);
+
+	igt_system_suspend_autoresume(state, test);
+	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
+	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
+
+	get_connectors_link_status_failed(data, link_status_failed[1]);
+
+	for (p = 0; p < data->port_count; p++)
+		igt_skip_on(!link_status_failed[0][p] &&
+			    link_status_failed[1][p]);
+}
+
+static const char test_mode_timings_desc[] =
+	"For each mode of the IGT base EDID, perform a modeset and check the "
+	"mode detected by the Chamelium receiver matches the mode we set";
+static void test_mode_timings(chamelium_data_t *data,
+			      struct chamelium_port *port)
+{
+	int i, count_modes;
+
+	i = 0;
+	igt_require(chamelium_supports_get_video_params(data->chamelium));
+	do {
+		igt_output_t *output;
+		igt_plane_t *primary;
+		drmModeConnector *connector;
+		drmModeModeInfo *mode;
+		int fb_id;
+		struct igt_fb fb;
+
+		/*
+		 * let's reset state each mode so we will get the
+		 * HPD pulses realibably
+		 */
+		igt_modeset_disable_all_outputs(&data->display);
+		chamelium_reset_state(&data->display, data->chamelium, port,
+				      data->ports, data->port_count);
+
+		/*
+		 * modes may change due to mode pruining and link issues, so we
+		 * need to refresh the connector
+		 */
+		output = chamelium_prepare_output(data, port,
+						  IGT_CUSTOM_EDID_BASE);
+		connector = chamelium_port_get_connector(data->chamelium, port,
+							 false);
+		primary = igt_output_get_plane_type(output,
+						    DRM_PLANE_TYPE_PRIMARY);
+		igt_assert(primary);
+
+		/* we may skip some modes due to above but that's ok */
+		count_modes = connector->count_modes;
+		if (i >= count_modes)
+			break;
+
+		mode = &connector->modes[i];
+
+		fb_id = igt_create_color_pattern_fb(
+			data->drm_fd, mode->hdisplay, mode->vdisplay,
+			DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
+			&fb);
+		igt_assert(fb_id > 0);
+
+		chamelium_enable_output(data, port, output, mode, &fb);
+
+		/* Trigger the FSM */
+		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0);
+
+		check_mode(data->chamelium, port, mode);
+
+		igt_remove_fb(data->drm_fd, &fb);
+		drmModeFreeConnector(connector);
+	} while (++i < count_modes);
+}
+
+IGT_TEST_DESCRIPTION("Testing EDID with a Chamelium board");
+igt_main
+{
+	chamelium_data_t data;
+	struct chamelium_port *port;
+	int p;
+
+	igt_fixture {
+		chamelium_init_test(&data);
+	}
+
+	igt_describe("DisplayPort tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
+				data.port_count, 1);
+		}
+
+		igt_describe(igt_custom_edid_type_read_desc);
+		connector_subtest("dp-edid-read", DisplayPort)
+		{
+			igt_custom_edid_type_read(&data, port,
+						  IGT_CUSTOM_EDID_BASE);
+			igt_custom_edid_type_read(&data, port,
+						  IGT_CUSTOM_EDID_ALT);
+		}
+
+		igt_describe(igt_edid_stress_resolution_desc);
+		connector_subtest("dp-edid-stress-resolution-4k", DisplayPort)
+			edid_stress_resolution(&data, port, DP_EDIDS_4K,
+					       ARRAY_SIZE(DP_EDIDS_4K));
+
+		igt_describe(igt_edid_stress_resolution_desc);
+		connector_subtest("dp-edid-stress-resolution-non-4k",
+				  DisplayPort)
+			edid_stress_resolution(&data, port, DP_EDIDS_NON_4K,
+					       ARRAY_SIZE(DP_EDIDS_NON_4K));
+
+		igt_describe(igt_edid_resolution_list_desc);
+		connector_subtest("dp-edid-resolution-list", DisplayPort)
+			edid_resolution_list(&data, port);
+
+		igt_describe(test_suspend_resume_edid_change_desc);
+		connector_subtest("dp-edid-change-during-suspend", DisplayPort)
+			test_suspend_resume_edid_change(&data, port,
+							SUSPEND_STATE_MEM,
+							SUSPEND_TEST_NONE,
+							IGT_CUSTOM_EDID_BASE,
+							IGT_CUSTOM_EDID_ALT);
+
+		igt_describe(test_suspend_resume_edid_change_desc);
+		connector_subtest("dp-edid-change-during-hibernate",
+				  DisplayPort)
+			test_suspend_resume_edid_change(&data, port,
+							SUSPEND_STATE_DISK,
+							SUSPEND_TEST_DEVICES,
+							IGT_CUSTOM_EDID_BASE,
+							IGT_CUSTOM_EDID_ALT);
+
+		igt_describe(test_mode_timings_desc);
+		connector_subtest("dp-mode-timings", DisplayPort)
+			test_mode_timings(&data, port);
+	}
+
+	igt_describe("HDMI tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_HDMIA,
+				data.port_count, 1);
+		}
+
+		igt_describe(igt_custom_edid_type_read_desc);
+		connector_subtest("hdmi-edid-read", HDMIA)
+		{
+			igt_custom_edid_type_read(&data, port,
+						  IGT_CUSTOM_EDID_BASE);
+			igt_custom_edid_type_read(&data, port,
+						  IGT_CUSTOM_EDID_ALT);
+		}
+
+		igt_describe(igt_edid_stress_resolution_desc);
+		connector_subtest("hdmi-edid-stress-resolution-4k", HDMIA)
+			edid_stress_resolution(&data, port, HDMI_EDIDS_4K,
+					       ARRAY_SIZE(HDMI_EDIDS_4K));
+
+		igt_describe(igt_edid_stress_resolution_desc);
+		connector_subtest("hdmi-edid-stress-resolution-non-4k", HDMIA)
+			edid_stress_resolution(&data, port, HDMI_EDIDS_NON_4K,
+					       ARRAY_SIZE(HDMI_EDIDS_NON_4K));
+
+		igt_describe(test_suspend_resume_edid_change_desc);
+		connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
+			test_suspend_resume_edid_change(&data, port,
+							SUSPEND_STATE_MEM,
+							SUSPEND_TEST_NONE,
+							IGT_CUSTOM_EDID_BASE,
+							IGT_CUSTOM_EDID_ALT);
+
+		igt_describe(test_suspend_resume_edid_change_desc);
+		connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
+			test_suspend_resume_edid_change(&data, port,
+							SUSPEND_STATE_DISK,
+							SUSPEND_TEST_DEVICES,
+							IGT_CUSTOM_EDID_BASE,
+							IGT_CUSTOM_EDID_ALT);
+
+		igt_describe(test_mode_timings_desc);
+		connector_subtest("hdmi-mode-timings", HDMIA)
+			test_mode_timings(&data, port);
+	}
+
+	igt_describe("VGA tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_VGA,
+				data.port_count, 1);
+		}
+
+		igt_describe(igt_custom_edid_type_read_desc);
+		connector_subtest("vga-edid-read", VGA)
+		{
+			igt_custom_edid_type_read(&data, port,
+						  IGT_CUSTOM_EDID_BASE);
+			igt_custom_edid_type_read(&data, port,
+						  IGT_CUSTOM_EDID_ALT);
+		}
+	}
+
+	igt_fixture {
+		igt_display_fini(&data.display);
+		close(data.drm_fd);
+	}
+}
diff --git a/tests/chamelium/kms_chamelium_frames.c b/tests/chamelium/kms_chamelium_frames.c
new file mode 100644
index 00000000..008bc34b
--- /dev/null
+++ b/tests/chamelium/kms_chamelium_frames.c
@@ -0,0 +1,1085 @@
+/*
+ * Copyright © 2016 Red Hat 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 (including the next
+ * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
+ *
+ * Authors:
+ *    Lyude Paul <lyude@redhat.com>
+ */
+
+#include "igt_eld.h"
+#include "igt_infoframe.h"
+#include "kms_chamelium_helper.h"
+
+#define connector_dynamic_subtest(name__, type__)                   \
+	igt_subtest_with_dynamic(name__)                            \
+	for_each_port(p, port) if (chamelium_port_get_type(port) == \
+				   DRM_MODE_CONNECTOR_##type__)
+
+struct vic_mode {
+	int hactive, vactive;
+	int vrefresh; /* Hz */
+	uint32_t picture_ar;
+};
+
+static int chamelium_vga_modes[][2] = {
+	{ 1600, 1200 }, { 1920, 1200 }, { 1920, 1080 }, { 1680, 1050 },
+	{ 1280, 1024 }, { 1280, 960 },	{ 1440, 900 },	{ 1280, 800 },
+	{ 1024, 768 },	{ 1360, 768 },	{ 1280, 720 },	{ 800, 600 },
+	{ 640, 480 },	{ -1, -1 },
+};
+
+/* Maps Video Identification Codes to a mode */
+static const struct vic_mode vic_modes[] = {
+	[16] = {
+		.hactive = 1920,
+		.vactive = 1080,
+		.vrefresh = 60,
+		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
+	},
+};
+
+/* Maps aspect ratios to their mode flag */
+static const uint32_t mode_ar_flags[] = {
+	[DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9,
+};
+
+static bool prune_vga_mode(chamelium_data_t *data, drmModeModeInfo *mode)
+{
+	int i = 0;
+
+	while (chamelium_vga_modes[i][0] != -1) {
+		if (mode->hdisplay == chamelium_vga_modes[i][0] &&
+		    mode->vdisplay == chamelium_vga_modes[i][1])
+			return false;
+
+		i++;
+	}
+
+	return true;
+}
+
+static void do_test_display(chamelium_data_t *data, struct chamelium_port *port,
+			    igt_output_t *output, drmModeModeInfo *mode,
+			    uint32_t fourcc, enum chamelium_check check,
+			    int count)
+{
+	struct chamelium_fb_crc_async_data *fb_crc;
+	struct igt_fb frame_fb, fb;
+	int i, fb_id, captured_frame_count;
+	int frame_id;
+
+	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
+					 DRM_FORMAT_XRGB8888, 64, &fb);
+	igt_assert(fb_id > 0);
+
+	frame_id =
+		igt_fb_convert(&frame_fb, &fb, fourcc, DRM_FORMAT_MOD_LINEAR);
+	igt_assert(frame_id > 0);
+
+	if (check == CHAMELIUM_CHECK_CRC)
+		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
+								&fb);
+
+	chamelium_enable_output(data, port, output, mode, &frame_fb);
+
+	if (check == CHAMELIUM_CHECK_CRC) {
+		igt_crc_t *expected_crc;
+		igt_crc_t *crc;
+
+		/* We want to keep the display running for a little bit, since
+		 * there's always the potential the driver isn't able to keep
+		 * the display running properly for very long
+		 */
+		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count);
+		crc = chamelium_read_captured_crcs(data->chamelium,
+						   &captured_frame_count);
+
+		igt_assert(captured_frame_count == count);
+
+		igt_debug("Captured %d frames\n", captured_frame_count);
+
+		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
+
+		for (i = 0; i < captured_frame_count; i++)
+			chamelium_assert_crc_eq_or_dump(
+				data->chamelium, expected_crc, &crc[i], &fb, i);
+
+		free(expected_crc);
+		free(crc);
+	} else if (check == CHAMELIUM_CHECK_ANALOG ||
+		   check == CHAMELIUM_CHECK_CHECKERBOARD) {
+		struct chamelium_frame_dump *dump;
+
+		igt_assert(count == 1);
+
+		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
+						  0, 0);
+
+		if (check == CHAMELIUM_CHECK_ANALOG)
+			chamelium_crop_analog_frame(dump, mode->hdisplay,
+						    mode->vdisplay);
+
+		chamelium_assert_frame_match_or_dump(data->chamelium, port,
+						     dump, &fb, check);
+		chamelium_destroy_frame_dump(dump);
+	}
+
+	igt_remove_fb(data->drm_fd, &frame_fb);
+	igt_remove_fb(data->drm_fd, &fb);
+}
+
+static enum infoframe_avi_picture_aspect_ratio
+get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
+{
+	/* The AVI picture aspect ratio field only supports 4:3 and 16:9 */
+	switch (aspect_ratio) {
+	case DRM_MODE_PICTURE_ASPECT_4_3:
+		return INFOFRAME_AVI_PIC_AR_4_3;
+	case DRM_MODE_PICTURE_ASPECT_16_9:
+		return INFOFRAME_AVI_PIC_AR_16_9;
+	default:
+		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
+	}
+}
+
+static bool vic_mode_matches_drm(const struct vic_mode *vic_mode,
+				 drmModeModeInfo *drm_mode)
+{
+	uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar];
+
+	return vic_mode->hactive == drm_mode->hdisplay &&
+	       vic_mode->vactive == drm_mode->vdisplay &&
+	       vic_mode->vrefresh == drm_mode->vrefresh &&
+	       ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
+}
+
+static void randomize_plane_stride(chamelium_data_t *data, uint32_t width,
+				   uint32_t height, uint32_t format,
+				   uint64_t modifier, size_t *stride)
+{
+	size_t stride_min;
+	uint32_t max_tile_w = 4, tile_w, tile_h;
+	int i;
+	struct igt_fb dummy;
+
+	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
+
+	/* Randomize the stride to less than twice the minimum. */
+	*stride = (rand() % stride_min) + stride_min;
+
+	/*
+	 * Create a dummy FB to determine bpp for each plane, and calculate
+	 * the maximum tile width from that.
+	 */
+	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
+	for (i = 0; i < dummy.num_planes; i++) {
+		igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i],
+				     &tile_w, &tile_h);
+
+		if (tile_w > max_tile_w)
+			max_tile_w = tile_w;
+	}
+	igt_remove_fb(data->drm_fd, &dummy);
+
+	/*
+	 * Pixman requires the stride to be aligned to 32-bits, which is
+	 * reflected in the initial value of max_tile_w and the hw
+	 * may require a multiple of tile width, choose biggest of the 2.
+	 */
+	*stride = ALIGN(*stride, max_tile_w);
+}
+
+static void update_tiled_modifier(igt_plane_t *plane, uint32_t width,
+				  uint32_t height, uint32_t format,
+				  uint64_t *modifier)
+{
+	if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) {
+		/* Randomize the column height to less than twice the minimum.
+		 */
+		size_t column_height = (rand() % height) + height;
+
+		igt_debug(
+			"Selecting VC4 SAND256 tiling with column height %ld\n",
+			column_height);
+
+		*modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(
+			column_height);
+	}
+}
+
+static void randomize_plane_setup(chamelium_data_t *data, igt_plane_t *plane,
+				  drmModeModeInfo *mode, uint32_t *width,
+				  uint32_t *height, uint32_t *format,
+				  uint64_t *modifier, bool allow_yuv)
+{
+	int min_dim;
+	uint32_t idx[plane->format_mod_count];
+	unsigned int count = 0;
+	unsigned int i;
+
+	/* First pass to count the supported formats. */
+	for (i = 0; i < plane->format_mod_count; i++)
+		if (igt_fb_supported_format(plane->formats[i]) &&
+		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
+			idx[count++] = i;
+
+	igt_assert(count > 0);
+
+	i = idx[rand() % count];
+	*format = plane->formats[i];
+	*modifier = plane->modifiers[i];
+
+	update_tiled_modifier(plane, *width, *height, *format, modifier);
+
+	/*
+	 * Randomize width and height in the mode dimensions range.
+	 *
+	 * Restrict to a min of 2 * min_dim, this way src_w/h are always at
+	 * least min_dim, because src_w = width - (rand % w / 2).
+	 *
+	 * Use a minimum dimension of 16 for YUV, because planar YUV
+	 * subsamples the UV plane.
+	 */
+	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
+
+	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
+	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
+}
+
+static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h,
+			    uint32_t src_x, uint32_t src_y, uint32_t crtc_w,
+			    uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y,
+			    struct igt_fb *fb)
+{
+	igt_plane_set_fb(plane, fb);
+
+	igt_plane_set_position(plane, crtc_x, crtc_y);
+	igt_plane_set_size(plane, crtc_w, crtc_h);
+
+	igt_fb_set_position(fb, plane, src_x, src_y);
+	igt_fb_set_size(fb, plane, src_w, src_h);
+}
+
+static void randomize_plane_coordinates(
+	chamelium_data_t *data, igt_plane_t *plane, drmModeModeInfo *mode,
+	struct igt_fb *fb, uint32_t *src_w, uint32_t *src_h, uint32_t *src_x,
+	uint32_t *src_y, uint32_t *crtc_w, uint32_t *crtc_h, int32_t *crtc_x,
+	int32_t *crtc_y, bool allow_scaling)
+{
+	bool is_yuv = igt_format_is_yuv(fb->drm_format);
+	uint32_t width = fb->width, height = fb->height;
+	double ratio;
+	int ret;
+
+	/* Randomize source offset in the first half of the original size. */
+	*src_x = rand() % (width / 2);
+	*src_y = rand() % (height / 2);
+
+	/* The source size only includes the active source area. */
+	*src_w = width - *src_x;
+	*src_h = height - *src_y;
+
+	if (allow_scaling) {
+		*crtc_w = (rand() % mode->hdisplay) + 1;
+		*crtc_h = (rand() % mode->vdisplay) + 1;
+
+		/*
+		 * Don't bother with scaling if dimensions are quite close in
+		 * order to get non-scaling cases more frequently. Also limit
+		 * scaling to 3x to avoid aggressive filtering that makes
+		 * comparison less reliable, and don't go above 2x downsampling
+		 * to avoid possible hw limitations.
+		 */
+
+		ratio = ((double)*crtc_w / *src_w);
+		if (ratio < 0.5)
+			*src_w = *crtc_w * 2;
+		else if (ratio > 0.8 && ratio < 1.2)
+			*crtc_w = *src_w;
+		else if (ratio > 3.0)
+			*crtc_w = *src_w * 3;
+
+		ratio = ((double)*crtc_h / *src_h);
+		if (ratio < 0.5)
+			*src_h = *crtc_h * 2;
+		else if (ratio > 0.8 && ratio < 1.2)
+			*crtc_h = *src_h;
+		else if (ratio > 3.0)
+			*crtc_h = *src_h * 3;
+	} else {
+		*crtc_w = *src_w;
+		*crtc_h = *src_h;
+	}
+
+	if (*crtc_w != *src_w || *crtc_h != *src_h) {
+		/*
+		 * When scaling is involved, make sure to not go off-bounds or
+		 * scaled clipping may result in decimal dimensions, that most
+		 * drivers don't support.
+		 */
+		if (*crtc_w < mode->hdisplay)
+			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
+		else
+			*crtc_x = 0;
+
+		if (*crtc_h < mode->vdisplay)
+			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
+		else
+			*crtc_y = 0;
+	} else {
+		/*
+		 * Randomize the on-crtc position and allow the plane to go
+		 * off-display by less than half of its on-crtc dimensions.
+		 */
+		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
+		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
+	}
+
+	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, *crtc_h,
+			*crtc_x, *crtc_y, fb);
+	ret = igt_display_try_commit_atomic(
+		&data->display,
+		DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
+		NULL);
+	if (!ret)
+		return;
+
+	/* Coordinates are logged in the dumped debug log, so only report w/h on
+	 * failure here. */
+	igt_assert_f(ret != -ENOSPC,
+		     "Failure in testcase, invalid coordinates on a %ux%u fb\n",
+		     width, height);
+
+	/* Make YUV coordinates a multiple of 2 and retry the math. */
+	if (is_yuv) {
+		*src_x &= ~1;
+		*src_y &= ~1;
+		*src_w &= ~1;
+		*src_h &= ~1;
+		/* To handle 1:1 scaling, clear crtc_w/h too. */
+		*crtc_w &= ~1;
+		*crtc_h &= ~1;
+
+		if (*crtc_x < 0 && (*crtc_x & 1))
+			(*crtc_x)++;
+		else
+			*crtc_x &= ~1;
+
+		/* If negative, round up to 0 instead of down */
+		if (*crtc_y < 0 && (*crtc_y & 1))
+			(*crtc_y)++;
+		else
+			*crtc_y &= ~1;
+
+		configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
+				*crtc_h, *crtc_x, *crtc_y, fb);
+		ret = igt_display_try_commit_atomic(
+			&data->display,
+			DRM_MODE_ATOMIC_TEST_ONLY |
+				DRM_MODE_ATOMIC_ALLOW_MODESET,
+			NULL);
+		if (!ret)
+			return;
+	}
+
+	igt_assert(!ret || allow_scaling);
+	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
+		 ((double)*crtc_w / *src_w), ((double)*crtc_h / *src_h));
+
+	*crtc_w = *src_w;
+	*crtc_h = *src_h;
+
+	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, *crtc_h,
+			*crtc_x, *crtc_y, fb);
+	igt_display_commit_atomic(&data->display,
+				  DRM_MODE_ATOMIC_TEST_ONLY |
+					  DRM_MODE_ATOMIC_ALLOW_MODESET,
+				  NULL);
+}
+
+static void blit_plane_cairo(chamelium_data_t *data, cairo_surface_t *result,
+			     uint32_t src_w, uint32_t src_h, uint32_t src_x,
+			     uint32_t src_y, uint32_t crtc_w, uint32_t crtc_h,
+			     int32_t crtc_x, int32_t crtc_y, struct igt_fb *fb)
+{
+	cairo_surface_t *surface;
+	cairo_surface_t *clipped_surface;
+	cairo_t *cr;
+
+	surface = igt_get_cairo_surface(data->drm_fd, fb);
+
+	if (src_x || src_y) {
+		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
+							     src_w, src_h);
+
+		cr = cairo_create(clipped_surface);
+
+		cairo_translate(cr, -1. * src_x, -1. * src_y);
+
+		cairo_set_source_surface(cr, surface, 0, 0);
+
+		cairo_paint(cr);
+		cairo_surface_flush(clipped_surface);
+
+		cairo_destroy(cr);
+	} else {
+		clipped_surface = surface;
+	}
+
+	cr = cairo_create(result);
+
+	cairo_translate(cr, crtc_x, crtc_y);
+
+	if (src_w != crtc_w || src_h != crtc_h) {
+		cairo_scale(cr, (double)crtc_w / src_w, (double)crtc_h / src_h);
+	}
+
+	cairo_set_source_surface(cr, clipped_surface, 0, 0);
+	cairo_surface_destroy(clipped_surface);
+
+	if (src_w != crtc_w || src_h != crtc_h) {
+		cairo_pattern_set_filter(cairo_get_source(cr),
+					 CAIRO_FILTER_BILINEAR);
+		cairo_pattern_set_extend(cairo_get_source(cr),
+					 CAIRO_EXTEND_NONE);
+	}
+
+	cairo_paint(cr);
+	cairo_surface_flush(result);
+
+	cairo_destroy(cr);
+}
+
+static void prepare_randomized_plane(chamelium_data_t *data,
+				     drmModeModeInfo *mode, igt_plane_t *plane,
+				     struct igt_fb *overlay_fb,
+				     unsigned int index,
+				     cairo_surface_t *result_surface,
+				     bool allow_scaling, bool allow_yuv)
+{
+	struct igt_fb pattern_fb;
+	uint32_t overlay_fb_w, overlay_fb_h;
+	uint32_t overlay_src_w, overlay_src_h;
+	uint32_t overlay_src_x, overlay_src_y;
+	int32_t overlay_crtc_x, overlay_crtc_y;
+	uint32_t overlay_crtc_w, overlay_crtc_h;
+	uint32_t format;
+	uint64_t modifier;
+	size_t stride;
+	bool tiled;
+	int fb_id;
+
+	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
+			      &format, &modifier, allow_yuv);
+
+	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
+	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n", index,
+		  overlay_fb_w, overlay_fb_h, igt_format_str(format),
+		  tiled ? "tiled" : "linear");
+
+	/* Get a pattern framebuffer for the overlay plane. */
+	fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h,
+					 DRM_FORMAT_XRGB8888, 32, &pattern_fb);
+	igt_assert(fb_id > 0);
+
+	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h, format,
+			       modifier, &stride);
+
+	igt_debug("Plane %d: stride %ld\n", index, stride);
+
+	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
+					   modifier, stride);
+	igt_assert(fb_id > 0);
+
+	randomize_plane_coordinates(data, plane, mode, overlay_fb,
+				    &overlay_src_w, &overlay_src_h,
+				    &overlay_src_x, &overlay_src_y,
+				    &overlay_crtc_w, &overlay_crtc_h,
+				    &overlay_crtc_x, &overlay_crtc_y,
+				    allow_scaling);
+
+	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index, overlay_src_w,
+		  overlay_src_h);
+	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
+		  overlay_src_x, overlay_src_y);
+	igt_debug("Plane %d: on-crtc size %dx%d\n", index, overlay_crtc_w,
+		  overlay_crtc_h);
+	igt_debug("Plane %d: on-crtc position %dx%d\n", index, overlay_crtc_x,
+		  overlay_crtc_y);
+
+	blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h,
+			 overlay_src_x, overlay_src_y, overlay_crtc_w,
+			 overlay_crtc_h, overlay_crtc_x, overlay_crtc_y,
+			 &pattern_fb);
+
+	/* Remove the original pattern framebuffer. */
+	igt_remove_fb(data->drm_fd, &pattern_fb);
+}
+
+static const char test_display_one_mode_desc[] =
+	"Pick the first mode of the IGT base EDID, display and capture a few "
+	"frames, then check captured frames are correct";
+static void test_display_one_mode(chamelium_data_t *data,
+				  struct chamelium_port *port, uint32_t fourcc,
+				  enum chamelium_check check, int count)
+{
+	drmModeConnector *connector;
+	drmModeModeInfo *mode;
+	igt_output_t *output;
+	igt_plane_t *primary;
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	output = chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
+	connector = chamelium_port_get_connector(data->chamelium, port, false);
+	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	igt_assert(primary);
+
+	igt_require(igt_plane_has_format_mod(primary, fourcc,
+					     DRM_FORMAT_MOD_LINEAR));
+
+	mode = &connector->modes[0];
+	if (check == CHAMELIUM_CHECK_ANALOG) {
+		bool bridge = chamelium_check_analog_bridge(data, port);
+
+		igt_assert(!(bridge && prune_vga_mode(data, mode)));
+	}
+
+	do_test_display(data, port, output, mode, fourcc, check, count);
+
+	drmModeFreeConnector(connector);
+}
+
+static const char test_display_all_modes_desc[] =
+	"For each mode of the IGT base EDID, display and capture a few "
+	"frames, then check captured frames are correct";
+static void test_display_all_modes(chamelium_data_t *data,
+				   struct chamelium_port *port, uint32_t fourcc,
+				   enum chamelium_check check, int count)
+{
+	bool bridge;
+	int i, count_modes;
+
+	if (check == CHAMELIUM_CHECK_ANALOG)
+		bridge = chamelium_check_analog_bridge(data, port);
+
+	i = 0;
+	do {
+		igt_output_t *output;
+		igt_plane_t *primary;
+		drmModeConnector *connector;
+		drmModeModeInfo *mode;
+
+		/*
+		 * let's reset state each mode so we will get the
+		 * HPD pulses realibably
+		 */
+		igt_modeset_disable_all_outputs(&data->display);
+		chamelium_reset_state(&data->display, data->chamelium, port,
+				      data->ports, data->port_count);
+
+		/*
+		 * modes may change due to mode pruining and link issues, so we
+		 * need to refresh the connector
+		 */
+		output = chamelium_prepare_output(data, port,
+						  IGT_CUSTOM_EDID_BASE);
+		connector = chamelium_port_get_connector(data->chamelium, port,
+							 false);
+		primary = igt_output_get_plane_type(output,
+						    DRM_PLANE_TYPE_PRIMARY);
+		igt_assert(primary);
+		igt_require(igt_plane_has_format_mod(primary, fourcc,
+						     DRM_FORMAT_MOD_LINEAR));
+
+		/* we may skip some modes due to above but that's ok */
+		count_modes = connector->count_modes;
+		if (i >= count_modes)
+			break;
+
+		mode = &connector->modes[i];
+
+		if (check == CHAMELIUM_CHECK_ANALOG && bridge &&
+		    prune_vga_mode(data, mode))
+			continue;
+
+		do_test_display(data, port, output, mode, fourcc, check, count);
+		drmModeFreeConnector(connector);
+	} while (++i < count_modes);
+}
+
+static const char test_display_frame_dump_desc[] =
+	"For each mode of the IGT base EDID, display and capture a few "
+	"frames, then download the captured frames and compare them "
+	"bit-by-bit to the sent ones";
+static void test_display_frame_dump(chamelium_data_t *data,
+				    struct chamelium_port *port)
+{
+	int i, count_modes;
+
+	i = 0;
+	do {
+		igt_output_t *output;
+		igt_plane_t *primary;
+		struct igt_fb fb;
+		struct chamelium_frame_dump *frame;
+		drmModeModeInfo *mode;
+		drmModeConnector *connector;
+		int fb_id, j;
+
+		/*
+		 * let's reset state each mode so we will get the
+		 * HPD pulses realibably
+		 */
+		igt_modeset_disable_all_outputs(&data->display);
+		chamelium_reset_state(&data->display, data->chamelium, port,
+				      data->ports, data->port_count);
+
+		/*
+		 * modes may change due to mode pruining and link issues, so we
+		 * need to refresh the connector
+		 */
+		output = chamelium_prepare_output(data, port,
+						  IGT_CUSTOM_EDID_BASE);
+		connector = chamelium_port_get_connector(data->chamelium, port,
+							 false);
+		primary = igt_output_get_plane_type(output,
+						    DRM_PLANE_TYPE_PRIMARY);
+		igt_assert(primary);
+
+		/* we may skip some modes due to above but that's ok */
+		count_modes = connector->count_modes;
+		if (i >= count_modes)
+			break;
+
+		mode = &connector->modes[i];
+
+		fb_id = igt_create_color_pattern_fb(
+			data->drm_fd, mode->hdisplay, mode->vdisplay,
+			DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
+			&fb);
+		igt_assert(fb_id > 0);
+
+		chamelium_enable_output(data, port, output, mode, &fb);
+
+		igt_debug("Reading frame dumps from Chamelium...\n");
+		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
+		for (j = 0; j < 5; j++) {
+			frame = chamelium_read_captured_frame(data->chamelium,
+							      j);
+			chamelium_assert_frame_eq(data->chamelium, frame, &fb);
+			chamelium_destroy_frame_dump(frame);
+		}
+
+		igt_remove_fb(data->drm_fd, &fb);
+		drmModeFreeConnector(connector);
+	} while (++i < count_modes);
+}
+
+static const char test_display_aspect_ratio_desc[] =
+	"Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and "
+	"check they include the relevant fields";
+static void test_display_aspect_ratio(chamelium_data_t *data,
+				      struct chamelium_port *port)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeConnector *connector;
+	drmModeModeInfo *mode;
+	int fb_id, i;
+	struct igt_fb fb;
+	bool found, ok;
+	struct chamelium_infoframe *infoframe;
+	struct infoframe_avi infoframe_avi;
+	uint8_t vic = 16; /* TODO: test more VICs */
+	const struct vic_mode *vic_mode;
+	uint32_t aspect_ratio;
+	enum infoframe_avi_picture_aspect_ratio frame_ar;
+
+	igt_require(chamelium_supports_get_last_infoframe(data->chamelium));
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	output = chamelium_prepare_output(data, port,
+					  IGT_CUSTOM_EDID_ASPECT_RATIO);
+	connector = chamelium_port_get_connector(data->chamelium, port, false);
+	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	igt_assert(primary);
+
+	vic_mode = &vic_modes[vic];
+	aspect_ratio = vic_mode->picture_ar;
+
+	found = false;
+	igt_assert(connector->count_modes > 0);
+	for (i = 0; i < connector->count_modes; i++) {
+		mode = &connector->modes[i];
+
+		if (vic_mode_matches_drm(vic_mode, mode)) {
+			found = true;
+			break;
+		}
+	}
+	igt_assert_f(found,
+		     "Failed to find mode with the correct aspect ratio\n");
+
+	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
+					    mode->vdisplay, DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
+					    &fb);
+	igt_assert(fb_id > 0);
+
+	chamelium_enable_output(data, port, output, mode, &fb);
+
+	infoframe = chamelium_get_last_infoframe(data->chamelium, port,
+						 CHAMELIUM_INFOFRAME_AVI);
+	igt_assert_f(infoframe, "AVI InfoFrame not received\n");
+
+	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
+				 infoframe->payload, infoframe->payload_size);
+	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
+
+	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
+
+	igt_debug("Checking AVI InfoFrame\n");
+	igt_debug("Picture aspect ratio: got %d, expected %d\n",
+		  infoframe_avi.picture_aspect_ratio, frame_ar);
+	igt_debug("Video Identification Code (VIC): got %d, expected %d\n",
+		  infoframe_avi.vic, vic);
+
+	igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar);
+	igt_assert(infoframe_avi.vic == vic);
+
+	chamelium_infoframe_destroy(infoframe);
+	igt_remove_fb(data->drm_fd, &fb);
+	drmModeFreeConnector(connector);
+}
+
+static const char test_display_planes_random_desc[] =
+	"Setup a few overlay planes with random parameters, capture the frame "
+	"and check it matches the expected output";
+static void test_display_planes_random(chamelium_data_t *data,
+				       struct chamelium_port *port,
+				       enum chamelium_check check)
+{
+	igt_output_t *output;
+	drmModeModeInfo *mode;
+	igt_plane_t *primary_plane;
+	struct igt_fb primary_fb;
+	struct igt_fb result_fb;
+	struct igt_fb *overlay_fbs;
+	igt_crc_t *crc;
+	igt_crc_t *expected_crc;
+	struct chamelium_fb_crc_async_data *fb_crc;
+	unsigned int overlay_planes_max = 0;
+	unsigned int overlay_planes_count;
+	cairo_surface_t *result_surface;
+	int captured_frame_count;
+	bool allow_scaling;
+	bool allow_yuv;
+	unsigned int i;
+	unsigned int fb_id;
+
+	switch (check) {
+	case CHAMELIUM_CHECK_CRC:
+		allow_scaling = false;
+		allow_yuv = false;
+		break;
+	case CHAMELIUM_CHECK_CHECKERBOARD:
+		allow_scaling = true;
+		allow_yuv = true;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	srand(time(NULL));
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	/* Find the connector and pipe. */
+	output = chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
+
+	mode = igt_output_get_mode(output);
+
+	/* Get a framebuffer for the primary plane. */
+	primary_plane =
+		igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	igt_assert(primary_plane);
+
+	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
+					 DRM_FORMAT_XRGB8888, 64, &primary_fb);
+	igt_assert(fb_id > 0);
+
+	/* Get a framebuffer for the cairo composition result. */
+	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay, mode->vdisplay,
+			      DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
+			      &result_fb);
+	igt_assert(fb_id > 0);
+
+	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
+
+	/* Paint the primary framebuffer on the result surface. */
+	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
+			 &primary_fb);
+
+	/* Configure the primary plane. */
+	igt_plane_set_fb(primary_plane, &primary_fb);
+
+	overlay_planes_max =
+		igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY);
+
+	/* Limit the number of planes to a reasonable scene. */
+	overlay_planes_max = min(overlay_planes_max, 4u);
+
+	overlay_planes_count = (rand() % overlay_planes_max) + 1;
+	igt_debug("Using %d overlay planes\n", overlay_planes_count);
+
+	overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count);
+
+	for (i = 0; i < overlay_planes_count; i++) {
+		struct igt_fb *overlay_fb = &overlay_fbs[i];
+		igt_plane_t *plane = igt_output_get_plane_type_index(
+			output, DRM_PLANE_TYPE_OVERLAY, i);
+		igt_assert(plane);
+
+		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
+					 result_surface, allow_scaling,
+					 allow_yuv);
+	}
+
+	cairo_surface_destroy(result_surface);
+
+	if (check == CHAMELIUM_CHECK_CRC)
+		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
+								&result_fb);
+
+	igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+	if (check == CHAMELIUM_CHECK_CRC) {
+		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1);
+		crc = chamelium_read_captured_crcs(data->chamelium,
+						   &captured_frame_count);
+
+		igt_assert(captured_frame_count == 1);
+
+		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
+
+		chamelium_assert_crc_eq_or_dump(data->chamelium, expected_crc,
+						crc, &result_fb, 0);
+
+		free(expected_crc);
+		free(crc);
+	} else if (check == CHAMELIUM_CHECK_CHECKERBOARD) {
+		struct chamelium_frame_dump *dump;
+
+		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
+						  0, 0);
+		chamelium_assert_frame_match_or_dump(data->chamelium, port,
+						     dump, &result_fb, check);
+		chamelium_destroy_frame_dump(dump);
+	}
+
+	for (i = 0; i < overlay_planes_count; i++)
+		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
+
+	free(overlay_fbs);
+
+	igt_remove_fb(data->drm_fd, &primary_fb);
+	igt_remove_fb(data->drm_fd, &result_fb);
+}
+
+IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board");
+igt_main
+{
+	chamelium_data_t data;
+	struct chamelium_port *port;
+	int p;
+
+	igt_fixture {
+		chamelium_init_test(&data);
+	}
+
+	igt_describe("DisplayPort tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_display_all_modes_desc);
+		connector_subtest("dp-crc-single", DisplayPort)
+			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
+					       CHAMELIUM_CHECK_CRC, 1);
+
+		igt_describe(test_display_one_mode_desc);
+		connector_subtest("dp-crc-fast", DisplayPort)
+			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
+					      CHAMELIUM_CHECK_CRC, 1);
+
+		igt_describe(test_display_all_modes_desc);
+		connector_subtest("dp-crc-multiple", DisplayPort)
+			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
+					       CHAMELIUM_CHECK_CRC, 3);
+
+		igt_describe(test_display_frame_dump_desc);
+		connector_subtest("dp-frame-dump", DisplayPort)
+			test_display_frame_dump(&data, port);
+	}
+
+	igt_describe("HDMI tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_HDMIA,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_display_all_modes_desc);
+		connector_subtest("hdmi-crc-single", HDMIA)
+			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
+					       CHAMELIUM_CHECK_CRC, 1);
+
+		igt_describe(test_display_one_mode_desc);
+		connector_subtest("hdmi-crc-fast", HDMIA)
+			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
+					      CHAMELIUM_CHECK_CRC, 1);
+
+		igt_describe(test_display_all_modes_desc);
+		connector_subtest("hdmi-crc-multiple", HDMIA)
+			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
+					       CHAMELIUM_CHECK_CRC, 3);
+
+		igt_describe(test_display_one_mode_desc);
+		connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA)
+		{
+			int k;
+			igt_output_t *output;
+			igt_plane_t *primary;
+
+			output = chamelium_prepare_output(&data, port,
+							  IGT_CUSTOM_EDID_BASE);
+			primary = igt_output_get_plane_type(
+				output, DRM_PLANE_TYPE_PRIMARY);
+			igt_assert(primary);
+
+			for (k = 0; k < primary->format_mod_count; k++) {
+				if (!igt_fb_supported_format(
+					    primary->formats[k]))
+					continue;
+
+				if (igt_format_is_yuv(primary->formats[k]))
+					continue;
+
+				if (primary->modifiers[k] !=
+				    DRM_FORMAT_MOD_LINEAR)
+					continue;
+
+				igt_dynamic_f(
+					"%s",
+					igt_format_str(primary->formats[k]))
+					test_display_one_mode(
+						&data, port,
+						primary->formats[k],
+						CHAMELIUM_CHECK_CRC, 1);
+			}
+		}
+
+		igt_describe(test_display_planes_random_desc);
+		connector_subtest("hdmi-crc-planes-random", HDMIA)
+			test_display_planes_random(&data, port,
+						   CHAMELIUM_CHECK_CRC);
+
+		igt_describe(test_display_one_mode_desc);
+		connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA)
+		{
+			int k;
+			igt_output_t *output;
+			igt_plane_t *primary;
+
+			output = chamelium_prepare_output(&data, port,
+							  IGT_CUSTOM_EDID_BASE);
+			primary = igt_output_get_plane_type(
+				output, DRM_PLANE_TYPE_PRIMARY);
+			igt_assert(primary);
+
+			for (k = 0; k < primary->format_mod_count; k++) {
+				if (!igt_fb_supported_format(
+					    primary->formats[k]))
+					continue;
+
+				if (!igt_format_is_yuv(primary->formats[k]))
+					continue;
+
+				if (primary->modifiers[k] !=
+				    DRM_FORMAT_MOD_LINEAR)
+					continue;
+
+				igt_dynamic_f(
+					"%s",
+					igt_format_str(primary->formats[k]))
+					test_display_one_mode(
+						&data, port,
+						primary->formats[k],
+						CHAMELIUM_CHECK_CHECKERBOARD,
+						1);
+			}
+		}
+
+		igt_describe(test_display_planes_random_desc);
+		connector_subtest("hdmi-cmp-planes-random", HDMIA)
+			test_display_planes_random(
+				&data, port, CHAMELIUM_CHECK_CHECKERBOARD);
+
+		igt_describe(test_display_frame_dump_desc);
+		connector_subtest("hdmi-frame-dump", HDMIA)
+			test_display_frame_dump(&data, port);
+
+		igt_describe(test_display_aspect_ratio_desc);
+		connector_subtest("hdmi-aspect-ratio", HDMIA)
+			test_display_aspect_ratio(&data, port);
+	}
+
+	igt_describe("VGA tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_VGA,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_display_all_modes_desc);
+		connector_subtest("vga-frame-dump", VGA)
+			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
+					       CHAMELIUM_CHECK_ANALOG, 1);
+	}
+
+	igt_fixture {
+		igt_display_fini(&data.display);
+		close(data.drm_fd);
+	}
+}
diff --git a/tests/chamelium/kms_chamelium_helper.c b/tests/chamelium/kms_chamelium_helper.c
new file mode 100644
index 00000000..b9544288
--- /dev/null
+++ b/tests/chamelium/kms_chamelium_helper.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: MIT
+/*
+ * A helper library for all Chamelium tests.
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Authors: Mark Yacoub <markyacoub@chromium.org>
+ */
+
+#include "igt_edid.h"
+#include "kms_chamelium_helper.h"
+
+void chamelium_init_test(chamelium_data_t *data)
+{
+	int i;
+
+	/* So fbcon doesn't try to reprobe things itself */
+	kmstest_set_vt_graphics_mode();
+
+	data->drm_fd = drm_open_driver_master(DRIVER_ANY);
+	igt_display_require(&data->display, data->drm_fd);
+	igt_require(data->display.is_atomic);
+
+	/*
+	 * XXX: disabling modeset, can be removed when
+	 * igt_display_require will start doing this for us
+	 */
+	igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+	/* we need to initalize chamelium after igt_display_require */
+	data->chamelium = chamelium_init(data->drm_fd, &data->display);
+	igt_require(data->chamelium);
+
+	data->ports = chamelium_get_ports(data->chamelium, &data->port_count);
+
+	for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) {
+		data->edids[i] = chamelium_new_edid(data->chamelium,
+						    igt_kms_get_custom_edid(i));
+	}
+}
+
+/* Wait for hotplug and return the remaining time left from timeout */
+bool chamelium_wait_for_hotplug(struct udev_monitor *mon, int *timeout)
+{
+	struct timespec start, end;
+	int elapsed;
+	bool detected;
+
+	igt_assert_eq(igt_gettime(&start), 0);
+	detected = igt_hotplug_detected(mon, *timeout);
+	igt_assert_eq(igt_gettime(&end), 0);
+
+	elapsed = igt_time_elapsed(&start, &end);
+	igt_assert_lte(0, elapsed);
+	*timeout = max(0, *timeout - elapsed);
+
+	return detected;
+}
+
+/**
+ * chamelium_wait_for_connector_after_hotplug:
+ *
+ * Waits for the connector attached to @port to have a status of @status after
+ * it's plugged/unplugged.
+ *
+ */
+void chamelium_wait_for_connector_after_hotplug(chamelium_data_t *data,
+						struct udev_monitor *mon,
+						struct chamelium_port *port,
+						drmModeConnection status)
+{
+	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
+	int hotplug_count = 0;
+
+	igt_debug("Waiting for %s to get %s after a hotplug event...\n",
+		  chamelium_port_get_name(port),
+		  kmstest_connector_status_str(status));
+
+	while (timeout > 0) {
+		if (!chamelium_wait_for_hotplug(mon, &timeout))
+			break;
+
+		hotplug_count++;
+
+		if (chamelium_reprobe_connector(&data->display, data->chamelium,
+						port) == status)
+			return;
+	}
+
+	igt_assert_f(
+		false,
+		"Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n",
+		chamelium_port_get_name(port),
+		kmstest_connector_status_str(status),
+		kmstest_connector_status_str(chamelium_reprobe_connector(
+			&data->display, data->chamelium, port)),
+		hotplug_count);
+}
+
+/**
+ * chamelium_port_get_connector:
+ * @data: The Chamelium data instance to use
+ * @port: The chamelium port to prepare its connector
+ * @edid: The chamelium's default EDID has a lot of resolutions, way more then
+ * 		  we need to test. Additionally the default EDID doesn't support
+ *        HDMI audio.
+ *
+ * Makes sure the output display of the connector attached to @port is connected
+ * and ready for use.
+ *
+ * Returns: a pointer to the enabled igt_output_t
+ */
+igt_output_t *chamelium_prepare_output(chamelium_data_t *data,
+				       struct chamelium_port *port,
+				       enum igt_custom_edid_type edid)
+{
+	igt_display_t *display = &data->display;
+	igt_output_t *output;
+	enum pipe pipe;
+
+	/* The chamelium's default EDID has a lot of resolutions, way more then
+	 * we need to test. Additionally the default EDID doesn't support HDMI
+	 * audio.
+	 */
+	chamelium_set_edid(data, port, edid);
+
+	chamelium_plug(data->chamelium, port);
+	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
+					      port, DRM_MODE_CONNECTED);
+
+	igt_display_reset(display);
+
+	output = chamelium_get_output_for_port(data, port);
+
+	/* Refresh pipe to update connected status */
+	igt_output_set_pipe(output, PIPE_NONE);
+
+	pipe = chamelium_get_pipe_for_output(display, output);
+	igt_output_set_pipe(output, pipe);
+
+	return output;
+}
+
+/**
+ * chamelium_enable_output:
+ *
+ * Modesets the connector attached to @port for the assigned @mode and draws the
+ * @fb.
+ *
+ */
+void chamelium_enable_output(chamelium_data_t *data,
+			     struct chamelium_port *port, igt_output_t *output,
+			     drmModeModeInfo *mode, struct igt_fb *fb)
+{
+	igt_display_t *display = output->display;
+	igt_plane_t *primary =
+		igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	drmModeConnector *connector =
+		chamelium_port_get_connector(data->chamelium, port, false);
+
+	igt_assert(primary);
+
+	igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
+	igt_plane_set_fb(primary, fb);
+	igt_output_override_mode(output, mode);
+
+	/* Clear any color correction values that might be enabled */
+	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT))
+		igt_pipe_obj_replace_prop_blob(primary->pipe,
+					       IGT_CRTC_DEGAMMA_LUT, NULL, 0);
+	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT))
+		igt_pipe_obj_replace_prop_blob(primary->pipe,
+					       IGT_CRTC_GAMMA_LUT, NULL, 0);
+	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM))
+		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM,
+					       NULL, 0);
+
+	igt_display_commit2(display, COMMIT_ATOMIC);
+
+	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA)
+		usleep(250000);
+
+	drmModeFreeConnector(connector);
+}
+
+/* Return pipe attached to @outpu.t */
+enum pipe chamelium_get_pipe_for_output(igt_display_t *display,
+					igt_output_t *output)
+{
+	enum pipe pipe;
+
+	for_each_pipe(display, pipe) {
+		if (igt_pipe_connector_valid(pipe, output)) {
+			return pipe;
+		}
+	}
+
+	igt_assert_f(false, "No pipe found for output %s\n",
+		     igt_output_name(output));
+}
+
+static void chamelium_paint_xr24_pattern(uint32_t *data, size_t width,
+					 size_t height, size_t stride,
+					 size_t block_size)
+{
+	uint32_t colors[] = { 0xff000000, 0xffff0000, 0xff00ff00, 0xff0000ff,
+			      0xffffffff };
+	unsigned i, j;
+
+	for (i = 0; i < height; i++)
+		for (j = 0; j < width; j++)
+			*(data + i * stride / 4 +
+			  j) = colors[((j / block_size) + (i / block_size)) % 5];
+}
+
+/**
+ * chamelium_get_pattern_fb:
+ *
+ * Creates an @fb with an xr24 pattern and returns the fb_id.
+ *
+ */
+int chamelium_get_pattern_fb(chamelium_data_t *data, size_t width,
+			     size_t height, uint32_t fourcc, size_t block_size,
+			     struct igt_fb *fb)
+{
+	int fb_id;
+	void *ptr;
+
+	igt_assert(fourcc == DRM_FORMAT_XRGB8888);
+
+	fb_id = igt_create_fb(data->drm_fd, width, height, fourcc,
+			      DRM_FORMAT_MOD_LINEAR, fb);
+	igt_assert(fb_id > 0);
+
+	ptr = igt_fb_map_buffer(fb->fd, fb);
+	igt_assert(ptr);
+
+	chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0],
+				     block_size);
+	igt_fb_unmap_buffer(fb, ptr);
+
+	return fb_id;
+}
+
+/* Generate a simple @fb for the size of @mode. */
+void chamelium_create_fb_for_mode(chamelium_data_t *data, struct igt_fb *fb,
+				  drmModeModeInfo *mode)
+{
+	int fb_id;
+
+	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
+					 DRM_FORMAT_XRGB8888, 64, fb);
+
+	igt_assert(fb_id > 0);
+}
+
+/* Returns the first preferred mode for the connector attached to @port. */
+drmModeModeInfo chamelium_get_mode_for_port(struct chamelium *chamelium,
+					    struct chamelium_port *port)
+{
+	drmModeConnector *connector =
+		chamelium_port_get_connector(chamelium, port, false);
+	drmModeModeInfo mode;
+	igt_assert(&connector->modes[0] != NULL);
+	memcpy(&mode, &connector->modes[0], sizeof(mode));
+	drmModeFreeConnector(connector);
+	return mode;
+}
+
+/* Returns the igt display output for the connector attached to @port. */
+igt_output_t *chamelium_get_output_for_port(chamelium_data_t *data,
+					    struct chamelium_port *port)
+{
+	drmModeConnector *connector =
+		chamelium_port_get_connector(data->chamelium, port, true);
+	igt_output_t *output =
+		igt_output_from_connector(&data->display, connector);
+	drmModeFreeConnector(connector);
+	igt_assert(output != NULL);
+	return output;
+}
+
+/* Set the EDID of index @edid to Chamelium's @port. */
+void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *port,
+			enum igt_custom_edid_type edid)
+{
+	chamelium_port_set_edid(data->chamelium, port, data->edids[edid]);
+}
+
+/**
+ * chamelium_check_analog_bridge:
+ *
+ * Check if the connector associalted to @port is an analog bridge by checking
+ * if it has its own EDID.
+ *
+ */
+bool chamelium_check_analog_bridge(chamelium_data_t *data,
+				   struct chamelium_port *port)
+{
+	drmModePropertyBlobPtr edid_blob = NULL;
+	drmModeConnector *connector =
+		chamelium_port_get_connector(data->chamelium, port, false);
+	uint64_t edid_blob_id;
+	const struct edid *edid;
+	char edid_vendor[3];
+
+	if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) {
+		drmModeFreeConnector(connector);
+		return false;
+	}
+
+	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
+					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
+					&edid_blob_id, NULL));
+	igt_assert(edid_blob =
+			   drmModeGetPropertyBlob(data->drm_fd, edid_blob_id));
+
+	edid = (const struct edid *)edid_blob->data;
+	edid_get_mfg(edid, edid_vendor);
+
+	drmModeFreePropertyBlob(edid_blob);
+	drmModeFreeConnector(connector);
+
+	/* Analog bridges provide their own EDID */
+	if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' ||
+	    edid_vendor[2] != 'T')
+		return true;
+
+	return false;
+}
\ No newline at end of file
diff --git a/tests/chamelium/kms_chamelium_helper.h b/tests/chamelium/kms_chamelium_helper.h
new file mode 100644
index 00000000..09fa4829
--- /dev/null
+++ b/tests/chamelium/kms_chamelium_helper.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * A helper library for all Chamelium tests.
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Authors: Mark Yacoub <markyacoub@chromium.org>
+ */
+
+#ifndef TESTS_CHAMELIUM_CHAMELIUM_HELPER_H
+#define TESTS_CHAMELIUM_CHAMELIUM_HELPER_H
+
+#include "igt.h"
+
+#define ONLINE_TIMEOUT 20 /* seconds */
+
+#define for_each_port(p, port)                                 \
+	for (p = 0, port = data.ports[p]; p < data.port_count; \
+	     p++, port = data.ports[p])
+
+#define connector_subtest(name__, type__)                           \
+	igt_subtest(name__)                                         \
+	for_each_port(p, port) if (chamelium_port_get_type(port) == \
+				   DRM_MODE_CONNECTOR_##type__)
+
+/*
+ * The chamelium data structure is used to store all the information known about
+ * chamelium to run the tests.
+ */
+typedef struct {
+	struct chamelium *chamelium;
+	struct chamelium_port **ports;
+	igt_display_t display;
+	int port_count;
+
+	int drm_fd;
+
+	struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT];
+} chamelium_data_t;
+
+void chamelium_init_test(chamelium_data_t *data);
+
+bool chamelium_wait_for_hotplug(struct udev_monitor *mon, int *timeout);
+void chamelium_wait_for_connector_after_hotplug(chamelium_data_t *data,
+						struct udev_monitor *mon,
+						struct chamelium_port *port,
+						drmModeConnection status);
+
+igt_output_t *chamelium_prepare_output(chamelium_data_t *data,
+				       struct chamelium_port *port,
+				       enum igt_custom_edid_type edid);
+void chamelium_enable_output(chamelium_data_t *data,
+			     struct chamelium_port *port, igt_output_t *output,
+			     drmModeModeInfo *mode, struct igt_fb *fb);
+enum pipe chamelium_get_pipe_for_output(igt_display_t *display,
+					igt_output_t *output);
+
+int chamelium_get_pattern_fb(chamelium_data_t *data, size_t width,
+			     size_t height, uint32_t fourcc, size_t block_size,
+			     struct igt_fb *fb);
+void chamelium_create_fb_for_mode(chamelium_data_t *data, struct igt_fb *fb,
+				  drmModeModeInfo *mode);
+drmModeModeInfo chamelium_get_mode_for_port(struct chamelium *chamelium,
+					    struct chamelium_port *port);
+igt_output_t *chamelium_get_output_for_port(chamelium_data_t *data,
+					    struct chamelium_port *port);
+
+void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *port,
+			enum igt_custom_edid_type edid);
+
+bool chamelium_check_analog_bridge(chamelium_data_t *data,
+				   struct chamelium_port *port);
+
+#endif /* TESTS_CHAMELIUM_CHAMELIUM_HELPER_H */
diff --git a/tests/chamelium/kms_chamelium_hpd.c b/tests/chamelium/kms_chamelium_hpd.c
new file mode 100644
index 00000000..8a4e1aba
--- /dev/null
+++ b/tests/chamelium/kms_chamelium_hpd.c
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: MIT
+/*
+ * A Chamelium test for testing the HPD functionality.
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Authors: Mark Yacoub <markyacoub@chromium.org>
+ */
+
+#include "kms_chamelium_helper.h"
+
+#define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */
+#define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */
+
+#define HPD_TOGGLE_COUNT_VGA 5
+#define HPD_TOGGLE_COUNT_DP_HDMI 15
+#define HPD_TOGGLE_COUNT_FAST 3
+
+enum test_modeset_mode {
+	TEST_MODESET_ON,
+	TEST_MODESET_ON_OFF,
+	TEST_MODESET_OFF,
+};
+
+static void try_suspend_resume_hpd(chamelium_data_t *data,
+				   struct chamelium_port *port,
+				   enum igt_suspend_state state,
+				   enum igt_suspend_test test,
+				   struct udev_monitor *mon, bool connected)
+{
+	drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED :
+						     DRM_MODE_CONNECTED;
+	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
+	int delay;
+	int p;
+
+	igt_flush_uevents(mon);
+
+	delay = igt_get_autoresume_delay(state) * 1000 / 2;
+
+	if (port) {
+		chamelium_schedule_hpd_toggle(data->chamelium, port, delay,
+					      !connected);
+	} else {
+		for (p = 0; p < data->port_count; p++) {
+			port = data->ports[p];
+			chamelium_schedule_hpd_toggle(data->chamelium, port,
+						      delay, !connected);
+		}
+
+		port = NULL;
+	}
+
+	igt_system_suspend_autoresume(state, test);
+	igt_assert(chamelium_wait_for_hotplug(mon, &timeout));
+	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
+
+	if (port) {
+		igt_assert_eq(chamelium_reprobe_connector(
+				      &data->display, data->chamelium, port),
+			      target_state);
+	} else {
+		for (p = 0; p < data->port_count; p++) {
+			drmModeConnection current_state;
+
+			port = data->ports[p];
+			/*
+			 * There could be as many hotplug events sent by
+			 * driver as connectors we scheduled an HPD toggle on
+			 * above, depending on timing. So if we're not seeing
+			 * the expected connector state try to wait for an HPD
+			 * event for each connector/port.
+			 */
+			current_state = chamelium_reprobe_connector(
+				&data->display, data->chamelium, port);
+			if (p > 0 && current_state != target_state) {
+				igt_assert(chamelium_wait_for_hotplug(
+					mon, &timeout));
+				current_state = chamelium_reprobe_connector(
+					&data->display, data->chamelium, port);
+			}
+
+			igt_assert_eq(current_state, target_state);
+		}
+
+		port = NULL;
+	}
+}
+
+static const char test_basic_hotplug_desc[] =
+	"Check that we get uevents and updated connector status on "
+	"hotplug and unplug";
+static void test_hotplug(chamelium_data_t *data, struct chamelium_port *port,
+			 int toggle_count, enum test_modeset_mode modeset_mode)
+{
+	int i;
+	enum pipe pipe;
+	struct igt_fb fb = { 0 };
+	drmModeModeInfo mode;
+	struct udev_monitor *mon = igt_watch_uevents();
+	igt_output_t *output = chamelium_get_output_for_port(data, port);
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, NULL,
+			      data->ports, data->port_count);
+
+	igt_hpd_storm_set_threshold(data->drm_fd, 0);
+
+	for (i = 0; i < toggle_count; i++) {
+		igt_flush_uevents(mon);
+
+		/* Check if we get a sysfs hotplug event */
+		chamelium_plug(data->chamelium, port);
+
+		chamelium_wait_for_connector_after_hotplug(data, mon, port,
+							   DRM_MODE_CONNECTED);
+		igt_flush_uevents(mon);
+
+		if (modeset_mode == TEST_MODESET_ON_OFF ||
+		    (modeset_mode == TEST_MODESET_ON && i == 0)) {
+			if (i == 0) {
+				/* We can only get mode and pipe once we are
+				 * connected */
+				output = chamelium_get_output_for_port(data,
+								       port);
+				pipe = chamelium_get_pipe_for_output(
+					&data->display, output);
+				mode = chamelium_get_mode_for_port(
+					data->chamelium, port);
+				chamelium_create_fb_for_mode(data, &fb, &mode);
+			}
+
+			igt_output_set_pipe(output, pipe);
+			chamelium_enable_output(data, port, output, &mode, &fb);
+		}
+
+		/* Now check if we get a hotplug from disconnection */
+		chamelium_unplug(data->chamelium, port);
+
+		chamelium_wait_for_connector_after_hotplug(
+			data, mon, port, DRM_MODE_DISCONNECTED);
+
+		igt_flush_uevents(mon);
+
+		if (modeset_mode == TEST_MODESET_ON_OFF) {
+			igt_output_set_pipe(output, PIPE_NONE);
+			igt_display_commit2(&data->display, COMMIT_ATOMIC);
+		}
+	}
+
+	igt_cleanup_uevents(mon);
+	igt_hpd_storm_reset(data->drm_fd);
+	igt_remove_fb(data->drm_fd, &fb);
+}
+
+static const char test_hotplug_for_each_pipe_desc[] =
+	"Check that we get uevents and updated connector status on "
+	"hotplug and unplug for each pipe with valid output";
+static void test_hotplug_for_each_pipe(chamelium_data_t *data,
+				       struct chamelium_port *port)
+{
+	igt_output_t *output;
+	enum pipe pipe;
+	struct udev_monitor *mon = igt_watch_uevents();
+
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	igt_hpd_storm_set_threshold(data->drm_fd, 0);
+	/* Disconnect if any port got connected */
+	chamelium_unplug(data->chamelium, port);
+	chamelium_wait_for_connector_after_hotplug(data, mon, port,
+						   DRM_MODE_DISCONNECTED);
+
+	for_each_pipe(&data->display, pipe) {
+		igt_flush_uevents(mon);
+		/* Check if we get a sysfs hotplug event */
+		chamelium_plug(data->chamelium, port);
+		chamelium_wait_for_connector_after_hotplug(data, mon, port,
+							   DRM_MODE_CONNECTED);
+		igt_flush_uevents(mon);
+		output = chamelium_get_output_for_port(data, port);
+
+		/* If pipe is valid for output then set it */
+		if (igt_pipe_connector_valid(pipe, output)) {
+			igt_output_set_pipe(output, pipe);
+			igt_display_commit2(&data->display, COMMIT_ATOMIC);
+		}
+
+		chamelium_unplug(data->chamelium, port);
+		chamelium_wait_for_connector_after_hotplug(
+			data, mon, port, DRM_MODE_DISCONNECTED);
+		igt_flush_uevents(mon);
+	}
+
+	igt_cleanup_uevents(mon);
+	igt_hpd_storm_reset(data->drm_fd);
+}
+
+static const char test_suspend_resume_hpd_desc[] =
+	"Toggle HPD during suspend, check that uevents are sent and connector "
+	"status is updated";
+static void test_suspend_resume_hpd(chamelium_data_t *data,
+				    struct chamelium_port *port,
+				    enum igt_suspend_state state,
+				    enum igt_suspend_test test)
+{
+	struct udev_monitor *mon = igt_watch_uevents();
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	/* Make sure we notice new connectors after resuming */
+	try_suspend_resume_hpd(data, port, state, test, mon, false);
+
+	/* Now make sure we notice disconnected connectors after resuming */
+	try_suspend_resume_hpd(data, port, state, test, mon, true);
+
+	igt_cleanup_uevents(mon);
+}
+
+static const char test_suspend_resume_hpd_common_desc[] =
+	"Toggle HPD during suspend on all connectors, check that uevents are "
+	"sent and connector status is updated";
+static void test_suspend_resume_hpd_common(chamelium_data_t *data,
+					   enum igt_suspend_state state,
+					   enum igt_suspend_test test)
+{
+	struct udev_monitor *mon = igt_watch_uevents();
+	struct chamelium_port *port;
+	int p;
+
+	for (p = 0; p < data->port_count; p++) {
+		port = data->ports[p];
+		igt_debug("Testing port %s\n", chamelium_port_get_name(port));
+	}
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, NULL,
+			      data->ports, data->port_count);
+
+	/* Make sure we notice new connectors after resuming */
+	try_suspend_resume_hpd(data, NULL, state, test, mon, false);
+
+	/* Now make sure we notice disconnected connectors after resuming */
+	try_suspend_resume_hpd(data, NULL, state, test, mon, true);
+
+	igt_cleanup_uevents(mon);
+}
+
+static const char test_hpd_without_ddc_desc[] =
+	"Disable DDC on a VGA connector, check we still get a uevent on hotplug";
+static void test_hpd_without_ddc(chamelium_data_t *data,
+				 struct chamelium_port *port)
+{
+	struct udev_monitor *mon = igt_watch_uevents();
+
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+	igt_flush_uevents(mon);
+
+	/* Disable the DDC on the connector and make sure we still get a
+	 * hotplug
+	 */
+	chamelium_port_set_ddc_state(data->chamelium, port, false);
+	chamelium_plug(data->chamelium, port);
+
+	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
+	igt_assert_eq(chamelium_reprobe_connector(&data->display,
+						  data->chamelium, port),
+		      DRM_MODE_CONNECTED);
+
+	igt_cleanup_uevents(mon);
+}
+
+static const char test_hpd_storm_detect_desc[] =
+	"Trigger a series of hotplugs in a very small timeframe to simulate a"
+	"bad cable, check the kernel falls back to polling to avoid a hotplug "
+	"storm";
+static void test_hpd_storm_detect(chamelium_data_t *data,
+				  struct chamelium_port *port, int width)
+{
+	struct udev_monitor *mon;
+	int count = 0;
+
+	igt_require_hpd_storm_ctl(data->drm_fd);
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	igt_hpd_storm_set_threshold(data->drm_fd, 1);
+	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
+	igt_assert(igt_hpd_storm_detected(data->drm_fd));
+
+	mon = igt_watch_uevents();
+	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
+
+	/*
+	 * Polling should have been enabled by the HPD storm at this point,
+	 * so we should only get at most 1 hotplug event
+	 */
+	igt_until_timeout(5)
+		count += igt_hotplug_detected(mon, 1);
+	igt_assert_lt(count, 2);
+
+	igt_cleanup_uevents(mon);
+	igt_hpd_storm_reset(data->drm_fd);
+}
+
+static const char test_hpd_storm_disable_desc[] =
+	"Disable HPD storm detection, trigger a storm and check the kernel "
+	"doesn't detect one";
+static void test_hpd_storm_disable(chamelium_data_t *data,
+				   struct chamelium_port *port, int width)
+{
+	igt_require_hpd_storm_ctl(data->drm_fd);
+	igt_modeset_disable_all_outputs(&data->display);
+	chamelium_reset_state(&data->display, data->chamelium, port,
+			      data->ports, data->port_count);
+
+	igt_hpd_storm_set_threshold(data->drm_fd, 0);
+	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
+	igt_assert(!igt_hpd_storm_detected(data->drm_fd));
+
+	igt_hpd_storm_reset(data->drm_fd);
+}
+
+IGT_TEST_DESCRIPTION("Testing HPD with a Chamelium board");
+igt_main
+{
+	chamelium_data_t data;
+	struct chamelium_port *port;
+	int p;
+
+	igt_fixture {
+		chamelium_init_test(&data);
+	}
+
+	igt_describe("DisplayPort tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("dp-hpd", DisplayPort)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_DP_HDMI,
+				     TEST_MODESET_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("dp-hpd-fast", DisplayPort) test_hotplug(
+			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("dp-hpd-enable-disable-mode", DisplayPort)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
+				     TEST_MODESET_ON_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("dp-hpd-with-enabled-mode", DisplayPort)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
+				     TEST_MODESET_ON);
+
+		igt_describe(test_hotplug_for_each_pipe_desc);
+		connector_subtest("dp-hpd-for-each-pipe", DisplayPort)
+			test_hotplug_for_each_pipe(&data, port);
+
+		igt_describe(test_suspend_resume_hpd_desc);
+		connector_subtest("dp-hpd-after-suspend", DisplayPort)
+			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
+						SUSPEND_TEST_NONE);
+
+		igt_describe(test_suspend_resume_hpd_desc);
+		connector_subtest("dp-hpd-after-hibernate", DisplayPort)
+			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
+						SUSPEND_TEST_DEVICES);
+
+		igt_describe(test_hpd_storm_detect_desc);
+		connector_subtest("dp-hpd-storm", DisplayPort)
+			test_hpd_storm_detect(&data, port,
+					      HPD_STORM_PULSE_INTERVAL_DP);
+
+		igt_describe(test_hpd_storm_disable_desc);
+		connector_subtest("dp-hpd-storm-disable", DisplayPort)
+			test_hpd_storm_disable(&data, port,
+					       HPD_STORM_PULSE_INTERVAL_DP);
+	}
+
+	igt_describe("HDMI tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_HDMIA,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("hdmi-hpd", HDMIA)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_DP_HDMI,
+				     TEST_MODESET_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("hdmi-hpd-fast", HDMIA) test_hotplug(
+			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
+				     TEST_MODESET_ON_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
+				     TEST_MODESET_ON);
+
+		igt_describe(test_hotplug_for_each_pipe_desc);
+		connector_subtest("hdmi-hpd-for-each-pipe", HDMIA)
+			test_hotplug_for_each_pipe(&data, port);
+
+		igt_describe(test_suspend_resume_hpd_desc);
+		connector_subtest("hdmi-hpd-after-suspend", HDMIA)
+			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
+						SUSPEND_TEST_NONE);
+
+		igt_describe(test_suspend_resume_hpd_desc);
+		connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
+			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
+						SUSPEND_TEST_DEVICES);
+
+		igt_describe(test_hpd_storm_detect_desc);
+		connector_subtest("hdmi-hpd-storm", HDMIA)
+			test_hpd_storm_detect(&data, port,
+					      HPD_STORM_PULSE_INTERVAL_HDMI);
+
+		igt_describe(test_hpd_storm_disable_desc);
+		connector_subtest("hdmi-hpd-storm-disable", HDMIA)
+			test_hpd_storm_disable(&data, port,
+					       HPD_STORM_PULSE_INTERVAL_HDMI);
+	}
+
+	igt_describe("VGA tests");
+	igt_subtest_group {
+		igt_fixture {
+			chamelium_require_connector_present(
+				data.ports, DRM_MODE_CONNECTOR_VGA,
+				data.port_count, 1);
+		}
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("vga-hpd", VGA) test_hotplug(
+			&data, port, HPD_TOGGLE_COUNT_VGA, TEST_MODESET_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("vga-hpd-fast", VGA) test_hotplug(
+			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("vga-hpd-enable-disable-mode", VGA)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
+				     TEST_MODESET_ON_OFF);
+
+		igt_describe(test_basic_hotplug_desc);
+		connector_subtest("vga-hpd-with-enabled-mode", VGA)
+			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
+				     TEST_MODESET_ON);
+
+		igt_describe(test_suspend_resume_hpd_desc);
+		connector_subtest("vga-hpd-after-suspend", VGA)
+			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
+						SUSPEND_TEST_NONE);
+
+		igt_describe(test_suspend_resume_hpd_desc);
+		connector_subtest("vga-hpd-after-hibernate", VGA)
+			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
+						SUSPEND_TEST_DEVICES);
+
+		igt_describe(test_hpd_without_ddc_desc);
+		connector_subtest("vga-hpd-without-ddc", VGA)
+			test_hpd_without_ddc(&data, port);
+	}
+
+	igt_describe("Tests that operate on all connectors");
+	igt_subtest_group {
+		igt_fixture {
+			igt_require(data.port_count);
+		}
+
+		igt_describe(test_suspend_resume_hpd_common_desc);
+		igt_subtest("common-hpd-after-suspend")
+			test_suspend_resume_hpd_common(&data, SUSPEND_STATE_MEM,
+						       SUSPEND_TEST_NONE);
+
+		igt_describe(test_suspend_resume_hpd_common_desc);
+		igt_subtest("common-hpd-after-hibernate")
+			test_suspend_resume_hpd_common(&data,
+						       SUSPEND_STATE_DISK,
+						       SUSPEND_TEST_DEVICES);
+	}
+
+	igt_describe(test_hotplug_for_each_pipe_desc);
+	connector_subtest("vga-hpd-for-each-pipe", VGA)
+		test_hotplug_for_each_pipe(&data, port);
+
+	igt_fixture {
+		igt_display_fini(&data.display);
+		close(data.drm_fd);
+	}
+}
diff --git a/tests/intel-ci/blacklist.txt b/tests/intel-ci/blacklist.txt
index 0d307730..6e5cc436 100644
--- a/tests/intel-ci/blacklist.txt
+++ b/tests/intel-ci/blacklist.txt
@@ -77,7 +77,7 @@ igt@kms_frontbuffer_tracking@.*drrs.*
 # is too costly in comparison to the value
 # provided.
 ###############################################
-igt@kms_chamelium@hdmi-.*-planes-random
+igt@kms_chamelium_frames@hdmi-.*-planes-random
 ###############################################
 # Broadcom
 ###############################################
diff --git a/tests/intel-ci/fast-feedback.testlist b/tests/intel-ci/fast-feedback.testlist
index f57f8ff3..fb4c0f73 100644
--- a/tests/intel-ci/fast-feedback.testlist
+++ b/tests/intel-ci/fast-feedback.testlist
@@ -92,14 +92,14 @@ igt@kms_addfb_basic@unused-modifier
 igt@kms_addfb_basic@unused-offsets
 igt@kms_addfb_basic@unused-pitches
 igt@kms_busy@basic
-igt@kms_chamelium@dp-hpd-fast
-igt@kms_chamelium@dp-edid-read
-igt@kms_chamelium@dp-crc-fast
-igt@kms_chamelium@hdmi-hpd-fast
-igt@kms_chamelium@hdmi-edid-read
-igt@kms_chamelium@hdmi-crc-fast
-igt@kms_chamelium@vga-hpd-fast
-igt@kms_chamelium@vga-edid-read
+igt@kms_chamelium_hpd@dp-hpd-fast
+igt@kms_chamelium_edid@dp-edid-read
+igt@kms_chamelium_frames@dp-crc-fast
+igt@kms_chamelium_hpd@hdmi-hpd-fast
+igt@kms_chamelium_edid@hdmi-edid-read
+igt@kms_chamelium_frames@hdmi-crc-fast
+igt@kms_chamelium_hpd@vga-hpd-fast
+igt@kms_chamelium_edid@vga-edid-read
 igt@kms_prop_blob@basic
 igt@kms_cursor_legacy@basic-busy-flip-before-cursor
 igt@kms_cursor_legacy@basic-flip-after-cursor
@@ -174,5 +174,5 @@ igt@i915_suspend@basic-s2idle-without-i915
 igt@i915_suspend@basic-s3-without-i915
 igt@gem_exec_suspend@basic-s0
 igt@gem_exec_suspend@basic-s3
-igt@kms_chamelium@common-hpd-after-suspend
+igt@kms_chamelium_hpd@common-hpd-after-suspend
 igt@kms_pipe_crc_basic@suspend-read-crc
diff --git a/tests/kms_color_helper.h b/tests/kms_color_helper.h
index f0ae30e3..f9242232 100644
--- a/tests/kms_color_helper.h
+++ b/tests/kms_color_helper.h
@@ -27,7 +27,7 @@
 
 /*
  * This header is for code that is shared between kms_color.c and
- * kms_color_chamelium.c. Reusability elsewhere can be questionable.
+ * kms_chamelium_color.c. Reusability elsewhere can be questionable.
  */
 
 #include <math.h>
diff --git a/tests/meson.build b/tests/meson.build
index 5c052e73..b52399d5 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -260,7 +260,10 @@ msm_progs = [
 ]
 
 chamelium_progs = [
-	'kms_chamelium',
+	'kms_chamelium_audio',
+	'kms_chamelium_edid',
+	'kms_chamelium_frames',
+	'kms_chamelium_hpd',
 ]
 
 test_deps = [ igt_deps ]
@@ -309,7 +312,8 @@ endforeach
 if chamelium.found()
 	foreach prog : chamelium_progs
 		test_executables += executable(prog,
-				 join_paths('chamelium', prog + '.c'),
+				 [join_paths('chamelium', prog + '.c'), 
+				 	join_paths('chamelium', 'kms_chamelium_helper.c')],
 				 dependencies : test_deps,
 				 install_dir : libexecdir,
 				 install_rpath : libexecdir_rpathdir,
@@ -436,13 +440,13 @@ test_executables += executable('kms_color',
 test_list += 'kms_color'
 
 if chamelium.found()
-       test_executables += executable('kms_color_chamelium',
-                             [ 'chamelium/kms_color_chamelium.c', 'kms_color_helper.c' ],
+       test_executables += executable('kms_chamelium_color',
+                             [ 'chamelium/kms_chamelium_color.c', 'kms_color_helper.c' ],
                              dependencies : test_deps + [ chamelium ],
                              install_dir : libexecdir,
                              install_rpath : libexecdir_rpathdir,
                              install : true)
-       test_list += 'kms_color_chamelium'
+       test_list += 'kms_chamelium_color'
 endif
 
 test_executables += executable('sw_sync', 'sw_sync.c',
diff --git a/tests/vc4_ci/vc4-chamelium-fast.testlist b/tests/vc4_ci/vc4-chamelium-fast.testlist
index dd45d12a..a5521021 100644
--- a/tests/vc4_ci/vc4-chamelium-fast.testlist
+++ b/tests/vc4_ci/vc4-chamelium-fast.testlist
@@ -1,14 +1,14 @@
-igt@kms_chamelium@hdmi-crc-abgr8888
-igt@kms_chamelium@hdmi-crc-argb1555
-igt@kms_chamelium@hdmi-crc-argb8888
-igt@kms_chamelium@hdmi-crc-bgr565
-igt@kms_chamelium@hdmi-crc-bgr888
-igt@kms_chamelium@hdmi-crc-fast
-igt@kms_chamelium@hdmi-crc-rgb565
-igt@kms_chamelium@hdmi-crc-rgb888
-igt@kms_chamelium@hdmi-crc-xbgr8888
-igt@kms_chamelium@hdmi-crc-xrgb1555
-igt@kms_chamelium@hdmi-crc-xrgb8888
-igt@kms_chamelium@hdmi-edid-read
-igt@kms_chamelium@hdmi-hpd
-igt@kms_chamelium@hdmi-hpd-fast
+igt@kms_chamelium_frames@hdmi-crc-abgr8888
+igt@kms_chamelium_frames@hdmi-crc-argb1555
+igt@kms_chamelium_frames@hdmi-crc-argb8888
+igt@kms_chamelium_frames@hdmi-crc-bgr565
+igt@kms_chamelium_frames@hdmi-crc-bgr888
+igt@kms_chamelium_frames@hdmi-crc-fast
+igt@kms_chamelium_frames@hdmi-crc-rgb565
+igt@kms_chamelium_frames@hdmi-crc-rgb888
+igt@kms_chamelium_frames@hdmi-crc-xbgr8888
+igt@kms_chamelium_frames@hdmi-crc-xrgb1555
+igt@kms_chamelium_frames@hdmi-crc-xrgb8888
+igt@kms_chamelium_edid@hdmi-edid-read
+igt@kms_chamelium_hpd@hdmi-hpd
+igt@kms_chamelium_hpd@hdmi-hpd-fast
-- 
2.39.0.314.g84b9a713c41-goog

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [igt-dev] ✓ Fi.CI.BAT: success for testing headers in CI (rev2)
  2022-12-29 19:27 [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Mark Yacoub
@ 2022-12-29 21:16 ` Patchwork
  2022-12-29 23:27 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork
  2022-12-30 12:51 ` [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Jani Nikula
  2 siblings, 0 replies; 5+ messages in thread
From: Patchwork @ 2022-12-29 21:16 UTC (permalink / raw)
  To: Mark Yacoub; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 13663 bytes --]

== Series Details ==

Series: testing headers in CI (rev2)
URL   : https://patchwork.freedesktop.org/series/112294/
State : success

== Summary ==

CI Bug Log - changes from CI_DRM_12529 -> IGTPW_8283
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/index.html

Participating hosts (44 -> 42)
------------------------------

  Missing    (2): fi-rkl-11600 fi-snb-2520m 

Possible new issues
-------------------

  Here are the unknown changes that may have been introduced in IGTPW_8283:

### IGT changes ###

#### Possible regressions ####

  * {igt@kms_chamelium_edid@dp-edid-read} (NEW):
    - {fi-jsl-1}:         NOTRUN -> [SKIP][1] +8 similar issues
   [1]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-jsl-1/igt@kms_chamelium_edid@dp-edid-read.html
    - {bat-rpls-1}:       NOTRUN -> [SKIP][2] +7 similar issues
   [2]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-rpls-1/igt@kms_chamelium_edid@dp-edid-read.html

  * {igt@kms_chamelium_edid@hdmi-edid-read} (NEW):
    - fi-adl-ddr5:        NOTRUN -> [SKIP][3] +8 similar issues
   [3]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-adl-ddr5/igt@kms_chamelium_edid@hdmi-edid-read.html
    - {fi-ehl-2}:         NOTRUN -> [SKIP][4] +8 similar issues
   [4]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-ehl-2/igt@kms_chamelium_edid@hdmi-edid-read.html
    - {bat-rpls-2}:       NOTRUN -> [SKIP][5] +7 similar issues
   [5]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-rpls-2/igt@kms_chamelium_edid@hdmi-edid-read.html
    - bat-dg1-6:          NOTRUN -> [SKIP][6] +8 similar issues
   [6]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-dg1-6/igt@kms_chamelium_edid@hdmi-edid-read.html

  * {igt@kms_chamelium_frames@hdmi-crc-fast} (NEW):
    - {bat-adln-1}:       NOTRUN -> [SKIP][7] +8 similar issues
   [7]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adln-1/igt@kms_chamelium_frames@hdmi-crc-fast.html

  * {igt@kms_chamelium_hpd@common-hpd-after-suspend} (NEW):
    - {bat-adlp-9}:       NOTRUN -> [SKIP][8] +8 similar issues
   [8]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adlp-9/igt@kms_chamelium_hpd@common-hpd-after-suspend.html

  * {igt@kms_chamelium_hpd@dp-hpd-fast} (NEW):
    - bat-dg1-5:          NOTRUN -> [SKIP][9] +8 similar issues
   [9]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-dg1-5/igt@kms_chamelium_hpd@dp-hpd-fast.html
    - {bat-dg1-7}:        NOTRUN -> [SKIP][10] +8 similar issues
   [10]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-dg1-7/igt@kms_chamelium_hpd@dp-hpd-fast.html
    - {bat-dg2-9}:        NOTRUN -> [SKIP][11] +8 similar issues
   [11]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-dg2-9/igt@kms_chamelium_hpd@dp-hpd-fast.html

  * {igt@kms_chamelium_hpd@hdmi-hpd-fast} (NEW):
    - {bat-adlp-6}:       NOTRUN -> [SKIP][12] +8 similar issues
   [12]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adlp-6/igt@kms_chamelium_hpd@hdmi-hpd-fast.html

  * {igt@kms_chamelium_hpd@vga-hpd-fast} (NEW):
    - fi-icl-u2:          NOTRUN -> [SKIP][13] +8 similar issues
   [13]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-icl-u2/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - {bat-atsm-1}:       NOTRUN -> [SKIP][14] +7 similar issues
   [14]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-atsm-1/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - {bat-jsl-3}:        NOTRUN -> [SKIP][15] +8 similar issues
   [15]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-jsl-3/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-rkl-guc:         NOTRUN -> [SKIP][16] +8 similar issues
   [16]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-rkl-guc/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - {bat-dg2-11}:       NOTRUN -> [SKIP][17] +8 similar issues
   [17]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-dg2-11/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - bat-adlp-4:         NOTRUN -> [SKIP][18] +8 similar issues
   [18]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adlp-4/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - {bat-dg2-8}:        NOTRUN -> [SKIP][19] +8 similar issues
   [19]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-dg2-8/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - {bat-adlm-1}:       NOTRUN -> [SKIP][20] +8 similar issues
   [20]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adlm-1/igt@kms_chamelium_hpd@vga-hpd-fast.html

  
New tests
---------

  New tests have been introduced between CI_DRM_12529 and IGTPW_8283:

### New IGT tests (9) ###

  * igt@kms_chamelium_edid@dp-edid-read:
    - Statuses : 42 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@hdmi-edid-read:
    - Statuses : 1 pass(s) 41 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@vga-edid-read:
    - Statuses : 42 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@dp-crc-fast:
    - Statuses : 42 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-crc-fast:
    - Statuses : 1 pass(s) 41 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@common-hpd-after-suspend:
    - Statuses : 1 pass(s) 37 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-fast:
    - Statuses : 42 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-fast:
    - Statuses : 1 pass(s) 41 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-fast:
    - Statuses : 42 skip(s)
    - Exec time: [0.0] s

  

Known issues
------------

  Here are the changes found in IGTPW_8283 that come from known issues:

### IGT changes ###

#### Issues hit ####

  * {igt@kms_chamelium_edid@dp-edid-read} (NEW):
    - fi-bsw-n3050:       NOTRUN -> [SKIP][21] ([fdo#109271]) +8 similar issues
   [21]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-bsw-n3050/igt@kms_chamelium_edid@dp-edid-read.html
    - fi-bwr-2160:        NOTRUN -> [SKIP][22] ([fdo#109271]) +8 similar issues
   [22]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-bwr-2160/igt@kms_chamelium_edid@dp-edid-read.html
    - fi-pnv-d510:        NOTRUN -> [SKIP][23] ([fdo#109271]) +8 similar issues
   [23]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-pnv-d510/igt@kms_chamelium_edid@dp-edid-read.html

  * {igt@kms_chamelium_edid@hdmi-edid-read} (NEW):
    - fi-cfl-guc:         NOTRUN -> [SKIP][24] ([fdo#109271]) +8 similar issues
   [24]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-cfl-guc/igt@kms_chamelium_edid@hdmi-edid-read.html
    - fi-skl-6600u:       NOTRUN -> [SKIP][25] ([fdo#109271]) +8 similar issues
   [25]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-skl-6600u/igt@kms_chamelium_edid@hdmi-edid-read.html

  * {igt@kms_chamelium_frames@dp-crc-fast} (NEW):
    - fi-ilk-650:         NOTRUN -> [SKIP][26] ([fdo#109271]) +8 similar issues
   [26]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-ilk-650/igt@kms_chamelium_frames@dp-crc-fast.html

  * {igt@kms_chamelium_frames@hdmi-crc-fast} (NEW):
    - fi-cfl-8109u:       NOTRUN -> [SKIP][27] ([fdo#109271]) +8 similar issues
   [27]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-cfl-8109u/igt@kms_chamelium_frames@hdmi-crc-fast.html
    - fi-kbl-7567u:       NOTRUN -> [SKIP][28] ([fdo#109271]) +8 similar issues
   [28]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-kbl-7567u/igt@kms_chamelium_frames@hdmi-crc-fast.html
    - fi-kbl-8809g:       NOTRUN -> [SKIP][29] ([fdo#109271]) +7 similar issues
   [29]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-kbl-8809g/igt@kms_chamelium_frames@hdmi-crc-fast.html

  * {igt@kms_chamelium_hpd@common-hpd-after-suspend} (NEW):
    - fi-glk-j4005:       NOTRUN -> [SKIP][30] ([fdo#109271]) +8 similar issues
   [30]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-glk-j4005/igt@kms_chamelium_hpd@common-hpd-after-suspend.html
    - fi-snb-2600:        NOTRUN -> [SKIP][31] ([fdo#109271]) +8 similar issues
   [31]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-snb-2600/igt@kms_chamelium_hpd@common-hpd-after-suspend.html

  * {igt@kms_chamelium_hpd@dp-hpd-fast} (NEW):
    - fi-skl-6700k2:      NOTRUN -> [SKIP][32] ([fdo#109271]) +4 similar issues
   [32]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-skl-6700k2/igt@kms_chamelium_hpd@dp-hpd-fast.html

  * {igt@kms_chamelium_hpd@hdmi-hpd-fast} (NEW):
    - fi-cfl-8700k:       NOTRUN -> [SKIP][33] ([fdo#109271]) +8 similar issues
   [33]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-cfl-8700k/igt@kms_chamelium_hpd@hdmi-hpd-fast.html
    - fi-blb-e6850:       NOTRUN -> [SKIP][34] ([fdo#109271]) +8 similar issues
   [34]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-blb-e6850/igt@kms_chamelium_hpd@hdmi-hpd-fast.html
    - {bat-kbl-2}:        NOTRUN -> [SKIP][35] ([fdo#109271]) +8 similar issues
   [35]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-kbl-2/igt@kms_chamelium_hpd@hdmi-hpd-fast.html

  * {igt@kms_chamelium_hpd@vga-hpd-fast} (NEW):
    - fi-apl-guc:         NOTRUN -> [SKIP][36] ([fdo#109271]) +8 similar issues
   [36]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-apl-guc/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-bsw-kefka:       NOTRUN -> [SKIP][37] ([fdo#109271]) +8 similar issues
   [37]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-bsw-kefka/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-skl-guc:         NOTRUN -> [SKIP][38] ([fdo#109271]) +8 similar issues
   [38]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-skl-guc/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-hsw-4770:        NOTRUN -> [SKIP][39] ([fdo#109271]) +8 similar issues
   [39]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-hsw-4770/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-ivb-3770:        NOTRUN -> [SKIP][40] ([fdo#109271]) +8 similar issues
   [40]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-ivb-3770/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-elk-e7500:       NOTRUN -> [SKIP][41] ([fdo#109271]) +8 similar issues
   [41]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-elk-e7500/igt@kms_chamelium_hpd@vga-hpd-fast.html
    - fi-bsw-nick:        NOTRUN -> [SKIP][42] ([fdo#109271]) +8 similar issues
   [42]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-bsw-nick/igt@kms_chamelium_hpd@vga-hpd-fast.html

  * igt@kms_cursor_legacy@basic-busy-flip-before-cursor@atomic-transitions-varying-size:
    - fi-bsw-kefka:       [PASS][43] -> [FAIL][44] ([i915#6298])
   [43]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/fi-bsw-kefka/igt@kms_cursor_legacy@basic-busy-flip-before-cursor@atomic-transitions-varying-size.html
   [44]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/fi-bsw-kefka/igt@kms_cursor_legacy@basic-busy-flip-before-cursor@atomic-transitions-varying-size.html

  
#### Possible fixes ####

  * igt@i915_pm_rpm@basic-rte:
    - {bat-adln-1}:       [DMESG-WARN][45] -> [PASS][46]
   [45]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/bat-adln-1/igt@i915_pm_rpm@basic-rte.html
   [46]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adln-1/igt@i915_pm_rpm@basic-rte.html

  * igt@i915_selftest@live@migrate:
    - {bat-adlp-9}:       [DMESG-FAIL][47] ([i915#7699]) -> [PASS][48]
   [47]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/bat-adlp-9/igt@i915_selftest@live@migrate.html
   [48]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-adlp-9/igt@i915_selftest@live@migrate.html

  * igt@i915_selftest@live@slpc:
    - {bat-rpls-1}:       [DMESG-FAIL][49] ([i915#6367]) -> [PASS][50]
   [49]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/bat-rpls-1/igt@i915_selftest@live@slpc.html
   [50]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/bat-rpls-1/igt@i915_selftest@live@slpc.html

  
  {name}: This element is suppressed. This means it is ignored when computing
          the status of the difference (SUCCESS, WARNING, or FAILURE).

  [fdo#109271]: https://bugs.freedesktop.org/show_bug.cgi?id=109271
  [fdo#109295]: https://bugs.freedesktop.org/show_bug.cgi?id=109295
  [i915#3291]: https://gitlab.freedesktop.org/drm/intel/issues/3291
  [i915#3301]: https://gitlab.freedesktop.org/drm/intel/issues/3301
  [i915#3546]: https://gitlab.freedesktop.org/drm/intel/issues/3546
  [i915#4312]: https://gitlab.freedesktop.org/drm/intel/issues/4312
  [i915#4613]: https://gitlab.freedesktop.org/drm/intel/issues/4613
  [i915#4983]: https://gitlab.freedesktop.org/drm/intel/issues/4983
  [i915#6257]: https://gitlab.freedesktop.org/drm/intel/issues/6257
  [i915#6298]: https://gitlab.freedesktop.org/drm/intel/issues/6298
  [i915#6367]: https://gitlab.freedesktop.org/drm/intel/issues/6367
  [i915#6621]: https://gitlab.freedesktop.org/drm/intel/issues/6621
  [i915#6687]: https://gitlab.freedesktop.org/drm/intel/issues/6687
  [i915#6997]: https://gitlab.freedesktop.org/drm/intel/issues/6997
  [i915#7336]: https://gitlab.freedesktop.org/drm/intel/issues/7336
  [i915#7699]: https://gitlab.freedesktop.org/drm/intel/issues/7699


Build changes
-------------

  * CI: CI-20190529 -> None
  * IGT: IGT_7104 -> IGTPW_8283

  CI-20190529: 20190529
  CI_DRM_12529: 0ca1a1772701d5c3350d8736080e528e2f2f413e @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_8283: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/index.html
  IGT_7104: fe5def13049225967770eaaf19ec01ef80e2adc5 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/index.html

[-- Attachment #2: Type: text/html, Size: 16070 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [igt-dev] ✓ Fi.CI.IGT: success for testing headers in CI (rev2)
  2022-12-29 19:27 [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Mark Yacoub
  2022-12-29 21:16 ` [igt-dev] ✓ Fi.CI.BAT: success for testing headers in CI (rev2) Patchwork
@ 2022-12-29 23:27 ` Patchwork
  2022-12-30 12:51 ` [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Jani Nikula
  2 siblings, 0 replies; 5+ messages in thread
From: Patchwork @ 2022-12-29 23:27 UTC (permalink / raw)
  To: Mark Yacoub; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 35265 bytes --]

== Series Details ==

Series: testing headers in CI (rev2)
URL   : https://patchwork.freedesktop.org/series/112294/
State : success

== Summary ==

CI Bug Log - changes from CI_DRM_12529_full -> IGTPW_8283_full
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/index.html

Participating hosts (14 -> 10)
------------------------------

  Missing    (4): shard-rkl0 pig-kbl-iris pig-glk-j5005 pig-skl-6260u 

Possible new issues
-------------------

  Here are the unknown changes that may have been introduced in IGTPW_8283_full:

### IGT changes ###

#### Possible regressions ####

  * {igt@kms_chamelium_edid@hdmi-mode-timings} (NEW):
    - {shard-tglu}:       NOTRUN -> [SKIP][1] +21 similar issues
   [1]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-tglu-1/igt@kms_chamelium_edid@hdmi-mode-timings.html

  * {igt@kms_chamelium_frames@hdmi-frame-dump} (NEW):
    - {shard-tglu-9}:     NOTRUN -> [SKIP][2] +4 similar issues
   [2]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-tglu-9/igt@kms_chamelium_frames@hdmi-frame-dump.html

  * {igt@kms_chamelium_hpd@hdmi-hpd-for-each-pipe} (NEW):
    - {shard-tglu-10}:    NOTRUN -> [SKIP][3] +10 similar issues
   [3]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-tglu-10/igt@kms_chamelium_hpd@hdmi-hpd-for-each-pipe.html

  * {igt@kms_chamelium_hpd@hdmi-hpd-storm-disable} (NEW):
    - {shard-dg1}:        NOTRUN -> [SKIP][4] +59 similar issues
   [4]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-16/igt@kms_chamelium_hpd@hdmi-hpd-storm-disable.html

  * {igt@kms_chamelium_hpd@vga-hpd-fast} (NEW):
    - {shard-rkl}:        NOTRUN -> [SKIP][5] +58 similar issues
   [5]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-2/igt@kms_chamelium_hpd@vga-hpd-fast.html

  
#### Suppressed ####

  The following results come from untrusted machines, tests, or statuses.
  They do not affect the overall result.

  * igt@drm_fdinfo@idle@rcs0:
    - {shard-rkl}:        NOTRUN -> [FAIL][6]
   [6]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-2/igt@drm_fdinfo@idle@rcs0.html

  
New tests
---------

  New tests have been introduced between CI_DRM_12529_full and IGTPW_8283_full:

### New IGT tests (63) ###

  * igt@kms_chamelium_audio@dp-audio:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_audio@dp-audio-edid:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_audio@hdmi-audio:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_audio@hdmi-audio-edid:
    - Statuses :
    - Exec time: [None] s

  * igt@kms_chamelium_color@ctm-0-25:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-0-50:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-0-75:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-blue-to-red:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-green-to-red:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-limited-range:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-max:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-negative:
    - Statuses : 4 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@ctm-red-to-blue:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@degamma:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_color@gamma:
    - Statuses : 4 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@dp-edid-change-during-suspend:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@dp-edid-read:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@dp-edid-resolution-list:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@dp-edid-stress-resolution-4k:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@dp-edid-stress-resolution-non-4k:
    - Statuses : 4 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@dp-mode-timings:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@hdmi-edid-change-during-suspend:
    - Statuses :
    - Exec time: [None] s

  * igt@kms_chamelium_edid@hdmi-edid-read:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@hdmi-edid-stress-resolution-4k:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@hdmi-edid-stress-resolution-non-4k:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@hdmi-mode-timings:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_edid@vga-edid-read:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@dp-crc-fast:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@dp-crc-multiple:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@dp-crc-single:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@dp-frame-dump:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-aspect-ratio:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-cmp-planar-formats:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-crc-fast:
    - Statuses : 4 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-crc-multiple:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-crc-nonplanar-formats:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-crc-single:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@hdmi-frame-dump:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_frames@vga-frame-dump:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@common-hpd-after-suspend:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-after-suspend:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-enable-disable-mode:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-fast:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-for-each-pipe:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-storm:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-storm-disable:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@dp-hpd-with-enabled-mode:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd:
    - Statuses :
    - Exec time: [None] s

  * igt@kms_chamelium_hpd@hdmi-hpd-after-suspend:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-enable-disable-mode:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-fast:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-for-each-pipe:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-storm:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-storm-disable:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@hdmi-hpd-with-enabled-mode:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-after-suspend:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-enable-disable-mode:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-fast:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-for-each-pipe:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-with-enabled-mode:
    - Statuses : 5 skip(s)
    - Exec time: [0.0] s

  * igt@kms_chamelium_hpd@vga-hpd-without-ddc:
    - Statuses : 6 skip(s)
    - Exec time: [0.0] s

  

Known issues
------------

  Here are the changes found in IGTPW_8283_full that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@gem_exec_fair@basic-none-rrul@rcs0:
    - shard-glk:          [PASS][7] -> [FAIL][8] ([i915#2842]) +2 similar issues
   [7]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-glk5/igt@gem_exec_fair@basic-none-rrul@rcs0.html
   [8]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk1/igt@gem_exec_fair@basic-none-rrul@rcs0.html

  * igt@gem_lmem_swapping@parallel-random-verify-ccs:
    - shard-apl:          NOTRUN -> [SKIP][9] ([fdo#109271] / [i915#4613])
   [9]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl2/igt@gem_lmem_swapping@parallel-random-verify-ccs.html

  * igt@gem_partial_pwrite_pread@writes-after-reads-snoop:
    - shard-apl:          [PASS][10] -> [INCOMPLETE][11] ([i915#7708])
   [10]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-apl1/igt@gem_partial_pwrite_pread@writes-after-reads-snoop.html
   [11]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl2/igt@gem_partial_pwrite_pread@writes-after-reads-snoop.html

  * igt@gen9_exec_parse@allowed-all:
    - shard-apl:          [PASS][12] -> [DMESG-WARN][13] ([i915#5566] / [i915#716]) +1 similar issue
   [12]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-apl3/igt@gen9_exec_parse@allowed-all.html
   [13]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl7/igt@gen9_exec_parse@allowed-all.html

  * igt@kms_ccs@pipe-a-bad-aux-stride-y_tiled_gen12_rc_ccs_cc:
    - shard-glk:          NOTRUN -> [SKIP][14] ([fdo#109271] / [i915#3886]) +4 similar issues
   [14]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk4/igt@kms_ccs@pipe-a-bad-aux-stride-y_tiled_gen12_rc_ccs_cc.html

  * igt@kms_ccs@pipe-a-crc-primary-rotation-180-y_tiled_gen12_mc_ccs:
    - shard-apl:          NOTRUN -> [SKIP][15] ([fdo#109271] / [i915#3886]) +1 similar issue
   [15]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl7/igt@kms_ccs@pipe-a-crc-primary-rotation-180-y_tiled_gen12_mc_ccs.html

  * {igt@kms_chamelium_color@ctm-0-75} (NEW):
    - shard-apl:          NOTRUN -> [SKIP][16] ([fdo#109271]) +143 similar issues
   [16]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl6/igt@kms_chamelium_color@ctm-0-75.html

  * igt@kms_cursor_crc@cursor-random-max-size:
    - shard-glk:          NOTRUN -> [SKIP][17] ([fdo#109271]) +161 similar issues
   [17]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk9/igt@kms_cursor_crc@cursor-random-max-size.html

  * igt@kms_cursor_legacy@flip-vs-cursor@atomic-transitions-varying-size:
    - shard-apl:          [PASS][18] -> [FAIL][19] ([i915#2346]) +1 similar issue
   [18]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-apl1/igt@kms_cursor_legacy@flip-vs-cursor@atomic-transitions-varying-size.html
   [19]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl1/igt@kms_cursor_legacy@flip-vs-cursor@atomic-transitions-varying-size.html

  * igt@kms_plane_alpha_blend@alpha-opaque-fb@pipe-b-hdmi-a-2:
    - shard-glk:          NOTRUN -> [FAIL][20] ([i915#4573]) +2 similar issues
   [20]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk8/igt@kms_plane_alpha_blend@alpha-opaque-fb@pipe-b-hdmi-a-2.html

  * igt@kms_psr2_sf@cursor-plane-update-sf:
    - shard-apl:          NOTRUN -> [SKIP][21] ([fdo#109271] / [i915#658]) +1 similar issue
   [21]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl7/igt@kms_psr2_sf@cursor-plane-update-sf.html

  * igt@kms_psr2_su@frontbuffer-xrgb8888:
    - shard-glk:          NOTRUN -> [SKIP][22] ([fdo#109271] / [i915#658]) +2 similar issues
   [22]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk9/igt@kms_psr2_su@frontbuffer-xrgb8888.html

  * igt@kms_rotation_crc@sprite-rotation-270:
    - shard-snb:          NOTRUN -> [SKIP][23] ([fdo#109271]) +149 similar issues
   [23]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-snb5/igt@kms_rotation_crc@sprite-rotation-270.html

  * igt@sysfs_clients@recycle:
    - shard-glk:          NOTRUN -> [SKIP][24] ([fdo#109271] / [i915#2994])
   [24]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk9/igt@sysfs_clients@recycle.html

  * igt@sysfs_clients@recycle-many:
    - shard-apl:          NOTRUN -> [SKIP][25] ([fdo#109271] / [i915#2994]) +1 similar issue
   [25]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl3/igt@sysfs_clients@recycle-many.html

  
#### Possible fixes ####

  * igt@fbdev@info:
    - {shard-rkl}:        [SKIP][26] ([i915#2582]) -> [PASS][27]
   [26]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-2/igt@fbdev@info.html
   [27]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-5/igt@fbdev@info.html

  * igt@fbdev@read:
    - {shard-dg1}:        [FAIL][28] -> [PASS][29]
   [28]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-dg1-12/igt@fbdev@read.html
   [29]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-13/igt@fbdev@read.html
    - shard-apl:          [FAIL][30] -> [PASS][31]
   [30]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-apl3/igt@fbdev@read.html
   [31]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl1/igt@fbdev@read.html
    - shard-snb:          [FAIL][32] -> [PASS][33]
   [32]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-snb7/igt@fbdev@read.html
   [33]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-snb7/igt@fbdev@read.html
    - shard-glk:          [FAIL][34] -> [PASS][35]
   [34]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-glk9/igt@fbdev@read.html
   [35]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk9/igt@fbdev@read.html

  * igt@feature_discovery@psr1:
    - {shard-rkl}:        [SKIP][36] ([i915#658]) -> [PASS][37] +1 similar issue
   [36]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-2/igt@feature_discovery@psr1.html
   [37]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@feature_discovery@psr1.html

  * igt@gem_ctx_persistence@hang:
    - {shard-rkl}:        [SKIP][38] ([i915#6252]) -> [PASS][39]
   [38]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-5/igt@gem_ctx_persistence@hang.html
   [39]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-2/igt@gem_ctx_persistence@hang.html

  * igt@gem_eio@unwedge-stress:
    - {shard-dg1}:        [FAIL][40] ([i915#5784]) -> [PASS][41]
   [40]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-dg1-19/igt@gem_eio@unwedge-stress.html
   [41]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-19/igt@gem_eio@unwedge-stress.html

  * igt@gem_exec_fair@basic-deadline:
    - shard-glk:          [FAIL][42] ([i915#2846]) -> [PASS][43]
   [42]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-glk5/igt@gem_exec_fair@basic-deadline.html
   [43]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk1/igt@gem_exec_fair@basic-deadline.html

  * igt@gem_exec_fair@basic-pace-solo@rcs0:
    - {shard-rkl}:        [FAIL][44] ([i915#2842]) -> [PASS][45] +1 similar issue
   [44]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-6/igt@gem_exec_fair@basic-pace-solo@rcs0.html
   [45]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-5/igt@gem_exec_fair@basic-pace-solo@rcs0.html

  * igt@gem_exec_reloc@basic-softpin:
    - {shard-rkl}:        [SKIP][46] ([i915#3281]) -> [PASS][47]
   [46]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-6/igt@gem_exec_reloc@basic-softpin.html
   [47]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-5/igt@gem_exec_reloc@basic-softpin.html

  * igt@gem_mmap_wc@set-cache-level:
    - {shard-rkl}:        [SKIP][48] ([i915#1850]) -> [PASS][49]
   [48]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-3/igt@gem_mmap_wc@set-cache-level.html
   [49]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@gem_mmap_wc@set-cache-level.html

  * igt@gem_pread@uncached:
    - {shard-rkl}:        [SKIP][50] ([i915#3282]) -> [PASS][51]
   [50]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-3/igt@gem_pread@uncached.html
   [51]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-5/igt@gem_pread@uncached.html

  * igt@gen9_exec_parse@shadow-peek:
    - {shard-rkl}:        [SKIP][52] ([i915#2527]) -> [PASS][53] +1 similar issue
   [52]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-1/igt@gen9_exec_parse@shadow-peek.html
   [53]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-5/igt@gen9_exec_parse@shadow-peek.html

  * igt@i915_hangman@engine-engine-error@bcs0:
    - {shard-rkl}:        [SKIP][54] ([i915#6258]) -> [PASS][55]
   [54]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-5/igt@i915_hangman@engine-engine-error@bcs0.html
   [55]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@i915_hangman@engine-engine-error@bcs0.html

  * igt@i915_pm_lpsp@kms-lpsp@kms-lpsp-hdmi-a:
    - {shard-dg1}:        [SKIP][56] ([i915#1937]) -> [PASS][57]
   [56]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-dg1-18/igt@i915_pm_lpsp@kms-lpsp@kms-lpsp-hdmi-a.html
   [57]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-14/igt@i915_pm_lpsp@kms-lpsp@kms-lpsp-hdmi-a.html

  * igt@i915_pm_rc6_residency@rc6-idle@bcs0:
    - {shard-dg1}:        [FAIL][58] ([i915#3591]) -> [PASS][59]
   [58]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-dg1-12/igt@i915_pm_rc6_residency@rc6-idle@bcs0.html
   [59]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-18/igt@i915_pm_rc6_residency@rc6-idle@bcs0.html

  * igt@i915_pm_rpm@system-suspend-modeset:
    - {shard-rkl}:        [SKIP][60] ([fdo#109308]) -> [PASS][61]
   [60]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-5/igt@i915_pm_rpm@system-suspend-modeset.html
   [61]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@i915_pm_rpm@system-suspend-modeset.html

  * igt@kms_big_fb@x-tiled-32bpp-rotate-0:
    - {shard-rkl}:        [SKIP][62] ([i915#1845] / [i915#4098]) -> [PASS][63] +14 similar issues
   [62]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-5/igt@kms_big_fb@x-tiled-32bpp-rotate-0.html
   [63]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@kms_big_fb@x-tiled-32bpp-rotate-0.html

  * igt@kms_ccs@pipe-a-crc-primary-basic-y_tiled_gen12_rc_ccs_cc:
    - {shard-tglu}:       [SKIP][64] ([i915#7651]) -> [PASS][65] +2 similar issues
   [64]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-tglu-6/igt@kms_ccs@pipe-a-crc-primary-basic-y_tiled_gen12_rc_ccs_cc.html
   [65]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-tglu-8/igt@kms_ccs@pipe-a-crc-primary-basic-y_tiled_gen12_rc_ccs_cc.html

  * igt@kms_cursor_legacy@flip-vs-cursor@atomic-transitions-varying-size:
    - shard-glk:          [FAIL][66] ([i915#2346]) -> [PASS][67] +1 similar issue
   [66]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-glk5/igt@kms_cursor_legacy@flip-vs-cursor@atomic-transitions-varying-size.html
   [67]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk8/igt@kms_cursor_legacy@flip-vs-cursor@atomic-transitions-varying-size.html

  * igt@kms_fbcon_fbt@psr-suspend:
    - {shard-rkl}:        [SKIP][68] ([fdo#110189] / [i915#3955]) -> [PASS][69]
   [68]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-1/igt@kms_fbcon_fbt@psr-suspend.html
   [69]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@kms_fbcon_fbt@psr-suspend.html

  * igt@kms_flip@2x-flip-vs-expired-vblank-interruptible@ac-hdmi-a1-hdmi-a2:
    - shard-glk:          [FAIL][70] ([i915#79]) -> [PASS][71]
   [70]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-glk7/igt@kms_flip@2x-flip-vs-expired-vblank-interruptible@ac-hdmi-a1-hdmi-a2.html
   [71]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-glk2/igt@kms_flip@2x-flip-vs-expired-vblank-interruptible@ac-hdmi-a1-hdmi-a2.html

  * igt@kms_frontbuffer_tracking@psr-1p-primscrn-pri-indfb-draw-render:
    - {shard-rkl}:        [SKIP][72] ([i915#1849] / [i915#4098]) -> [PASS][73] +9 similar issues
   [72]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-2/igt@kms_frontbuffer_tracking@psr-1p-primscrn-pri-indfb-draw-render.html
   [73]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@kms_frontbuffer_tracking@psr-1p-primscrn-pri-indfb-draw-render.html

  * igt@kms_plane@plane-panning-top-left@pipe-a-planes:
    - {shard-rkl}:        [SKIP][74] ([i915#1849] / [i915#3558]) -> [PASS][75] +1 similar issue
   [74]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-2/igt@kms_plane@plane-panning-top-left@pipe-a-planes.html
   [75]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@kms_plane@plane-panning-top-left@pipe-a-planes.html

  * igt@kms_properties@crtc-properties-legacy:
    - {shard-rkl}:        [SKIP][76] ([i915#1849]) -> [PASS][77] +1 similar issue
   [76]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-2/igt@kms_properties@crtc-properties-legacy.html
   [77]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@kms_properties@crtc-properties-legacy.html

  * igt@kms_psr@sprite_plane_onoff:
    - {shard-rkl}:        [SKIP][78] ([i915#1072]) -> [PASS][79] +1 similar issue
   [78]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-1/igt@kms_psr@sprite_plane_onoff.html
   [79]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@kms_psr@sprite_plane_onoff.html

  * igt@perf_pmu@busy-double-start@vcs0:
    - {shard-dg1}:        [FAIL][80] ([i915#4349]) -> [PASS][81] +1 similar issue
   [80]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-dg1-14/igt@perf_pmu@busy-double-start@vcs0.html
   [81]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-19/igt@perf_pmu@busy-double-start@vcs0.html

  * igt@sysfs_heartbeat_interval@precise@vcs0:
    - {shard-dg1}:        [FAIL][82] ([i915#1755]) -> [PASS][83] +1 similar issue
   [82]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-dg1-17/igt@sysfs_heartbeat_interval@precise@vcs0.html
   [83]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-dg1-14/igt@sysfs_heartbeat_interval@precise@vcs0.html

  * igt@testdisplay:
    - {shard-rkl}:        [SKIP][84] ([i915#4098]) -> [PASS][85] +1 similar issue
   [84]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-rkl-4/igt@testdisplay.html
   [85]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-rkl-6/igt@testdisplay.html

  
#### Warnings ####

  * igt@runner@aborted:
    - shard-apl:          ([FAIL][86], [FAIL][87]) ([i915#3002] / [i915#4312]) -> ([FAIL][88], [FAIL][89], [FAIL][90]) ([fdo#109271] / [i915#3002] / [i915#4312])
   [86]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-apl3/igt@runner@aborted.html
   [87]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_12529/shard-apl6/igt@runner@aborted.html
   [88]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl6/igt@runner@aborted.html
   [89]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl6/igt@runner@aborted.html
   [90]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/shard-apl7/igt@runner@aborted.html

  
  {name}: This element is suppressed. This means it is ignored when computing
          the status of the difference (SUCCESS, WARNING, or FAILURE).

  [fdo#103375]: https://bugs.freedesktop.org/show_bug.cgi?id=103375
  [fdo#109271]: https://bugs.freedesktop.org/show_bug.cgi?id=109271
  [fdo#109274]: https://bugs.freedesktop.org/show_bug.cgi?id=109274
  [fdo#109279]: https://bugs.freedesktop.org/show_bug.cgi?id=109279
  [fdo#109280]: https://bugs.freedesktop.org/show_bug.cgi?id=109280
  [fdo#109283]: https://bugs.freedesktop.org/show_bug.cgi?id=109283
  [fdo#109285]: https://bugs.freedesktop.org/show_bug.cgi?id=109285
  [fdo#109289]: https://bugs.freedesktop.org/show_bug.cgi?id=109289
  [fdo#109291]: https://bugs.freedesktop.org/show_bug.cgi?id=109291
  [fdo#109295]: https://bugs.freedesktop.org/show_bug.cgi?id=109295
  [fdo#109300]: https://bugs.freedesktop.org/show_bug.cgi?id=109300
  [fdo#109303]: https://bugs.freedesktop.org/show_bug.cgi?id=109303
  [fdo#109308]: https://bugs.freedesktop.org/show_bug.cgi?id=109308
  [fdo#109309]: https://bugs.freedesktop.org/show_bug.cgi?id=109309
  [fdo#109312]: https://bugs.freedesktop.org/show_bug.cgi?id=109312
  [fdo#109315]: https://bugs.freedesktop.org/show_bug.cgi?id=109315
  [fdo#109506]: https://bugs.freedesktop.org/show_bug.cgi?id=109506
  [fdo#109642]: https://bugs.freedesktop.org/show_bug.cgi?id=109642
  [fdo#110189]: https://bugs.freedesktop.org/show_bug.cgi?id=110189
  [fdo#110723]: https://bugs.freedesktop.org/show_bug.cgi?id=110723
  [fdo#111068]: https://bugs.freedesktop.org/show_bug.cgi?id=111068
  [fdo#111614]: https://bugs.freedesktop.org/show_bug.cgi?id=111614
  [fdo#111615]: https://bugs.freedesktop.org/show_bug.cgi?id=111615
  [fdo#111644]: https://bugs.freedesktop.org/show_bug.cgi?id=111644
  [fdo#111656]: https://bugs.freedesktop.org/show_bug.cgi?id=111656
  [fdo#111825]: https://bugs.freedesktop.org/show_bug.cgi?id=111825
  [fdo#111827]: https://bugs.freedesktop.org/show_bug.cgi?id=111827
  [fdo#112054]: https://bugs.freedesktop.org/show_bug.cgi?id=112054
  [fdo#112283]: https://bugs.freedesktop.org/show_bug.cgi?id=112283
  [i915#1072]: https://gitlab.freedesktop.org/drm/intel/issues/1072
  [i915#132]: https://gitlab.freedesktop.org/drm/intel/issues/132
  [i915#1397]: https://gitlab.freedesktop.org/drm/intel/issues/1397
  [i915#1755]: https://gitlab.freedesktop.org/drm/intel/issues/1755
  [i915#1769]: https://gitlab.freedesktop.org/drm/intel/issues/1769
  [i915#1825]: https://gitlab.freedesktop.org/drm/intel/issues/1825
  [i915#1839]: https://gitlab.freedesktop.org/drm/intel/issues/1839
  [i915#1845]: https://gitlab.freedesktop.org/drm/intel/issues/1845
  [i915#1849]: https://gitlab.freedesktop.org/drm/intel/issues/1849
  [i915#1850]: https://gitlab.freedesktop.org/drm/intel/issues/1850
  [i915#1902]: https://gitlab.freedesktop.org/drm/intel/issues/1902
  [i915#1937]: https://gitlab.freedesktop.org/drm/intel/issues/1937
  [i915#2346]: https://gitlab.freedesktop.org/drm/intel/issues/2346
  [i915#2433]: https://gitlab.freedesktop.org/drm/intel/issues/2433
  [i915#2437]: https://gitlab.freedesktop.org/drm/intel/issues/2437
  [i915#2527]: https://gitlab.freedesktop.org/drm/intel/issues/2527
  [i915#2575]: https://gitlab.freedesktop.org/drm/intel/issues/2575
  [i915#2582]: https://gitlab.freedesktop.org/drm/intel/issues/2582
  [i915#2587]: https://gitlab.freedesktop.org/drm/intel/issues/2587
  [i915#2658]: https://gitlab.freedesktop.org/drm/intel/issues/2658
  [i915#2672]: https://gitlab.freedesktop.org/drm/intel/issues/2672
  [i915#2681]: https://gitlab.freedesktop.org/drm/intel/issues/2681
  [i915#2705]: https://gitlab.freedesktop.org/drm/intel/issues/2705
  [i915#280]: https://gitlab.freedesktop.org/drm/intel/issues/280
  [i915#284]: https://gitlab.freedesktop.org/drm/intel/issues/284
  [i915#2842]: https://gitlab.freedesktop.org/drm/intel/issues/2842
  [i915#2846]: https://gitlab.freedesktop.org/drm/intel/issues/2846
  [i915#2856]: https://gitlab.freedesktop.org/drm/intel/issues/2856
  [i915#2920]: https://gitlab.freedesktop.org/drm/intel/issues/2920
  [i915#2994]: https://gitlab.freedesktop.org/drm/intel/issues/2994
  [i915#3002]: https://gitlab.freedesktop.org/drm/intel/issues/3002
  [i915#3116]: https://gitlab.freedesktop.org/drm/intel/issues/3116
  [i915#3281]: https://gitlab.freedesktop.org/drm/intel/issues/3281
  [i915#3282]: https://gitlab.freedesktop.org/drm/intel/issues/3282
  [i915#3297]: https://gitlab.freedesktop.org/drm/intel/issues/3297
  [i915#3299]: https://gitlab.freedesktop.org/drm/intel/issues/3299
  [i915#3301]: https://gitlab.freedesktop.org/drm/intel/issues/3301
  [i915#3359]: https://gitlab.freedesktop.org/drm/intel/issues/3359
  [i915#3458]: https://gitlab.freedesktop.org/drm/intel/issues/3458
  [i915#3469]: https://gitlab.freedesktop.org/drm/intel/issues/3469
  [i915#3528]: https://gitlab.freedesktop.org/drm/intel/issues/3528
  [i915#3539]: https://gitlab.freedesktop.org/drm/intel/issues/3539
  [i915#3546]: https://gitlab.freedesktop.org/drm/intel/issues/3546
  [i915#3555]: https://gitlab.freedesktop.org/drm/intel/issues/3555
  [i915#3558]: https://gitlab.freedesktop.org/drm/intel/issues/3558
  [i915#3591]: https://gitlab.freedesktop.org/drm/intel/issues/3591
  [i915#3637]: https://gitlab.freedesktop.org/drm/intel/issues/3637
  [i915#3638]: https://gitlab.freedesktop.org/drm/intel/issues/3638
  [i915#3689]: https://gitlab.freedesktop.org/drm/intel/issues/3689
  [i915#3734]: https://gitlab.freedesktop.org/drm/intel/issues/3734
  [i915#3742]: https://gitlab.freedesktop.org/drm/intel/issues/3742
  [i915#3804]: https://gitlab.freedesktop.org/drm/intel/issues/3804
  [i915#3825]: https://gitlab.freedesktop.org/drm/intel/issues/3825
  [i915#3826]: https://gitlab.freedesktop.org/drm/intel/issues/3826
  [i915#3886]: https://gitlab.freedesktop.org/drm/intel/issues/3886
  [i915#3955]: https://gitlab.freedesktop.org/drm/intel/issues/3955
  [i915#4070]: https://gitlab.freedesktop.org/drm/intel/issues/4070
  [i915#4077]: https://gitlab.freedesktop.org/drm/intel/issues/4077
  [i915#4079]: https://gitlab.freedesktop.org/drm/intel/issues/4079
  [i915#4083]: https://gitlab.freedesktop.org/drm/intel/issues/4083
  [i915#4098]: https://gitlab.freedesktop.org/drm/intel/issues/4098
  [i915#4103]: https://gitlab.freedesktop.org/drm/intel/issues/4103
  [i915#4270]: https://gitlab.freedesktop.org/drm/intel/issues/4270
  [i915#4312]: https://gitlab.freedesktop.org/drm/intel/issues/4312
  [i915#4349]: https://gitlab.freedesktop.org/drm/intel/issues/4349
  [i915#4387]: https://gitlab.freedesktop.org/drm/intel/issues/4387
  [i915#4525]: https://gitlab.freedesktop.org/drm/intel/issues/4525
  [i915#4538]: https://gitlab.freedesktop.org/drm/intel/issues/4538
  [i915#4573]: https://gitlab.freedesktop.org/drm/intel/issues/4573
  [i915#4613]: https://gitlab.freedesktop.org/drm/intel/issues/4613
  [i915#4767]: https://gitlab.freedesktop.org/drm/intel/issues/4767
  [i915#4771]: https://gitlab.freedesktop.org/drm/intel/issues/4771
  [i915#4812]: https://gitlab.freedesktop.org/drm/intel/issues/4812
  [i915#4833]: https://gitlab.freedesktop.org/drm/intel/issues/4833
  [i915#4852]: https://gitlab.freedesktop.org/drm/intel/issues/4852
  [i915#4877]: https://gitlab.freedesktop.org/drm/intel/issues/4877
  [i915#4880]: https://gitlab.freedesktop.org/drm/intel/issues/4880
  [i915#5176]: https://gitlab.freedesktop.org/drm/intel/issues/5176
  [i915#5234]: https://gitlab.freedesktop.org/drm/intel/issues/5234
  [i915#5235]: https://gitlab.freedesktop.org/drm/intel/issues/5235
  [i915#5286]: https://gitlab.freedesktop.org/drm/intel/issues/5286
  [i915#5288]: https://gitlab.freedesktop.org/drm/intel/issues/5288
  [i915#5289]: https://gitlab.freedesktop.org/drm/intel/issues/5289
  [i915#5325]: https://gitlab.freedesktop.org/drm/intel/issues/5325
  [i915#5327]: https://gitlab.freedesktop.org/drm/intel/issues/5327
  [i915#533]: https://gitlab.freedesktop.org/drm/intel/issues/533
  [i915#5439]: https://gitlab.freedesktop.org/drm/intel/issues/5439
  [i915#5563]: https://gitlab.freedesktop.org/drm/intel/issues/5563
  [i915#5566]: https://gitlab.freedesktop.org/drm/intel/issues/5566
  [i915#5784]: https://gitlab.freedesktop.org/drm/intel/issues/5784
  [i915#6095]: https://gitlab.freedesktop.org/drm/intel/issues/6095
  [i915#6227]: https://gitlab.freedesktop.org/drm/intel/issues/6227
  [i915#6230]: https://gitlab.freedesktop.org/drm/intel/issues/6230
  [i915#6248]: https://gitlab.freedesktop.org/drm/intel/issues/6248
  [i915#6252]: https://gitlab.freedesktop.org/drm/intel/issues/6252
  [i915#6258]: https://gitlab.freedesktop.org/drm/intel/issues/6258
  [i915#6334]: https://gitlab.freedesktop.org/drm/intel/issues/6334
  [i915#6335]: https://gitlab.freedesktop.org/drm/intel/issues/6335
  [i915#6344]: https://gitlab.freedesktop.org/drm/intel/issues/6344
  [i915#6355]: https://gitlab.freedesktop.org/drm/intel/issues/6355
  [i915#6412]: https://gitlab.freedesktop.org/drm/intel/issues/6412
  [i915#6433]: https://gitlab.freedesktop.org/drm/intel/issues/6433
  [i915#6497]: https://gitlab.freedesktop.org/drm/intel/issues/6497
  [i915#6524]: https://gitlab.freedesktop.org/drm/intel/issues/6524
  [i915#658]: https://gitlab.freedesktop.org/drm/intel/issues/658
  [i915#6590]: https://gitlab.freedesktop.org/drm/intel/issues/6590
  [i915#6621]: https://gitlab.freedesktop.org/drm/intel/issues/6621
  [i915#6768]: https://gitlab.freedesktop.org/drm/intel/issues/6768
  [i915#6946]: https://gitlab.freedesktop.org/drm/intel/issues/6946
  [i915#6953]: https://gitlab.freedesktop.org/drm/intel/issues/6953
  [i915#7052]: https://gitlab.freedesktop.org/drm/intel/issues/7052
  [i915#7116]: https://gitlab.freedesktop.org/drm/intel/issues/7116
  [i915#7118]: https://gitlab.freedesktop.org/drm/intel/issues/7118
  [i915#7128]: https://gitlab.freedesktop.org/drm/intel/issues/7128
  [i915#716]: https://gitlab.freedesktop.org/drm/intel/issues/716
  [i915#7276]: https://gitlab.freedesktop.org/drm/intel/issues/7276
  [i915#7561]: https://gitlab.freedesktop.org/drm/intel/issues/7561
  [i915#7582]: https://gitlab.freedesktop.org/drm/intel/issues/7582
  [i915#7651]: https://gitlab.freedesktop.org/drm/intel/issues/7651
  [i915#7697]: https://gitlab.freedesktop.org/drm/intel/issues/7697
  [i915#7701]: https://gitlab.freedesktop.org/drm/intel/issues/7701
  [i915#7708]: https://gitlab.freedesktop.org/drm/intel/issues/7708
  [i915#7711]: https://gitlab.freedesktop.org/drm/intel/issues/7711
  [i915#79]: https://gitlab.freedesktop.org/drm/intel/issues/79


Build changes
-------------

  * CI: CI-20190529 -> None
  * IGT: IGT_7104 -> IGTPW_8283
  * Piglit: piglit_4509 -> None

  CI-20190529: 20190529
  CI_DRM_12529: 0ca1a1772701d5c3350d8736080e528e2f2f413e @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_8283: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/index.html
  IGT_7104: fe5def13049225967770eaaf19ec01ef80e2adc5 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git
  piglit_4509: fdc5a4ca11124ab8413c7988896eec4c97336694 @ git://anongit.freedesktop.org/piglit

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_8283/index.html

[-- Attachment #2: Type: text/html, Size: 32577 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI
  2022-12-29 19:27 [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Mark Yacoub
  2022-12-29 21:16 ` [igt-dev] ✓ Fi.CI.BAT: success for testing headers in CI (rev2) Patchwork
  2022-12-29 23:27 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork
@ 2022-12-30 12:51 ` Jani Nikula
  2023-01-02 16:50   ` Kamil Konieczny
  2 siblings, 1 reply; 5+ messages in thread
From: Jani Nikula @ 2022-12-30 12:51 UTC (permalink / raw)
  To: Mark Yacoub, igt-dev; +Cc: Mark Yacoub

On Thu, 29 Dec 2022, Mark Yacoub <markyacoub@chromium.org> wrote:
> From: Mark Yacoub <markyacoub@google.com>

A commit message would be appreciated to let us know what you're doing,
regardless of the "DO NOT REVIEW" part.

Thanks,
Jani.

>
> ---
>  docs/chamelium.txt                            |    2 +-
>  lib/igt_edid.h                                |    1 +
>  lib/igt_eld.h                                 |    1 +
>  lib/monitor_edids/monitor_edids_helper.c      |    2 +-
>  tests/chamelium/kms_chamelium.c               | 3132 -----------------
>  tests/chamelium/kms_chamelium_audio.c         |  858 +++++
>  ...olor_chamelium.c => kms_chamelium_color.c} |    0
>  tests/chamelium/kms_chamelium_edid.c          |  543 +++
>  tests/chamelium/kms_chamelium_frames.c        | 1085 ++++++
>  tests/chamelium/kms_chamelium_helper.c        |  330 ++
>  tests/chamelium/kms_chamelium_helper.h        |   74 +
>  tests/chamelium/kms_chamelium_hpd.c           |  512 +++
>  tests/intel-ci/blacklist.txt                  |    2 +-
>  tests/intel-ci/fast-feedback.testlist         |   18 +-
>  tests/kms_color_helper.h                      |    2 +-
>  tests/meson.build                             |   14 +-
>  tests/vc4_ci/vc4-chamelium-fast.testlist      |   28 +-
>  17 files changed, 3440 insertions(+), 3164 deletions(-)
>  delete mode 100644 tests/chamelium/kms_chamelium.c
>  create mode 100644 tests/chamelium/kms_chamelium_audio.c
>  rename tests/chamelium/{kms_color_chamelium.c => kms_chamelium_color.c} (100%)
>  create mode 100644 tests/chamelium/kms_chamelium_edid.c
>  create mode 100644 tests/chamelium/kms_chamelium_frames.c
>  create mode 100644 tests/chamelium/kms_chamelium_helper.c
>  create mode 100644 tests/chamelium/kms_chamelium_helper.h
>  create mode 100644 tests/chamelium/kms_chamelium_hpd.c
>
> diff --git a/docs/chamelium.txt b/docs/chamelium.txt
> index c4c22468..f82c8b0c 100644
> --- a/docs/chamelium.txt
> +++ b/docs/chamelium.txt
> @@ -241,7 +241,7 @@ Current Support in IGT
>  
>  Support for the Chamelium platform in IGT is found in the following places:
>  * lib/igt_chamelium.c: library with Chamelium-related helpers
> -* tests/kms_chamelium.c: sub-tests using the Chamelium
> +* tests/kms_chamelium_*.c: sub-tests using the Chamelium
>  
>  As of early April 2019, the following features are tested by IGT:
>  * Pixel-by-pixel frame integrity tests for DP and HDMI
> diff --git a/lib/igt_edid.h b/lib/igt_edid.h
> index 477f16c2..85a9ef5e 100644
> --- a/lib/igt_edid.h
> +++ b/lib/igt_edid.h
> @@ -29,6 +29,7 @@
>  #include "config.h"
>  
>  #include <stdint.h>
> +#include <stddef.h>
>  
>  #include <xf86drmMode.h>
>  
> diff --git a/lib/igt_eld.h b/lib/igt_eld.h
> index 30d7012d..1a46b6d2 100644
> --- a/lib/igt_eld.h
> +++ b/lib/igt_eld.h
> @@ -29,6 +29,7 @@
>  #include "config.h"
>  
>  #include <stdbool.h>
> +#include <stddef.h>
>  
>  #include "igt_edid.h"
>  
> diff --git a/lib/monitor_edids/monitor_edids_helper.c b/lib/monitor_edids/monitor_edids_helper.c
> index 41f199bd..1cbf1c22 100644
> --- a/lib/monitor_edids/monitor_edids_helper.c
> +++ b/lib/monitor_edids/monitor_edids_helper.c
> @@ -1,4 +1,4 @@
> -// SPDX-License-Identifier: GPL-2.0
> +// SPDX-License-Identifier: MIT
>  /*
>   * A helper library for parsing and making use of real EDID data from monitors
>   * and make them compatible with IGT and Chamelium.
> diff --git a/tests/chamelium/kms_chamelium.c b/tests/chamelium/kms_chamelium.c
> deleted file mode 100644
> index 3c4b4d75..00000000
> --- a/tests/chamelium/kms_chamelium.c
> +++ /dev/null
> @@ -1,3132 +0,0 @@
> -/*
> - * Copyright © 2016 Red Hat 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 (including the next
> - * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
> - *
> - * Authors:
> - *    Lyude Paul <lyude@redhat.com>
> - */
> -
> -#include "config.h"
> -#include "igt.h"
> -#include "igt_vc4.h"
> -#include "igt_edid.h"
> -#include "igt_eld.h"
> -#include "igt_infoframe.h"
> -#include "monitor_edids/dp_edids.h"
> -#include "monitor_edids/hdmi_edids.h"
> -#include "monitor_edids/monitor_edids_helper.h"
> -
> -#include <fcntl.h>
> -#include <pthread.h>
> -#include <string.h>
> -#include <stdatomic.h>
> -// #include <stdio.h>
> -
> -// struct chamelium_edid;
> -
> -enum test_modeset_mode {
> -	TEST_MODESET_ON,
> -	TEST_MODESET_ON_OFF,
> -	TEST_MODESET_OFF,
> -};
> -
> -typedef struct {
> -	struct chamelium *chamelium;
> -	struct chamelium_port **ports;
> -	igt_display_t display;
> -	int port_count;
> -
> -	int drm_fd;
> -
> -	struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT];
> -} data_t;
> -
> -#define ONLINE_TIMEOUT 20 /* seconds */
> -
> -#define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */
> -#define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */
> -
> -#define HPD_TOGGLE_COUNT_VGA 5
> -#define HPD_TOGGLE_COUNT_DP_HDMI 15
> -#define HPD_TOGGLE_COUNT_FAST 3
> -
> -static void
> -get_connectors_link_status_failed(data_t *data, bool *link_status_failed)
> -{
> -	drmModeConnector *connector;
> -	uint64_t link_status;
> -	drmModePropertyPtr prop;
> -	int p;
> -
> -	for (p = 0; p < data->port_count; p++) {
> -		connector = chamelium_port_get_connector(data->chamelium,
> -							 data->ports[p], false);
> -
> -		igt_assert(kmstest_get_property(data->drm_fd,
> -						connector->connector_id,
> -						DRM_MODE_OBJECT_CONNECTOR,
> -						"link-status", NULL,
> -						&link_status, &prop));
> -
> -		link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD;
> -
> -		drmModeFreeProperty(prop);
> -		drmModeFreeConnector(connector);
> -	}
> -}
> -
> -/* Wait for hotplug and return the remaining time left from timeout */
> -static bool wait_for_hotplug(struct udev_monitor *mon, int *timeout)
> -{
> -	struct timespec start, end;
> -	int elapsed;
> -	bool detected;
> -
> -	igt_assert_eq(igt_gettime(&start), 0);
> -	detected = igt_hotplug_detected(mon, *timeout);
> -	igt_assert_eq(igt_gettime(&end), 0);
> -
> -	elapsed = igt_time_elapsed(&start, &end);
> -	igt_assert_lte(0, elapsed);
> -	*timeout = max(0, *timeout - elapsed);
> -
> -	return detected;
> -}
> -
> -static void
> -wait_for_connector_after_hotplug(data_t *data, struct udev_monitor *mon,
> -				 struct chamelium_port *port,
> -				 drmModeConnection status)
> -{
> -	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> -	int hotplug_count = 0;
> -
> -	igt_debug("Waiting for %s to get %s after a hotplug event...\n",
> -			  chamelium_port_get_name(port),
> -			  kmstest_connector_status_str(status));
> -
> -	while (timeout > 0) {
> -		if (!wait_for_hotplug(mon, &timeout))
> -			break;
> -
> -		hotplug_count++;
> -
> -		if (chamelium_reprobe_connector(&data->display, data->chamelium,
> -						port) == status)
> -			return;
> -	}
> -
> -	igt_assert_f(false, "Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n",
> -			    chamelium_port_get_name(port),
> -			    kmstest_connector_status_str(status),
> -			    kmstest_connector_status_str(chamelium_reprobe_connector(&data->display, data->chamelium, port)), hotplug_count);
> -}
> -
> -
> -static int chamelium_vga_modes[][2] = {
> -	{ 1600, 1200 },
> -	{ 1920, 1200 },
> -	{ 1920, 1080 },
> -	{ 1680, 1050 },
> -	{ 1280, 1024 },
> -	{ 1280, 960 },
> -	{ 1440, 900 },
> -	{ 1280, 800 },
> -	{ 1024, 768 },
> -	{ 1360, 768 },
> -	{ 1280, 720 },
> -	{ 800, 600 },
> -	{ 640, 480 },
> -	{ -1, -1 },
> -};
> -
> -static bool
> -prune_vga_mode(data_t *data, drmModeModeInfo *mode)
> -{
> -	int i = 0;
> -
> -	while (chamelium_vga_modes[i][0] != -1) {
> -		if (mode->hdisplay == chamelium_vga_modes[i][0] &&
> -		    mode->vdisplay == chamelium_vga_modes[i][1])
> -			return false;
> -
> -		i++;
> -	}
> -
> -	return true;
> -}
> -
> -static bool
> -check_analog_bridge(data_t *data, struct chamelium_port *port)
> -{
> -	drmModePropertyBlobPtr edid_blob = NULL;
> -	drmModeConnector *connector = chamelium_port_get_connector(
> -	    data->chamelium, port, false);
> -	uint64_t edid_blob_id;
> -	const struct edid *edid;
> -	char edid_vendor[3];
> -
> -	if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) {
> -		drmModeFreeConnector(connector);
> -		return false;
> -	}
> -
> -	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> -					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> -					&edid_blob_id, NULL));
> -	igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
> -						      edid_blob_id));
> -
> -	edid = (const struct edid *) edid_blob->data;
> -	edid_get_mfg(edid, edid_vendor);
> -
> -	drmModeFreePropertyBlob(edid_blob);
> -	drmModeFreeConnector(connector);
> -
> -	/* Analog bridges provide their own EDID */
> -	if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' ||
> -	    edid_vendor[2] != 'T')
> -		return true;
> -
> -	return false;
> -}
> -
> -static void chamelium_paint_xr24_pattern(uint32_t *data,
> -					 size_t width, size_t height,
> -					 size_t stride, size_t block_size)
> -{
> -	uint32_t colors[] = { 0xff000000,
> -			      0xffff0000,
> -			      0xff00ff00,
> -			      0xff0000ff,
> -			      0xffffffff };
> -	unsigned i, j;
> -
> -	for (i = 0; i < height; i++)
> -		for (j = 0; j < width; j++)
> -			*(data + i * stride / 4 + j) = colors[((j / block_size) + (i / block_size)) % 5];
> -}
> -
> -static int chamelium_get_pattern_fb(data_t *data, size_t width, size_t height,
> -				    uint32_t fourcc, size_t block_size,
> -				    struct igt_fb *fb)
> -{
> -	int fb_id;
> -	void *ptr;
> -
> -	igt_assert(fourcc == DRM_FORMAT_XRGB8888);
> -
> -	fb_id = igt_create_fb(data->drm_fd, width, height, fourcc,
> -			      DRM_FORMAT_MOD_LINEAR, fb);
> -	igt_assert(fb_id > 0);
> -
> -	ptr = igt_fb_map_buffer(fb->fd, fb);
> -	igt_assert(ptr);
> -
> -	chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0],
> -				     block_size);
> -	igt_fb_unmap_buffer(fb, ptr);
> -
> -	return fb_id;
> -}
> -
> -static void
> -enable_output(data_t *data,
> -	      struct chamelium_port *port,
> -	      igt_output_t *output,
> -	      drmModeModeInfo *mode,
> -	      struct igt_fb *fb)
> -{
> -	igt_display_t *display = output->display;
> -	igt_plane_t *primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -	drmModeConnector *connector = chamelium_port_get_connector(
> -	    data->chamelium, port, false);
> -
> -	igt_assert(primary);
> -
> -	igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
> -	igt_plane_set_fb(primary, fb);
> -	igt_output_override_mode(output, mode);
> -
> -	/* Clear any color correction values that might be enabled */
> -	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT))
> -		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_DEGAMMA_LUT, NULL, 0);
> -	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT))
> -		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_GAMMA_LUT, NULL, 0);
> -	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM))
> -		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM, NULL, 0);
> -
> -	igt_display_commit2(display, COMMIT_ATOMIC);
> -
> -	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA)
> -		usleep(250000);
> -
> -	drmModeFreeConnector(connector);
> -}
> -
> -static enum pipe get_pipe_for_output(igt_display_t *display, igt_output_t *output)
> -{
> -	enum pipe pipe;
> -
> -	for_each_pipe(display, pipe) {
> -		if (igt_pipe_connector_valid(pipe, output)) {
> -			return pipe;
> -		}
> -	}
> -
> -	igt_assert_f(false, "No pipe found for output %s\n",
> -		     igt_output_name(output));
> -}
> -
> -static void create_fb_for_mode(data_t *data, struct igt_fb *fb, drmModeModeInfo *mode)
> -{
> -	int fb_id;
> -
> -	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> -					 DRM_FORMAT_XRGB8888, 64, fb);
> -
> -	igt_assert(fb_id > 0);
> -}
> -
> -static drmModeModeInfo get_mode_for_port(struct chamelium *chamelium,
> -					 struct chamelium_port *port)
> -{
> -	drmModeConnector *connector = chamelium_port_get_connector(chamelium,
> -								   port, false);
> -	drmModeModeInfo mode;
> -	igt_assert(&connector->modes[0] != NULL);
> -	memcpy(&mode, &connector->modes[0], sizeof(mode));
> -	drmModeFreeConnector(connector);
> -	return mode;
> -}
> -
> -static igt_output_t *get_output_for_port(data_t *data,
> -					 struct chamelium_port *port)
> -{
> -	drmModeConnector *connector =
> -		chamelium_port_get_connector(data->chamelium, port, true);
> -	igt_output_t *output = igt_output_from_connector(&data->display,
> -							 connector);
> -	drmModeFreeConnector(connector);
> -	igt_assert(output != NULL);
> -	return output;
> -}
> -
> -static const char test_hotplug_for_each_pipe_desc[] =
> -	"Check that we get uevents and updated connector status on "
> -	"hotplug and unplug for each pipe with valid output";
> -static void
> -test_hotplug_for_each_pipe(data_t *data, struct chamelium_port *port)
> -{
> -	igt_output_t *output;
> -	enum pipe pipe;
> -	struct udev_monitor *mon = igt_watch_uevents();
> -
> -	chamelium_reset_state(&data->display,
> -			      data->chamelium,
> -			      port,
> -			      data->ports,
> -			      data->port_count);
> -
> -	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> -	/* Disconnect if any port got connected */
> -	chamelium_unplug(data->chamelium, port);
> -	wait_for_connector_after_hotplug(data, mon, port,
> -			DRM_MODE_DISCONNECTED);
> -
> -	for_each_pipe(&data->display, pipe) {
> -		igt_flush_uevents(mon);
> -		/* Check if we get a sysfs hotplug event */
> -		chamelium_plug(data->chamelium, port);
> -		wait_for_connector_after_hotplug(data, mon, port,
> -				DRM_MODE_CONNECTED);
> -		igt_flush_uevents(mon);
> -		output = get_output_for_port(data, port);
> -
> -		/* If pipe is valid for output then set it */
> -		if (igt_pipe_connector_valid(pipe, output)) {
> -			igt_output_set_pipe(output, pipe);
> -			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> -		}
> -
> -		chamelium_unplug(data->chamelium, port);
> -		wait_for_connector_after_hotplug(data, mon, port,
> -				DRM_MODE_DISCONNECTED);
> -		igt_flush_uevents(mon);
> -	}
> -
> -	igt_cleanup_uevents(mon);
> -	igt_hpd_storm_reset(data->drm_fd);
> -}
> -
> -static const char test_basic_hotplug_desc[] =
> -	"Check that we get uevents and updated connector status on "
> -	"hotplug and unplug";
> -static void
> -test_hotplug(data_t *data, struct chamelium_port *port, int toggle_count,
> -	     enum test_modeset_mode modeset_mode)
> -{
> -	int i;
> -	enum pipe pipe;
> -	struct igt_fb fb = {0};
> -	drmModeModeInfo mode;
> -	struct udev_monitor *mon = igt_watch_uevents();
> -	igt_output_t *output = get_output_for_port(data, port);
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium, NULL,
> -			      data->ports, data->port_count);
> -
> -
> -	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> -
> -	for (i = 0; i < toggle_count; i++) {
> -		igt_flush_uevents(mon);
> -
> -		/* Check if we get a sysfs hotplug event */
> -		chamelium_plug(data->chamelium, port);
> -
> -		wait_for_connector_after_hotplug(data, mon, port,
> -						 DRM_MODE_CONNECTED);
> -		igt_flush_uevents(mon);
> -
> -		if (modeset_mode == TEST_MODESET_ON_OFF ||
> -		    (modeset_mode == TEST_MODESET_ON && i == 0 )) {
> -			if (i == 0) {
> -				/* We can only get mode and pipe once we are connected */
> -				output = get_output_for_port(data, port);
> -				pipe = get_pipe_for_output(&data->display, output);
> -				mode = get_mode_for_port(data->chamelium, port);
> -				create_fb_for_mode(data, &fb, &mode);
> -			}
> -
> -			igt_output_set_pipe(output, pipe);
> -			enable_output(data, port, output, &mode, &fb);
> -		}
> -
> -		/* Now check if we get a hotplug from disconnection */
> -		chamelium_unplug(data->chamelium, port);
> -
> -		wait_for_connector_after_hotplug(data, mon, port,
> -						 DRM_MODE_DISCONNECTED);
> -
> -		igt_flush_uevents(mon);
> -
> -		if (modeset_mode == TEST_MODESET_ON_OFF) {
> -			igt_output_set_pipe(output, PIPE_NONE);
> -			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> -		}
> -	}
> -
> -	igt_cleanup_uevents(mon);
> -	igt_hpd_storm_reset(data->drm_fd);
> -	igt_remove_fb(data->drm_fd, &fb);
> -}
> -
> -static void set_edid(data_t *data, struct chamelium_port *port,
> -		     enum igt_custom_edid_type edid)
> -{
> -	chamelium_port_set_edid(data->chamelium, port, data->edids[edid]);
> -}
> -
> -static const char igt_custom_edid_type_read_desc[] =
> -	"Make sure the EDID exposed by KMS is the same as the screen's";
> -static void
> -igt_custom_edid_type_read(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid)
> -{
> -	drmModePropertyBlobPtr edid_blob = NULL;
> -	drmModeConnector *connector;
> -	size_t raw_edid_size;
> -	const struct edid *raw_edid;
> -	uint64_t edid_blob_id;
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	set_edid(data, port, edid);
> -	chamelium_plug(data->chamelium, port);
> -	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> -					      port, DRM_MODE_CONNECTED);
> -
> -	igt_skip_on(check_analog_bridge(data, port));
> -
> -	connector = chamelium_port_get_connector(data->chamelium, port, true);
> -	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> -					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> -					&edid_blob_id, NULL));
> -	igt_assert(edid_blob_id != 0);
> -	igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
> -						      edid_blob_id));
> -
> -	raw_edid = chamelium_edid_get_raw(data->edids[edid], port);
> -	raw_edid_size = edid_get_size(raw_edid);
> -	igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0);
> -
> -	drmModeFreePropertyBlob(edid_blob);
> -	drmModeFreeConnector(connector);
> -}
> -
> -static void
> -try_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> -		       enum igt_suspend_state state, enum igt_suspend_test test,
> -		       struct udev_monitor *mon, bool connected)
> -{
> -	drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED :
> -						     DRM_MODE_CONNECTED;
> -	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> -	int delay;
> -	int p;
> -
> -	igt_flush_uevents(mon);
> -
> -	delay = igt_get_autoresume_delay(state) * 1000 / 2;
> -
> -	if (port) {
> -		chamelium_schedule_hpd_toggle(data->chamelium, port, delay,
> -					      !connected);
> -	} else {
> -		for (p = 0; p < data->port_count; p++) {
> -			port = data->ports[p];
> -			chamelium_schedule_hpd_toggle(data->chamelium, port,
> -						      delay, !connected);
> -		}
> -
> -		port = NULL;
> -	}
> -
> -	igt_system_suspend_autoresume(state, test);
> -	igt_assert(wait_for_hotplug(mon, &timeout));
> -	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> -
> -	if (port) {
> -		igt_assert_eq(chamelium_reprobe_connector(&data->display,
> -							  data->chamelium,
> -							  port),
> -							  target_state);
> -	} else {
> -		for (p = 0; p < data->port_count; p++) {
> -			drmModeConnection current_state;
> -
> -			port = data->ports[p];
> -			/*
> -			 * There could be as many hotplug events sent by
> -			 * driver as connectors we scheduled an HPD toggle on
> -			 * above, depending on timing. So if we're not seeing
> -			 * the expected connector state try to wait for an HPD
> -			 * event for each connector/port.
> -			 */
> -			current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port);
> -			if (p > 0 && current_state != target_state) {
> -				igt_assert(wait_for_hotplug(mon, &timeout));
> -				current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port);
> -			}
> -
> -			igt_assert_eq(current_state, target_state);
> -		}
> -
> -		port = NULL;
> -	}
> -}
> -
> -static const char test_suspend_resume_hpd_desc[] =
> -	"Toggle HPD during suspend, check that uevents are sent and connector "
> -	"status is updated";
> -static void
> -test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> -			enum igt_suspend_state state,
> -			enum igt_suspend_test test)
> -{
> -	struct udev_monitor *mon = igt_watch_uevents();
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	/* Make sure we notice new connectors after resuming */
> -	try_suspend_resume_hpd(data, port, state, test, mon, false);
> -
> -	/* Now make sure we notice disconnected connectors after resuming */
> -	try_suspend_resume_hpd(data, port, state, test, mon, true);
> -
> -	igt_cleanup_uevents(mon);
> -}
> -
> -static const char test_suspend_resume_hpd_common_desc[] =
> -	"Toggle HPD during suspend on all connectors, check that uevents are "
> -	"sent and connector status is updated";
> -static void
> -test_suspend_resume_hpd_common(data_t *data, enum igt_suspend_state state,
> -			       enum igt_suspend_test test)
> -{
> -	struct udev_monitor *mon = igt_watch_uevents();
> -	struct chamelium_port *port;
> -	int p;
> -
> -	for (p = 0; p < data->port_count; p++) {
> -		port = data->ports[p];
> -		igt_debug("Testing port %s\n", chamelium_port_get_name(port));
> -	}
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium, NULL,
> -			      data->ports, data->port_count);
> -
> -	/* Make sure we notice new connectors after resuming */
> -	try_suspend_resume_hpd(data, NULL, state, test, mon, false);
> -
> -	/* Now make sure we notice disconnected connectors after resuming */
> -	try_suspend_resume_hpd(data, NULL, state, test, mon, true);
> -
> -	igt_cleanup_uevents(mon);
> -}
> -
> -static const char test_suspend_resume_edid_change_desc[] =
> -	"Simulate a screen being unplugged and another screen being plugged "
> -	"during suspend, check that a uevent is sent and connector status is "
> -	"updated";
> -static void
> -test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
> -				enum igt_suspend_state state,
> -				enum igt_suspend_test test,
> -				enum igt_custom_edid_type edid,
> -				enum igt_custom_edid_type alt_edid)
> -{
> -	struct udev_monitor *mon = igt_watch_uevents();
> -	bool link_status_failed[2][data->port_count];
> -	int p;
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	/* Catch the event and flush all remaining ones. */
> -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> -	igt_flush_uevents(mon);
> -
> -	/* First plug in the port */
> -	set_edid(data, port, edid);
> -	chamelium_plug(data->chamelium, port);
> -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> -
> -	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> -					      port, DRM_MODE_CONNECTED);
> -
> -	/*
> -	 * Change the edid before we suspend. On resume, the machine should
> -	 * notice the EDID change and fire a hotplug event.
> -	 */
> -	set_edid(data, port, alt_edid);
> -
> -	get_connectors_link_status_failed(data, link_status_failed[0]);
> -
> -	igt_flush_uevents(mon);
> -
> -	igt_system_suspend_autoresume(state, test);
> -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> -	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> -
> -	get_connectors_link_status_failed(data, link_status_failed[1]);
> -
> -	for (p = 0; p < data->port_count; p++)
> -		igt_skip_on(!link_status_failed[0][p] && link_status_failed[1][p]);
> -}
> -
> -static igt_output_t *
> -prepare_output(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid)
> -{
> -	igt_display_t *display = &data->display;
> -	igt_output_t *output;
> -	enum pipe pipe;
> -
> -	/* The chamelium's default EDID has a lot of resolutions, way more then
> -	 * we need to test. Additionally the default EDID doesn't support HDMI
> -	 * audio.
> -	 */
> -	set_edid(data, port, edid);
> -
> -	chamelium_plug(data->chamelium, port);
> -	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> -					      port, DRM_MODE_CONNECTED);
> -
> -	igt_display_reset(display);
> -
> -	output = get_output_for_port(data, port);
> -
> -	/* Refresh pipe to update connected status */
> -	igt_output_set_pipe(output, PIPE_NONE);
> -
> -	pipe = get_pipe_for_output(display, output);
> -	igt_output_set_pipe(output, pipe);
> -
> -	return output;
> -}
> -
> -static void do_test_display(data_t *data, struct chamelium_port *port,
> -			    igt_output_t *output, drmModeModeInfo *mode,
> -			    uint32_t fourcc, enum chamelium_check check,
> -			    int count)
> -{
> -	struct chamelium_fb_crc_async_data *fb_crc;
> -	struct igt_fb frame_fb, fb;
> -	int i, fb_id, captured_frame_count;
> -	int frame_id;
> -
> -	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> -					 DRM_FORMAT_XRGB8888, 64, &fb);
> -	igt_assert(fb_id > 0);
> -
> -	frame_id = igt_fb_convert(&frame_fb, &fb, fourcc,
> -				  DRM_FORMAT_MOD_LINEAR);
> -	igt_assert(frame_id > 0);
> -
> -	if (check == CHAMELIUM_CHECK_CRC)
> -		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> -								&fb);
> -
> -	enable_output(data, port, output, mode, &frame_fb);
> -
> -	if (check == CHAMELIUM_CHECK_CRC) {
> -		igt_crc_t *expected_crc;
> -		igt_crc_t *crc;
> -
> -		/* We want to keep the display running for a little bit, since
> -		 * there's always the potential the driver isn't able to keep
> -		 * the display running properly for very long
> -		 */
> -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count);
> -		crc = chamelium_read_captured_crcs(data->chamelium,
> -						   &captured_frame_count);
> -
> -		igt_assert(captured_frame_count == count);
> -
> -		igt_debug("Captured %d frames\n", captured_frame_count);
> -
> -		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> -
> -		for (i = 0; i < captured_frame_count; i++)
> -			chamelium_assert_crc_eq_or_dump(data->chamelium,
> -							expected_crc, &crc[i],
> -							&fb, i);
> -
> -		free(expected_crc);
> -		free(crc);
> -	} else if (check == CHAMELIUM_CHECK_ANALOG ||
> -		   check == CHAMELIUM_CHECK_CHECKERBOARD) {
> -		struct chamelium_frame_dump *dump;
> -
> -		igt_assert(count == 1);
> -
> -		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> -						  0, 0);
> -
> -		if (check == CHAMELIUM_CHECK_ANALOG)
> -			chamelium_crop_analog_frame(dump, mode->hdisplay,
> -						    mode->vdisplay);
> -
> -		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> -						     dump, &fb, check);
> -		chamelium_destroy_frame_dump(dump);
> -	}
> -
> -	igt_remove_fb(data->drm_fd, &frame_fb);
> -	igt_remove_fb(data->drm_fd, &fb);
> -}
> -
> -static const char test_display_one_mode_desc[] =
> -	"Pick the first mode of the IGT base EDID, display and capture a few "
> -	"frames, then check captured frames are correct";
> -static void test_display_one_mode(data_t *data, struct chamelium_port *port,
> -				  uint32_t fourcc, enum chamelium_check check,
> -				  int count)
> -{
> -	drmModeConnector *connector;
> -	drmModeModeInfo *mode;
> -	igt_output_t *output;
> -	igt_plane_t *primary;
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -	igt_assert(primary);
> -
> -	igt_require(igt_plane_has_format_mod(primary, fourcc, DRM_FORMAT_MOD_LINEAR));
> -
> -	mode = &connector->modes[0];
> -	if (check == CHAMELIUM_CHECK_ANALOG) {
> -		bool bridge = check_analog_bridge(data, port);
> -
> -		igt_assert(!(bridge && prune_vga_mode(data, mode)));
> -	}
> -
> -	do_test_display(data, port, output, mode, fourcc, check, count);
> -
> -	drmModeFreeConnector(connector);
> -}
> -
> -static const char test_display_all_modes_desc[] =
> -	"For each mode of the IGT base EDID, display and capture a few "
> -	"frames, then check captured frames are correct";
> -static void test_display_all_modes(data_t *data, struct chamelium_port *port,
> -				   uint32_t fourcc, enum chamelium_check check,
> -				   int count)
> -{
> -	bool bridge;
> -	int i, count_modes;
> -
> -	if (check == CHAMELIUM_CHECK_ANALOG)
> -		bridge = check_analog_bridge(data, port);
> -
> -	i = 0;
> -	do {
> -		igt_output_t *output;
> -		igt_plane_t *primary;
> -		drmModeConnector *connector;
> -		drmModeModeInfo *mode;
> -
> -		/*
> -		 * let's reset state each mode so we will get the
> -		 * HPD pulses realibably
> -		 */
> -		igt_modeset_disable_all_outputs(&data->display);
> -		chamelium_reset_state(&data->display, data->chamelium,
> -				      port, data->ports, data->port_count);
> -
> -		/*
> -		 * modes may change due to mode pruining and link issues, so we
> -		 * need to refresh the connector
> -		 */
> -		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> -		connector = chamelium_port_get_connector(data->chamelium, port,
> -							 false);
> -		primary = igt_output_get_plane_type(output,
> -						    DRM_PLANE_TYPE_PRIMARY);
> -		igt_assert(primary);
> -		igt_require(igt_plane_has_format_mod(primary, fourcc,
> -			    DRM_FORMAT_MOD_LINEAR));
> -
> -		/* we may skip some modes due to above but that's ok */
> -		count_modes = connector->count_modes;
> -		if (i >= count_modes)
> -			break;
> -
> -		mode = &connector->modes[i];
> -
> -		if (check == CHAMELIUM_CHECK_ANALOG && bridge &&
> -		    prune_vga_mode(data, mode))
> -			continue;
> -
> -		do_test_display(data, port, output, mode, fourcc, check,
> -				count);
> -		drmModeFreeConnector(connector);
> -	} while (++i < count_modes);
> -}
> -
> -static const char test_display_frame_dump_desc[] =
> -	"For each mode of the IGT base EDID, display and capture a few "
> -	"frames, then download the captured frames and compare them "
> -	"bit-by-bit to the sent ones";
> -static void
> -test_display_frame_dump(data_t *data, struct chamelium_port *port)
> -{
> -
> -	int i, count_modes;
> -
> -	i = 0;
> -	do {
> -		igt_output_t *output;
> -		igt_plane_t *primary;
> -		struct igt_fb fb;
> -		struct chamelium_frame_dump *frame;
> -		drmModeModeInfo *mode;
> -		drmModeConnector *connector;
> -		int fb_id, j;
> -
> -		/*
> -		 * let's reset state each mode so we will get the
> -		 * HPD pulses realibably
> -		 */
> -		igt_modeset_disable_all_outputs(&data->display);
> -		chamelium_reset_state(&data->display, data->chamelium,
> -				      port, data->ports, data->port_count);
> -
> -		/*
> -		 * modes may change due to mode pruining and link issues, so we
> -		 * need to refresh the connector
> -		 */
> -		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> -		connector = chamelium_port_get_connector(data->chamelium, port,
> -							 false);
> -		primary = igt_output_get_plane_type(output,
> -						    DRM_PLANE_TYPE_PRIMARY);
> -		igt_assert(primary);
> -
> -		/* we may skip some modes due to above but that's ok */
> -		count_modes = connector->count_modes;
> -		if (i >= count_modes)
> -			break;
> -
> -		mode = &connector->modes[i];
> -
> -		fb_id = igt_create_color_pattern_fb(data->drm_fd,
> -						    mode->hdisplay, mode->vdisplay,
> -						    DRM_FORMAT_XRGB8888,
> -						    DRM_FORMAT_MOD_LINEAR,
> -						    0, 0, 0, &fb);
> -		igt_assert(fb_id > 0);
> -
> -		enable_output(data, port, output, mode, &fb);
> -
> -		igt_debug("Reading frame dumps from Chamelium...\n");
> -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
> -		for (j = 0; j < 5; j++) {
> -			frame = chamelium_read_captured_frame(data->chamelium,
> -							      j);
> -			chamelium_assert_frame_eq(data->chamelium, frame, &fb);
> -			chamelium_destroy_frame_dump(frame);
> -		}
> -
> -		igt_remove_fb(data->drm_fd, &fb);
> -		drmModeFreeConnector(connector);
> -	} while (++i < count_modes);
> -}
> -
> -#define MODE_CLOCK_ACCURACY 0.05 /* 5% */
> -
> -static void check_mode(struct chamelium *chamelium, struct chamelium_port *port,
> -		       drmModeModeInfo *mode)
> -{
> -	struct chamelium_video_params video_params = {0};
> -	double mode_clock;
> -	int mode_hsync_offset, mode_vsync_offset;
> -	int mode_hsync_width, mode_vsync_width;
> -	int mode_hsync_polarity, mode_vsync_polarity;
> -
> -	chamelium_port_get_video_params(chamelium, port, &video_params);
> -
> -	mode_clock = (double) mode->clock / 1000;
> -
> -	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) {
> -		/* this is what chamelium understands as offsets for DP */
> -		mode_hsync_offset = mode->htotal - mode->hsync_start;
> -		mode_vsync_offset = mode->vtotal - mode->vsync_start;
> -	} else {
> -		/* and this is what they are for other connectors */
> -		mode_hsync_offset = mode->hsync_start - mode->hdisplay;
> -		mode_vsync_offset = mode->vsync_start - mode->vdisplay;
> -	}
> -
> -	mode_hsync_width = mode->hsync_end - mode->hsync_start;
> -	mode_vsync_width = mode->vsync_end - mode->vsync_start;
> -
> -	mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
> -	mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
> -
> -	igt_debug("Checking video mode:\n");
> -	igt_debug("clock: got %f, expected %f ± %f%%\n",
> -		  video_params.clock, mode_clock, MODE_CLOCK_ACCURACY * 100);
> -	igt_debug("hactive: got %d, expected %d\n",
> -		  video_params.hactive, mode->hdisplay);
> -	igt_debug("vactive: got %d, expected %d\n",
> -		  video_params.vactive, mode->vdisplay);
> -	igt_debug("hsync_offset: got %d, expected %d\n",
> -		  video_params.hsync_offset, mode_hsync_offset);
> -	igt_debug("vsync_offset: got %d, expected %d\n",
> -		  video_params.vsync_offset, mode_vsync_offset);
> -	igt_debug("htotal: got %d, expected %d\n",
> -		  video_params.htotal, mode->htotal);
> -	igt_debug("vtotal: got %d, expected %d\n",
> -		  video_params.vtotal, mode->vtotal);
> -	igt_debug("hsync_width: got %d, expected %d\n",
> -		  video_params.hsync_width, mode_hsync_width);
> -	igt_debug("vsync_width: got %d, expected %d\n",
> -		  video_params.vsync_width, mode_vsync_width);
> -	igt_debug("hsync_polarity: got %d, expected %d\n",
> -		  video_params.hsync_polarity, mode_hsync_polarity);
> -	igt_debug("vsync_polarity: got %d, expected %d\n",
> -		  video_params.vsync_polarity, mode_vsync_polarity);
> -
> -	if (!isnan(video_params.clock)) {
> -		igt_assert(video_params.clock >
> -			   mode_clock * (1 - MODE_CLOCK_ACCURACY));
> -		igt_assert(video_params.clock <
> -			   mode_clock * (1 + MODE_CLOCK_ACCURACY));
> -	}
> -	igt_assert(video_params.hactive == mode->hdisplay);
> -	igt_assert(video_params.vactive == mode->vdisplay);
> -	igt_assert(video_params.hsync_offset == mode_hsync_offset);
> -	igt_assert(video_params.vsync_offset == mode_vsync_offset);
> -	igt_assert(video_params.htotal == mode->htotal);
> -	igt_assert(video_params.vtotal == mode->vtotal);
> -	igt_assert(video_params.hsync_width == mode_hsync_width);
> -	igt_assert(video_params.vsync_width == mode_vsync_width);
> -	igt_assert(video_params.hsync_polarity == mode_hsync_polarity);
> -	igt_assert(video_params.vsync_polarity == mode_vsync_polarity);
> -}
> -
> -static const char test_mode_timings_desc[] =
> -	"For each mode of the IGT base EDID, perform a modeset and check the "
> -	"mode detected by the Chamelium receiver matches the mode we set";
> -static void test_mode_timings(data_t *data, struct chamelium_port *port)
> -{
> -	int i, count_modes;
> -
> -	i = 0;
> -	igt_require(chamelium_supports_get_video_params(data->chamelium));
> -	do {
> -		igt_output_t *output;
> -		igt_plane_t *primary;
> -		drmModeConnector *connector;
> -		drmModeModeInfo *mode;
> -		int fb_id;
> -		struct igt_fb fb;
> -
> -		/*
> -		 * let's reset state each mode so we will get the
> -		 * HPD pulses realibably
> -		 */
> -		igt_modeset_disable_all_outputs(&data->display);
> -		chamelium_reset_state(&data->display, data->chamelium,
> -				      port, data->ports, data->port_count);
> -
> -		/*
> -		 * modes may change due to mode pruining and link issues, so we
> -		 * need to refresh the connector
> -		 */
> -		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> -		connector = chamelium_port_get_connector(data->chamelium, port, false);
> -		primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -		igt_assert(primary);
> -
> -		/* we may skip some modes due to above but that's ok */
> -		count_modes = connector->count_modes;
> -		if (i >= count_modes)
> -			break;
> -
> -		mode = &connector->modes[i];
> -
> -		fb_id = igt_create_color_pattern_fb(data->drm_fd,
> -						    mode->hdisplay, mode->vdisplay,
> -						    DRM_FORMAT_XRGB8888,
> -						    DRM_FORMAT_MOD_LINEAR,
> -						    0, 0, 0, &fb);
> -		igt_assert(fb_id > 0);
> -
> -		enable_output(data, port, output, mode, &fb);
> -
> -		/* Trigger the FSM */
> -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0);
> -
> -		check_mode(data->chamelium, port, mode);
> -
> -		igt_remove_fb(data->drm_fd, &fb);
> -		drmModeFreeConnector(connector);
> -	} while (++i < count_modes);
> -}
> -
> -struct vic_mode {
> -	int hactive, vactive;
> -	int vrefresh; /* Hz */
> -	uint32_t picture_ar;
> -};
> -
> -/* Maps Video Identification Codes to a mode */
> -static const struct vic_mode vic_modes[] = {
> -	[16] = {
> -		.hactive = 1920,
> -		.vactive = 1080,
> -		.vrefresh = 60,
> -		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
> -	},
> -};
> -
> -/* Maps aspect ratios to their mode flag */
> -static const uint32_t mode_ar_flags[] = {
> -	[DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9,
> -};
> -
> -static enum infoframe_avi_picture_aspect_ratio
> -get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
> -{
> -	/* The AVI picture aspect ratio field only supports 4:3 and 16:9 */
> -	switch (aspect_ratio) {
> -	case DRM_MODE_PICTURE_ASPECT_4_3:
> -		return INFOFRAME_AVI_PIC_AR_4_3;
> -	case DRM_MODE_PICTURE_ASPECT_16_9:
> -		return INFOFRAME_AVI_PIC_AR_16_9;
> -	default:
> -		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
> -	}
> -}
> -
> -static bool vic_mode_matches_drm(const struct vic_mode *vic_mode,
> -				 drmModeModeInfo *drm_mode)
> -{
> -	uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar];
> -
> -	return vic_mode->hactive == drm_mode->hdisplay &&
> -	       vic_mode->vactive == drm_mode->vdisplay &&
> -	       vic_mode->vrefresh == drm_mode->vrefresh &&
> -	       ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
> -}
> -
> -static const char test_display_aspect_ratio_desc[] =
> -	"Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and "
> -	"check they include the relevant fields";
> -static void test_display_aspect_ratio(data_t *data, struct chamelium_port *port)
> -{
> -	igt_output_t *output;
> -	igt_plane_t *primary;
> -	drmModeConnector *connector;
> -	drmModeModeInfo *mode;
> -	int fb_id, i;
> -	struct igt_fb fb;
> -	bool found, ok;
> -	struct chamelium_infoframe *infoframe;
> -	struct infoframe_avi infoframe_avi;
> -	uint8_t vic = 16; /* TODO: test more VICs */
> -	const struct vic_mode *vic_mode;
> -	uint32_t aspect_ratio;
> -	enum infoframe_avi_picture_aspect_ratio frame_ar;
> -
> -	igt_require(chamelium_supports_get_last_infoframe(data->chamelium));
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	output = prepare_output(data, port, IGT_CUSTOM_EDID_ASPECT_RATIO);
> -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -	igt_assert(primary);
> -
> -	vic_mode = &vic_modes[vic];
> -	aspect_ratio = vic_mode->picture_ar;
> -
> -	found = false;
> -	igt_assert(connector->count_modes > 0);
> -	for (i = 0; i < connector->count_modes; i++) {
> -		mode = &connector->modes[i];
> -
> -		if (vic_mode_matches_drm(vic_mode, mode)) {
> -			found = true;
> -			break;
> -		}
> -	}
> -	igt_assert_f(found,
> -		     "Failed to find mode with the correct aspect ratio\n");
> -
> -	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> -					    mode->hdisplay, mode->vdisplay,
> -					    DRM_FORMAT_XRGB8888,
> -					    DRM_FORMAT_MOD_LINEAR,
> -					    0, 0, 0, &fb);
> -	igt_assert(fb_id > 0);
> -
> -	enable_output(data, port, output, mode, &fb);
> -
> -	infoframe = chamelium_get_last_infoframe(data->chamelium, port,
> -						 CHAMELIUM_INFOFRAME_AVI);
> -	igt_assert_f(infoframe, "AVI InfoFrame not received\n");
> -
> -	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
> -				 infoframe->payload, infoframe->payload_size);
> -	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
> -
> -	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
> -
> -	igt_debug("Checking AVI InfoFrame\n");
> -	igt_debug("Picture aspect ratio: got %d, expected %d\n",
> -		  infoframe_avi.picture_aspect_ratio, frame_ar);
> -	igt_debug("Video Identification Code (VIC): got %d, expected %d\n",
> -		  infoframe_avi.vic, vic);
> -
> -	igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar);
> -	igt_assert(infoframe_avi.vic == vic);
> -
> -	chamelium_infoframe_destroy(infoframe);
> -	igt_remove_fb(data->drm_fd, &fb);
> -	drmModeFreeConnector(connector);
> -}
> -
> -
> -/* Playback parameters control the audio signal we synthesize and send */
> -#define PLAYBACK_CHANNELS 2
> -#define PLAYBACK_SAMPLES 1024
> -
> -/* Capture paremeters control the audio signal we receive */
> -#define CAPTURE_SAMPLES 2048
> -
> -#define AUDIO_TIMEOUT 2000 /* ms */
> -/* A streak of 3 gives confidence that the signal is good. */
> -#define MIN_STREAK 3
> -
> -#define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */
> -#define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */
> -#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */
> -
> -/* TODO: enable >48KHz rates, these are not reliable */
> -static int test_sampling_rates[] = {
> -	32000,
> -	44100,
> -	48000,
> -	/* 88200, */
> -	/* 96000, */
> -	/* 176400, */
> -	/* 192000, */
> -};
> -
> -static int test_sampling_rates_count = sizeof(test_sampling_rates) / sizeof(int);
> -
> -/* Test frequencies (Hz): a sine signal will be generated for each.
> - *
> - * Depending on the sampling rate chosen, it might not be possible to properly
> - * detect the generated sine (see Nyquist–Shannon sampling theorem).
> - * Frequencies that can't be reliably detected will be automatically pruned in
> - * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be
> - * tested with a 192KHz sampling rate.
> - */
> -static int test_frequencies[] = {
> -	300,
> -	600,
> -	1200,
> -	10000,
> -	80000,
> -};
> -
> -static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> -
> -static const snd_pcm_format_t test_formats[] = {
> -	SND_PCM_FORMAT_S16_LE,
> -	SND_PCM_FORMAT_S24_LE,
> -	SND_PCM_FORMAT_S32_LE,
> -};
> -
> -static const size_t test_formats_count = sizeof(test_formats) / sizeof(test_formats[0]);
> -
> -struct audio_state {
> -	struct alsa *alsa;
> -	struct chamelium *chamelium;
> -	struct chamelium_port *port;
> -	struct chamelium_stream *stream;
> -
> -	/* The capture format is only available after capture has started. */
> -	struct {
> -		snd_pcm_format_t format;
> -		int channels;
> -		int rate;
> -	} playback, capture;
> -
> -	char *name;
> -	struct audio_signal *signal; /* for frequencies test only */
> -	int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS];
> -
> -	size_t recv_pages;
> -	int msec;
> -
> -	int dump_fd;
> -	char *dump_path;
> -
> -	pthread_t thread;
> -	atomic_bool run;
> -	atomic_bool positive; /* for pulse test only */
> -};
> -
> -static void audio_state_init(struct audio_state *state, data_t *data,
> -			     struct alsa *alsa, struct chamelium_port *port,
> -			     snd_pcm_format_t format, int channels, int rate)
> -{
> -	memset(state, 0, sizeof(*state));
> -	state->dump_fd = -1;
> -
> -	state->alsa = alsa;
> -	state->chamelium = data->chamelium;
> -	state->port = port;
> -
> -	state->playback.format = format;
> -	state->playback.channels = channels;
> -	state->playback.rate = rate;
> -
> -	alsa_configure_output(alsa, format, channels, rate);
> -
> -	state->stream = chamelium_stream_init();
> -	igt_assert_f(state->stream,
> -		     "Failed to initialize Chamelium stream client\n");
> -}
> -
> -static void audio_state_fini(struct audio_state *state)
> -{
> -	chamelium_stream_deinit(state->stream);
> -	free(state->name);
> -}
> -
> -static void *run_audio_thread(void *data)
> -{
> -	struct alsa *alsa = data;
> -
> -	alsa_run(alsa, -1);
> -	return NULL;
> -}
> -
> -static void audio_state_start(struct audio_state *state, const char *name)
> -{
> -	int ret;
> -	bool ok;
> -	size_t i, j;
> -	enum chamelium_stream_realtime_mode stream_mode;
> -	char dump_suffix[64];
> -
> -	free(state->name);
> -	state->name = strdup(name);
> -	state->recv_pages = 0;
> -	state->msec = 0;
> -
> -	igt_debug("Starting %s test with playback format %s, "
> -		  "sampling rate %d Hz and %d channels\n",
> -		  name, snd_pcm_format_name(state->playback.format),
> -		  state->playback.rate, state->playback.channels);
> -
> -	chamelium_start_capturing_audio(state->chamelium, state->port, false);
> -
> -	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
> -	ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode);
> -	igt_assert_f(ok, "Failed to start streaming audio capture\n");
> -
> -	/* Start playing audio */
> -	state->run = true;
> -	ret = pthread_create(&state->thread, NULL,
> -			     run_audio_thread, state->alsa);
> -	igt_assert_f(ret == 0, "Failed to start audio playback thread\n");
> -
> -	/* The Chamelium device only supports this PCM format. */
> -	state->capture.format = SND_PCM_FORMAT_S32_LE;
> -
> -	/* Only after we've started playing audio, we can retrieve the capture
> -	 * format used by the Chamelium device. */
> -	chamelium_get_audio_format(state->chamelium, state->port,
> -				   &state->capture.rate,
> -				   &state->capture.channels);
> -	if (state->capture.rate == 0) {
> -		igt_debug("Audio receiver doesn't indicate the capture "
> -			 "sampling rate, assuming it's %d Hz\n",
> -			 state->playback.rate);
> -		state->capture.rate = state->playback.rate;
> -	}
> -
> -	chamelium_get_audio_channel_mapping(state->chamelium, state->port,
> -					    state->channel_mapping);
> -	/* Make sure we can capture all channels we send. */
> -	for (i = 0; i < state->playback.channels; i++) {
> -		ok = false;
> -		for (j = 0; j < state->capture.channels; j++) {
> -			if (state->channel_mapping[j] == i) {
> -				ok = true;
> -				break;
> -			}
> -		}
> -		igt_assert_f(ok, "Cannot capture all channels\n");
> -	}
> -
> -	if (igt_frame_dump_is_enabled()) {
> -		snprintf(dump_suffix, sizeof(dump_suffix),
> -			 "capture-%s-%s-%dch-%dHz",
> -			 name, snd_pcm_format_name(state->playback.format),
> -			 state->playback.channels, state->playback.rate);
> -
> -		state->dump_fd = audio_create_wav_file_s32_le(dump_suffix,
> -							      state->capture.rate,
> -							      state->capture.channels,
> -							      &state->dump_path);
> -		igt_assert_f(state->dump_fd >= 0,
> -			     "Failed to create audio dump file\n");
> -	}
> -}
> -
> -static void audio_state_receive(struct audio_state *state,
> -				int32_t **recv, size_t *recv_len)
> -{
> -	bool ok;
> -	size_t page_count;
> -	size_t recv_size;
> -
> -	ok = chamelium_stream_receive_realtime_audio(state->stream,
> -						     &page_count,
> -						     recv, recv_len);
> -	igt_assert_f(ok, "Failed to receive audio from stream server\n");
> -
> -	state->msec = state->recv_pages * *recv_len
> -		      / (double) state->capture.channels
> -		      / (double) state->capture.rate * 1000;
> -	state->recv_pages++;
> -
> -	if (state->dump_fd >= 0) {
> -		recv_size = *recv_len * sizeof(int32_t);
> -		igt_assert_f(write(state->dump_fd, *recv, recv_size) == recv_size,
> -			     "Failed to write to audio dump file\n");
> -	}
> -}
> -
> -static void audio_state_stop(struct audio_state *state, bool success)
> -{
> -	bool ok;
> -	int ret;
> -	struct chamelium_audio_file *audio_file;
> -	enum igt_log_level log_level;
> -
> -	igt_debug("Stopping audio playback\n");
> -	state->run = false;
> -	ret = pthread_join(state->thread, NULL);
> -	igt_assert_f(ret == 0, "Failed to join audio playback thread\n");
> -
> -	ok = chamelium_stream_stop_realtime_audio(state->stream);
> -	igt_assert_f(ok, "Failed to stop streaming audio capture\n");
> -
> -	audio_file = chamelium_stop_capturing_audio(state->chamelium,
> -						    state->port);
> -	if (audio_file) {
> -		igt_debug("Audio file saved on the Chamelium in %s\n",
> -			  audio_file->path);
> -		chamelium_destroy_audio_file(audio_file);
> -	}
> -
> -	if (state->dump_fd >= 0) {
> -		close(state->dump_fd);
> -		state->dump_fd = -1;
> -
> -		if (success) {
> -			/* Test succeeded, no need to keep the captured data */
> -			unlink(state->dump_path);
> -		} else
> -			igt_debug("Saved captured audio data to %s\n",
> -				  state->dump_path);
> -		free(state->dump_path);
> -		state->dump_path = NULL;
> -	}
> -
> -	if (success)
> -		log_level = IGT_LOG_DEBUG;
> -	else
> -		log_level = IGT_LOG_CRITICAL;
> -
> -	igt_log(IGT_LOG_DOMAIN, log_level, "Audio %s test result for format %s, "
> -		"sampling rate %d Hz and %d channels: %s\n",
> -		state->name, snd_pcm_format_name(state->playback.format),
> -		state->playback.rate, state->playback.channels,
> -		success ? "ALL GREEN" : "FAILED");
> -
> -}
> -
> -static void check_audio_infoframe(struct audio_state *state)
> -{
> -	struct chamelium_infoframe *infoframe;
> -	struct infoframe_audio infoframe_audio;
> -	struct infoframe_audio expected = {0};
> -	bool ok;
> -
> -	if (!chamelium_supports_get_last_infoframe(state->chamelium)) {
> -		igt_debug("Skipping audio InfoFrame check: "
> -			  "Chamelium board doesn't support GetLastInfoFrame\n");
> -		return;
> -	}
> -
> -	expected.coding_type = INFOFRAME_AUDIO_CT_PCM;
> -	expected.channel_count = state->playback.channels;
> -	expected.sampling_freq = state->playback.rate;
> -	expected.sample_size = snd_pcm_format_width(state->playback.format);
> -
> -	infoframe = chamelium_get_last_infoframe(state->chamelium, state->port,
> -						 CHAMELIUM_INFOFRAME_AUDIO);
> -	if (infoframe == NULL && state->playback.channels <= 2) {
> -		/* Audio InfoFrames are optional for mono and stereo audio */
> -		igt_debug("Skipping audio InfoFrame check: "
> -			  "no InfoFrame received\n");
> -		return;
> -	}
> -	igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n");
> -
> -	ok = infoframe_audio_parse(&infoframe_audio, infoframe->version,
> -				   infoframe->payload, infoframe->payload_size);
> -	chamelium_infoframe_destroy(infoframe);
> -	igt_assert_f(ok, "failed to parse audio InfoFrame\n");
> -
> -	igt_debug("Checking audio InfoFrame:\n");
> -	igt_debug("coding_type: got %d, expected %d\n",
> -		  infoframe_audio.coding_type, expected.coding_type);
> -	igt_debug("channel_count: got %d, expected %d\n",
> -		  infoframe_audio.channel_count, expected.channel_count);
> -	igt_debug("sampling_freq: got %d, expected %d\n",
> -		  infoframe_audio.sampling_freq, expected.sampling_freq);
> -	igt_debug("sample_size: got %d, expected %d\n",
> -		  infoframe_audio.sample_size, expected.sample_size);
> -
> -	if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED)
> -		igt_assert(infoframe_audio.coding_type == expected.coding_type);
> -	if (infoframe_audio.channel_count >= 0)
> -		igt_assert(infoframe_audio.channel_count == expected.channel_count);
> -	if (infoframe_audio.sampling_freq >= 0)
> -		igt_assert(infoframe_audio.sampling_freq == expected.sampling_freq);
> -	if (infoframe_audio.sample_size >= 0)
> -		igt_assert(infoframe_audio.sample_size == expected.sample_size);
> -}
> -
> -static int
> -audio_output_frequencies_callback(void *data, void *buffer, int samples)
> -{
> -	struct audio_state *state = data;
> -	double *tmp;
> -	size_t len;
> -
> -	len = samples * state->playback.channels;
> -	tmp = malloc(len * sizeof(double));
> -	audio_signal_fill(state->signal, tmp, samples);
> -	audio_convert_to(buffer, tmp, len, state->playback.format);
> -	free(tmp);
> -
> -	return state->run ? 0 : -1;
> -}
> -
> -static bool test_audio_frequencies(struct audio_state *state)
> -{
> -	int freq, step;
> -	int32_t *recv, *buf;
> -	double *channel;
> -	size_t i, j, streak;
> -	size_t recv_len, buf_len, buf_cap, channel_len;
> -	bool success;
> -	int capture_chan;
> -
> -	state->signal = audio_signal_init(state->playback.channels,
> -					  state->playback.rate);
> -	igt_assert_f(state->signal, "Failed to initialize audio signal\n");
> -
> -	/* We'll choose different frequencies per channel to make sure they are
> -	 * independent from each other. To do so, we'll add a different offset
> -	 * to the base frequencies for each channel. We need to choose a big
> -	 * enough offset so that we're sure to detect mixed up channels. We
> -	 * choose an offset of two 2 bins in the final FFT to enforce a clear
> -	 * difference.
> -	 *
> -	 * Note that we assume capture_rate == playback_rate. We'll assert this
> -	 * later on. We cannot retrieve the capture rate before starting
> -	 * playing audio, so we don't really have the choice.
> -	 */
> -	step = 2 * state->playback.rate / CAPTURE_SAMPLES;
> -	for (i = 0; i < test_frequencies_count; i++) {
> -		for (j = 0; j < state->playback.channels; j++) {
> -			freq = test_frequencies[i] + j * step;
> -			audio_signal_add_frequency(state->signal, freq, j);
> -		}
> -	}
> -	audio_signal_synthesize(state->signal);
> -
> -	alsa_register_output_callback(state->alsa,
> -				      audio_output_frequencies_callback, state,
> -				      PLAYBACK_SAMPLES);
> -
> -	audio_state_start(state, "frequencies");
> -
> -	igt_assert_f(state->capture.rate == state->playback.rate,
> -		     "Capture rate (%dHz) doesn't match playback rate (%dHz)\n",
> -		     state->capture.rate, state->playback.rate);
> -
> -	/* Needs to be a multiple of 128, because that's the number of samples
> -	 * we get per channel each time we receive an audio page from the
> -	 * Chamelium device.
> -	 *
> -	 * Additionally, this value needs to be high enough to guarantee we
> -	 * capture a full period of each sine we generate. If we capture 2048
> -	 * samples at a 192KHz sampling rate, we get a full period for a >94Hz
> -	 * sines. For lower sampling rates, the capture duration will be
> -	 * longer.
> -	 */
> -	channel_len = CAPTURE_SAMPLES;
> -	channel = malloc(sizeof(double) * channel_len);
> -
> -	buf_cap = state->capture.channels * channel_len;
> -	buf = malloc(sizeof(int32_t) * buf_cap);
> -	buf_len = 0;
> -
> -	recv = NULL;
> -	recv_len = 0;
> -
> -	success = false;
> -	streak = 0;
> -	while (!success && state->msec < AUDIO_TIMEOUT) {
> -		audio_state_receive(state, &recv, &recv_len);
> -
> -		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
> -		buf_len += recv_len;
> -
> -		if (buf_len < buf_cap)
> -			continue;
> -		igt_assert(buf_len == buf_cap);
> -
> -		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> -
> -		for (j = 0; j < state->playback.channels; j++) {
> -			capture_chan = state->channel_mapping[j];
> -			igt_assert(capture_chan >= 0);
> -			igt_debug("Processing channel %zu (captured as "
> -				  "channel %d)\n", j, capture_chan);
> -
> -			audio_extract_channel_s32_le(channel, channel_len,
> -						     buf, buf_len,
> -						     state->capture.channels,
> -						     capture_chan);
> -
> -			if (audio_signal_detect(state->signal,
> -						state->capture.rate, j,
> -						channel, channel_len))
> -				streak++;
> -			else
> -				streak = 0;
> -		}
> -
> -		buf_len = 0;
> -
> -		success = streak == MIN_STREAK * state->playback.channels;
> -	}
> -
> -	audio_state_stop(state, success);
> -
> -	free(recv);
> -	free(buf);
> -	free(channel);
> -	audio_signal_fini(state->signal);
> -
> -	check_audio_infoframe(state);
> -
> -	return success;
> -}
> -
> -static int audio_output_flatline_callback(void *data, void *buffer,
> -					     int samples)
> -{
> -	struct audio_state *state = data;
> -	double *tmp;
> -	size_t len, i;
> -
> -	len = samples * state->playback.channels;
> -	tmp = malloc(len * sizeof(double));
> -	for (i = 0; i < len; i++)
> -		tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE;
> -	audio_convert_to(buffer, tmp, len, state->playback.format);
> -	free(tmp);
> -
> -	return state->run ? 0 : -1;
> -}
> -
> -static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos)
> -{
> -	double expected, min, max;
> -	size_t i;
> -	bool ok;
> -
> -	min = max = NAN;
> -	for (i = 0; i < buf_len; i++) {
> -		if (isnan(min) || buf[i] < min)
> -			min = buf[i];
> -		if (isnan(max) || buf[i] > max)
> -			max = buf[i];
> -	}
> -
> -	expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE;
> -	ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY &&
> -	      max <= expected + FLATLINE_AMPLITUDE_ACCURACY);
> -	if (ok)
> -		igt_debug("Flatline wave amplitude detected\n");
> -	else
> -		igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n",
> -			  min, max);
> -	return ok;
> -}
> -
> -static ssize_t detect_falling_edge(double *buf, size_t buf_len)
> -{
> -	size_t i;
> -
> -	for (i = 0; i < buf_len; i++) {
> -		if (buf[i] < 0)
> -			return i;
> -	}
> -
> -	return -1;
> -}
> -
> -/** test_audio_flatline:
> - *
> - * Send a constant value (one positive, then a negative one) and check that:
> - *
> - * - The amplitude of the flatline is correct
> - * - All channels switch from a positive signal to a negative one at the same
> - *   time (ie. all channels are aligned)
> - */
> -static bool test_audio_flatline(struct audio_state *state)
> -{
> -	bool success, amp_success, align_success;
> -	int32_t *recv;
> -	size_t recv_len, i, channel_len;
> -	ssize_t j;
> -	int streak, capture_chan;
> -	double *channel;
> -	int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS];
> -
> -	alsa_register_output_callback(state->alsa,
> -				      audio_output_flatline_callback, state,
> -				      PLAYBACK_SAMPLES);
> -
> -	/* Start by sending a positive signal */
> -	state->positive = true;
> -
> -	audio_state_start(state, "flatline");
> -
> -	for (i = 0; i < state->playback.channels; i++)
> -		falling_edges[i] = -1;
> -
> -	recv = NULL;
> -	recv_len = 0;
> -	amp_success = false;
> -	streak = 0;
> -	while (!amp_success && state->msec < AUDIO_TIMEOUT) {
> -		audio_state_receive(state, &recv, &recv_len);
> -
> -		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> -
> -		for (i = 0; i < state->playback.channels; i++) {
> -			capture_chan = state->channel_mapping[i];
> -			igt_assert(capture_chan >= 0);
> -			igt_debug("Processing channel %zu (captured as "
> -				  "channel %d)\n", i, capture_chan);
> -
> -			channel_len = audio_extract_channel_s32_le(NULL, 0,
> -								   recv, recv_len,
> -								   state->capture.channels,
> -								   capture_chan);
> -			channel = malloc(channel_len * sizeof(double));
> -			audio_extract_channel_s32_le(channel, channel_len,
> -						     recv, recv_len,
> -						     state->capture.channels,
> -						     capture_chan);
> -
> -			/* Check whether the amplitude is fine */
> -			if (detect_flatline_amplitude(channel, channel_len,
> -						      state->positive))
> -				streak++;
> -			else
> -				streak = 0;
> -
> -			/* If we're now sending a negative signal, detect the
> -			 * falling edge */
> -			j = detect_falling_edge(channel, channel_len);
> -			if (!state->positive && j >= 0) {
> -				falling_edges[i] = recv_len * state->recv_pages
> -						   + j;
> -			}
> -
> -			free(channel);
> -		}
> -
> -		amp_success = streak == MIN_STREAK * state->playback.channels;
> -
> -		if (amp_success && state->positive) {
> -			/* Switch to a negative signal after we've detected the
> -			 * positive one. */
> -			state->positive = false;
> -			amp_success = false;
> -			streak = 0;
> -			igt_debug("Switching to negative square wave\n");
> -		}
> -	}
> -
> -	/* Check alignment between all channels by comparing the index of the
> -	 * falling edge. */
> -	align_success = true;
> -	for (i = 0; i < state->playback.channels; i++) {
> -		if (falling_edges[i] < 0) {
> -			igt_critical("Falling edge not detected for channel %zu\n",
> -				     i);
> -			align_success = false;
> -			continue;
> -		}
> -
> -		if (abs(falling_edges[0] - falling_edges[i]) >
> -		    FLATLINE_ALIGN_ACCURACY) {
> -			igt_critical("Channel alignment mismatch: "
> -				     "channel 0 has a falling edge at index %d "
> -				     "while channel %zu has index %d\n",
> -				     falling_edges[0], i, falling_edges[i]);
> -			align_success = false;
> -		}
> -	}
> -
> -	success = amp_success && align_success;
> -	audio_state_stop(state, success);
> -
> -	free(recv);
> -
> -	return success;
> -}
> -
> -static bool check_audio_configuration(struct alsa *alsa, snd_pcm_format_t format,
> -				      int channels, int sampling_rate)
> -{
> -	if (!alsa_test_output_configuration(alsa, format, channels,
> -					    sampling_rate)) {
> -		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> -			  "and %d channels because at least one of the "
> -			  "selected output devices doesn't support this "
> -			  "configuration\n",
> -			  snd_pcm_format_name(format),
> -			  sampling_rate, channels);
> -		return false;
> -	}
> -	/* TODO: the Chamelium device sends a malformed signal for some audio
> -	 * configurations. See crbug.com/950917 */
> -	if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) ||
> -			channels > 2) {
> -		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> -			  "and %d channels because the Chamelium device "
> -			  "doesn't support this configuration\n",
> -			  snd_pcm_format_name(format),
> -			  sampling_rate, channels);
> -		return false;
> -	}
> -	return true;
> -}
> -
> -static const char test_display_audio_desc[] =
> -	"Playback various audio signals with various audio formats/rates, "
> -	"capture them and check they are correct";
> -static void
> -test_display_audio(data_t *data, struct chamelium_port *port,
> -		   const char *audio_device, enum igt_custom_edid_type edid)
> -{
> -	bool run, success;
> -	struct alsa *alsa;
> -	int ret;
> -	igt_output_t *output;
> -	igt_plane_t *primary;
> -	struct igt_fb fb;
> -	drmModeModeInfo *mode;
> -	drmModeConnector *connector;
> -	int fb_id, i, j;
> -	int channels, sampling_rate;
> -	snd_pcm_format_t format;
> -	struct audio_state state;
> -
> -	igt_require(alsa_has_exclusive_access());
> -
> -	/* Old Chamelium devices need an update for DisplayPort audio and
> -	 * chamelium_get_audio_format support. */
> -	igt_require(chamelium_has_audio_support(data->chamelium, port));
> -
> -	alsa = alsa_init();
> -	igt_assert(alsa);
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	output = prepare_output(data, port, edid);
> -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -	igt_assert(primary);
> -
> -	/* Enable the output because the receiver won't try to receive audio if
> -	 * it doesn't receive video. */
> -	igt_assert(connector->count_modes > 0);
> -	mode = &connector->modes[0];
> -
> -	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> -					    mode->hdisplay, mode->vdisplay,
> -					    DRM_FORMAT_XRGB8888,
> -					    DRM_FORMAT_MOD_LINEAR,
> -					    0, 0, 0, &fb);
> -	igt_assert(fb_id > 0);
> -
> -	enable_output(data, port, output, mode, &fb);
> -
> -	run = false;
> -	success = true;
> -	for (i = 0; i < test_sampling_rates_count; i++) {
> -		for (j = 0; j < test_formats_count; j++) {
> -			ret = alsa_open_output(alsa, audio_device);
> -			igt_assert_f(ret >= 0, "Failed to open ALSA output\n");
> -
> -			/* TODO: playback on all 8 available channels (this
> -			 * isn't supported by Chamelium devices yet, see
> -			 * https://crbug.com/950917) */
> -			format = test_formats[j];
> -			channels = PLAYBACK_CHANNELS;
> -			sampling_rate = test_sampling_rates[i];
> -
> -			if (!check_audio_configuration(alsa, format, channels,
> -						       sampling_rate))
> -				continue;
> -
> -			run = true;
> -
> -			audio_state_init(&state, data, alsa, port,
> -					 format, channels, sampling_rate);
> -			success &= test_audio_frequencies(&state);
> -			success &= test_audio_flatline(&state);
> -			audio_state_fini(&state);
> -
> -			alsa_close_output(alsa);
> -		}
> -	}
> -
> -	/* Make sure we tested at least one frequency and format. */
> -	igt_assert(run);
> -	/* Make sure all runs were successful. */
> -	igt_assert(success);
> -
> -	igt_remove_fb(data->drm_fd, &fb);
> -
> -	drmModeFreeConnector(connector);
> -
> -	free(alsa);
> -}
> -
> -static const char test_display_audio_edid_desc[] =
> -	"Plug a connector with an EDID suitable for audio, check ALSA's "
> -	"EDID-Like Data reports the correct audio parameters";
> -static void
> -test_display_audio_edid(data_t *data, struct chamelium_port *port,
> -			enum igt_custom_edid_type edid)
> -{
> -	igt_output_t *output;
> -	igt_plane_t *primary;
> -	struct igt_fb fb;
> -	drmModeModeInfo *mode;
> -	drmModeConnector *connector;
> -	int fb_id;
> -	struct eld_entry eld;
> -	struct eld_sad *sad;
> -
> -	igt_require(eld_is_supported());
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	output = prepare_output(data, port, edid);
> -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -	igt_assert(primary);
> -
> -	/* Enable the output because audio cannot be played on inactive
> -	 * connectors. */
> -	igt_assert(connector->count_modes > 0);
> -	mode = &connector->modes[0];
> -
> -	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> -					    mode->hdisplay, mode->vdisplay,
> -					    DRM_FORMAT_XRGB8888,
> -					    DRM_FORMAT_MOD_LINEAR,
> -					    0, 0, 0, &fb);
> -	igt_assert(fb_id > 0);
> -
> -	enable_output(data, port, output, mode, &fb);
> -
> -	igt_assert(eld_get_igt(&eld));
> -	igt_assert(eld.sads_len == 1);
> -
> -	sad = &eld.sads[0];
> -	igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM);
> -	igt_assert(sad->channels == 2);
> -	igt_assert(sad->rates == (CEA_SAD_SAMPLING_RATE_32KHZ |
> -		   CEA_SAD_SAMPLING_RATE_44KHZ | CEA_SAD_SAMPLING_RATE_48KHZ));
> -	igt_assert(sad->bits == (CEA_SAD_SAMPLE_SIZE_16 |
> -		   CEA_SAD_SAMPLE_SIZE_20 | CEA_SAD_SAMPLE_SIZE_24));
> -
> -	igt_remove_fb(data->drm_fd, &fb);
> -
> -	drmModeFreeConnector(connector);
> -}
> -
> -static void randomize_plane_stride(data_t *data,
> -				   uint32_t width, uint32_t height,
> -				   uint32_t format, uint64_t modifier,
> -				   size_t *stride)
> -{
> -	size_t stride_min;
> -	uint32_t max_tile_w = 4, tile_w, tile_h;
> -	int i;
> -	struct igt_fb dummy;
> -
> -	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
> -
> -	/* Randomize the stride to less than twice the minimum. */
> -	*stride = (rand() % stride_min) + stride_min;
> -
> -	/*
> -	 * Create a dummy FB to determine bpp for each plane, and calculate
> -	 * the maximum tile width from that.
> -	 */
> -	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
> -	for (i = 0; i < dummy.num_planes; i++) {
> -		igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i], &tile_w, &tile_h);
> -
> -		if (tile_w > max_tile_w)
> -			max_tile_w = tile_w;
> -	}
> -	igt_remove_fb(data->drm_fd, &dummy);
> -
> -	/*
> -	 * Pixman requires the stride to be aligned to 32-bits, which is
> -	 * reflected in the initial value of max_tile_w and the hw
> -	 * may require a multiple of tile width, choose biggest of the 2.
> -	 */
> -	*stride = ALIGN(*stride, max_tile_w);
> -}
> -
> -static void update_tiled_modifier(igt_plane_t *plane, uint32_t width,
> -				  uint32_t height, uint32_t format,
> -				  uint64_t *modifier)
> -{
> -	if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) {
> -		/* Randomize the column height to less than twice the minimum. */
> -		size_t column_height = (rand() % height) + height;
> -
> -		igt_debug("Selecting VC4 SAND256 tiling with column height %ld\n",
> -			  column_height);
> -
> -		*modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(column_height);
> -	}
> -}
> -
> -static void randomize_plane_setup(data_t *data, igt_plane_t *plane,
> -				  drmModeModeInfo *mode,
> -				  uint32_t *width, uint32_t *height,
> -				  uint32_t *format, uint64_t *modifier,
> -				  bool allow_yuv)
> -{
> -	int min_dim;
> -	uint32_t idx[plane->format_mod_count];
> -	unsigned int count = 0;
> -	unsigned int i;
> -
> -	/* First pass to count the supported formats. */
> -	for (i = 0; i < plane->format_mod_count; i++)
> -		if (igt_fb_supported_format(plane->formats[i]) &&
> -		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
> -			idx[count++] = i;
> -
> -	igt_assert(count > 0);
> -
> -	i = idx[rand() % count];
> -	*format = plane->formats[i];
> -	*modifier = plane->modifiers[i];
> -
> -	update_tiled_modifier(plane, *width, *height, *format, modifier);
> -
> -	/*
> -	 * Randomize width and height in the mode dimensions range.
> -	 *
> -	 * Restrict to a min of 2 * min_dim, this way src_w/h are always at
> -	 * least min_dim, because src_w = width - (rand % w / 2).
> -	 *
> -	 * Use a minimum dimension of 16 for YUV, because planar YUV
> -	 * subsamples the UV plane.
> -	 */
> -	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
> -
> -	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
> -	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
> -}
> -
> -static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h,
> -			    uint32_t src_x, uint32_t src_y, uint32_t crtc_w,
> -			    uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y,
> -			    struct igt_fb *fb)
> -{
> -	igt_plane_set_fb(plane, fb);
> -
> -	igt_plane_set_position(plane, crtc_x, crtc_y);
> -	igt_plane_set_size(plane, crtc_w, crtc_h);
> -
> -	igt_fb_set_position(fb, plane, src_x, src_y);
> -	igt_fb_set_size(fb, plane, src_w, src_h);
> -}
> -
> -static void randomize_plane_coordinates(data_t *data, igt_plane_t *plane,
> -					drmModeModeInfo *mode,
> -					struct igt_fb *fb,
> -					uint32_t *src_w, uint32_t *src_h,
> -					uint32_t *src_x, uint32_t *src_y,
> -					uint32_t *crtc_w, uint32_t *crtc_h,
> -					int32_t *crtc_x, int32_t *crtc_y,
> -					bool allow_scaling)
> -{
> -	bool is_yuv = igt_format_is_yuv(fb->drm_format);
> -	uint32_t width = fb->width, height = fb->height;
> -	double ratio;
> -	int ret;
> -
> -	/* Randomize source offset in the first half of the original size. */
> -	*src_x = rand() % (width / 2);
> -	*src_y = rand() % (height / 2);
> -
> -	/* The source size only includes the active source area. */
> -	*src_w = width - *src_x;
> -	*src_h = height - *src_y;
> -
> -	if (allow_scaling) {
> -		*crtc_w = (rand() % mode->hdisplay) + 1;
> -		*crtc_h = (rand() % mode->vdisplay) + 1;
> -
> -		/*
> -		 * Don't bother with scaling if dimensions are quite close in
> -		 * order to get non-scaling cases more frequently. Also limit
> -		 * scaling to 3x to avoid agressive filtering that makes
> -		 * comparison less reliable, and don't go above 2x downsampling
> -		 * to avoid possible hw limitations.
> -		 */
> -
> -		ratio = ((double) *crtc_w / *src_w);
> -		if (ratio < 0.5)
> -			*src_w = *crtc_w * 2;
> -		else if (ratio > 0.8 && ratio < 1.2)
> -			*crtc_w = *src_w;
> -		else if (ratio > 3.0)
> -			*crtc_w = *src_w * 3;
> -
> -		ratio = ((double) *crtc_h / *src_h);
> -		if (ratio < 0.5)
> -			*src_h = *crtc_h * 2;
> -		else if (ratio > 0.8 && ratio < 1.2)
> -			*crtc_h = *src_h;
> -		else if (ratio > 3.0)
> -			*crtc_h = *src_h * 3;
> -	} else {
> -		*crtc_w = *src_w;
> -		*crtc_h = *src_h;
> -	}
> -
> -	if (*crtc_w != *src_w || *crtc_h != *src_h) {
> -		/*
> -		 * When scaling is involved, make sure to not go off-bounds or
> -		 * scaled clipping may result in decimal dimensions, that most
> -		 * drivers don't support.
> -		 */
> -		if (*crtc_w < mode->hdisplay)
> -			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
> -		else
> -			*crtc_x = 0;
> -
> -		if (*crtc_h < mode->vdisplay)
> -			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
> -		else
> -			*crtc_y = 0;
> -	} else {
> -		/*
> -		 * Randomize the on-crtc position and allow the plane to go
> -		 * off-display by less than half of its on-crtc dimensions.
> -		 */
> -		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
> -		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
> -	}
> -
> -	configure_plane(plane, *src_w, *src_h, *src_x, *src_y,
> -			*crtc_w, *crtc_h, *crtc_x, *crtc_y, fb);
> -	ret = igt_display_try_commit_atomic(&data->display,
> -					    DRM_MODE_ATOMIC_TEST_ONLY |
> -					    DRM_MODE_ATOMIC_ALLOW_MODESET,
> -					    NULL);
> -	if (!ret)
> -		return;
> -
> -	/* Coordinates are logged in the dumped debug log, so only report w/h on failure here. */
> -	igt_assert_f(ret != -ENOSPC,"Failure in testcase, invalid coordinates on a %ux%u fb\n", width, height);
> -
> -	/* Make YUV coordinates a multiple of 2 and retry the math. */
> -	if (is_yuv) {
> -		*src_x &= ~1;
> -		*src_y &= ~1;
> -		*src_w &= ~1;
> -		*src_h &= ~1;
> -		/* To handle 1:1 scaling, clear crtc_w/h too. */
> -		*crtc_w &= ~1;
> -		*crtc_h &= ~1;
> -
> -		if (*crtc_x < 0 && (*crtc_x & 1))
> -			(*crtc_x)++;
> -		else
> -			*crtc_x &= ~1;
> -
> -		/* If negative, round up to 0 instead of down */
> -		if (*crtc_y < 0 && (*crtc_y & 1))
> -			(*crtc_y)++;
> -		else
> -			*crtc_y &= ~1;
> -
> -		configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
> -				*crtc_h, *crtc_x, *crtc_y, fb);
> -		ret = igt_display_try_commit_atomic(&data->display,
> -						DRM_MODE_ATOMIC_TEST_ONLY |
> -						DRM_MODE_ATOMIC_ALLOW_MODESET,
> -						NULL);
> -		if (!ret)
> -			return;
> -	}
> -
> -	igt_assert(!ret || allow_scaling);
> -	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
> -		  ((double) *crtc_w / *src_w), ((double) *crtc_h / *src_h));
> -
> -	*crtc_w = *src_w;
> -	*crtc_h = *src_h;
> -
> -	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
> -			*crtc_h, *crtc_x, *crtc_y, fb);
> -	igt_display_commit_atomic(&data->display,
> -				  DRM_MODE_ATOMIC_TEST_ONLY |
> -				  DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
> -}
> -
> -static void blit_plane_cairo(data_t *data, cairo_surface_t *result,
> -			     uint32_t src_w, uint32_t src_h,
> -			     uint32_t src_x, uint32_t src_y,
> -			     uint32_t crtc_w, uint32_t crtc_h,
> -			     int32_t crtc_x, int32_t crtc_y,
> -			     struct igt_fb *fb)
> -{
> -	cairo_surface_t *surface;
> -	cairo_surface_t *clipped_surface;
> -	cairo_t *cr;
> -
> -	surface = igt_get_cairo_surface(data->drm_fd, fb);
> -
> -	if (src_x || src_y) {
> -		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
> -							     src_w, src_h);
> -
> -		cr = cairo_create(clipped_surface);
> -
> -		cairo_translate(cr, -1. * src_x, -1. * src_y);
> -
> -		cairo_set_source_surface(cr, surface, 0, 0);
> -
> -		cairo_paint(cr);
> -		cairo_surface_flush(clipped_surface);
> -
> -		cairo_destroy(cr);
> -	} else {
> -		clipped_surface = surface;
> -	}
> -
> -	cr = cairo_create(result);
> -
> -	cairo_translate(cr, crtc_x, crtc_y);
> -
> -	if (src_w != crtc_w || src_h != crtc_h) {
> -		cairo_scale(cr, (double) crtc_w / src_w,
> -			    (double) crtc_h / src_h);
> -	}
> -
> -	cairo_set_source_surface(cr, clipped_surface, 0, 0);
> -	cairo_surface_destroy(clipped_surface);
> -
> -	if (src_w != crtc_w || src_h != crtc_h) {
> -		cairo_pattern_set_filter(cairo_get_source(cr),
> -					 CAIRO_FILTER_BILINEAR);
> -		cairo_pattern_set_extend(cairo_get_source(cr),
> -					 CAIRO_EXTEND_NONE);
> -	}
> -
> -	cairo_paint(cr);
> -	cairo_surface_flush(result);
> -
> -	cairo_destroy(cr);
> -}
> -
> -static void prepare_randomized_plane(data_t *data,
> -				     drmModeModeInfo *mode,
> -				     igt_plane_t *plane,
> -				     struct igt_fb *overlay_fb,
> -				     unsigned int index,
> -				     cairo_surface_t *result_surface,
> -				     bool allow_scaling, bool allow_yuv)
> -{
> -	struct igt_fb pattern_fb;
> -	uint32_t overlay_fb_w, overlay_fb_h;
> -	uint32_t overlay_src_w, overlay_src_h;
> -	uint32_t overlay_src_x, overlay_src_y;
> -	int32_t overlay_crtc_x, overlay_crtc_y;
> -	uint32_t overlay_crtc_w, overlay_crtc_h;
> -	uint32_t format;
> -	uint64_t modifier;
> -	size_t stride;
> -	bool tiled;
> -	int fb_id;
> -
> -	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
> -			      &format, &modifier, allow_yuv);
> -
> -	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
> -	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n",
> -		  index, overlay_fb_w, overlay_fb_h,
> -		  igt_format_str(format), tiled ? "tiled" : "linear");
> -
> -	/* Get a pattern framebuffer for the overlay plane. */
> -	fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h,
> -					 DRM_FORMAT_XRGB8888, 32, &pattern_fb);
> -	igt_assert(fb_id > 0);
> -
> -	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h,
> -			       format, modifier, &stride);
> -
> -	igt_debug("Plane %d: stride %ld\n", index, stride);
> -
> -	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
> -					   modifier, stride);
> -	igt_assert(fb_id > 0);
> -
> -	randomize_plane_coordinates(data, plane, mode, overlay_fb,
> -				    &overlay_src_w, &overlay_src_h,
> -				    &overlay_src_x, &overlay_src_y,
> -				    &overlay_crtc_w, &overlay_crtc_h,
> -				    &overlay_crtc_x, &overlay_crtc_y,
> -				    allow_scaling);
> -
> -	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index,
> -		  overlay_src_w, overlay_src_h);
> -	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
> -		  overlay_src_x, overlay_src_y);
> -	igt_debug("Plane %d: on-crtc size %dx%d\n", index,
> -		  overlay_crtc_w, overlay_crtc_h);
> -	igt_debug("Plane %d: on-crtc position %dx%d\n", index,
> -		  overlay_crtc_x, overlay_crtc_y);
> -
> -	blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h,
> -			 overlay_src_x, overlay_src_y,
> -			 overlay_crtc_w, overlay_crtc_h,
> -			 overlay_crtc_x, overlay_crtc_y, &pattern_fb);
> -
> -	/* Remove the original pattern framebuffer. */
> -	igt_remove_fb(data->drm_fd, &pattern_fb);
> -}
> -
> -static const char test_display_planes_random_desc[] =
> -	"Setup a few overlay planes with random parameters, capture the frame "
> -	"and check it matches the expected output";
> -static void test_display_planes_random(data_t *data,
> -				       struct chamelium_port *port,
> -				       enum chamelium_check check)
> -{
> -	igt_output_t *output;
> -	drmModeModeInfo *mode;
> -	igt_plane_t *primary_plane;
> -	struct igt_fb primary_fb;
> -	struct igt_fb result_fb;
> -	struct igt_fb *overlay_fbs;
> -	igt_crc_t *crc;
> -	igt_crc_t *expected_crc;
> -	struct chamelium_fb_crc_async_data *fb_crc;
> -	unsigned int overlay_planes_max = 0;
> -	unsigned int overlay_planes_count;
> -	cairo_surface_t *result_surface;
> -	int captured_frame_count;
> -	bool allow_scaling;
> -	bool allow_yuv;
> -	unsigned int i;
> -	unsigned int fb_id;
> -
> -	switch (check) {
> -	case CHAMELIUM_CHECK_CRC:
> -		allow_scaling = false;
> -		allow_yuv = false;
> -		break;
> -	case CHAMELIUM_CHECK_CHECKERBOARD:
> -		allow_scaling = true;
> -		allow_yuv = true;
> -		break;
> -	default:
> -		igt_assert(false);
> -	}
> -
> -	srand(time(NULL));
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	/* Find the connector and pipe. */
> -	output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> -
> -	mode = igt_output_get_mode(output);
> -
> -	/* Get a framebuffer for the primary plane. */
> -	primary_plane = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -	igt_assert(primary_plane);
> -
> -	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> -					 DRM_FORMAT_XRGB8888, 64, &primary_fb);
> -	igt_assert(fb_id > 0);
> -
> -	/* Get a framebuffer for the cairo composition result. */
> -	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay,
> -			      mode->vdisplay, DRM_FORMAT_XRGB8888,
> -			      DRM_FORMAT_MOD_LINEAR, &result_fb);
> -	igt_assert(fb_id > 0);
> -
> -	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
> -
> -	/* Paint the primary framebuffer on the result surface. */
> -	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
> -			 &primary_fb);
> -
> -	/* Configure the primary plane. */
> -	igt_plane_set_fb(primary_plane, &primary_fb);
> -
> -	overlay_planes_max =
> -		igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY);
> -
> -	/* Limit the number of planes to a reasonable scene. */
> -	overlay_planes_max = min(overlay_planes_max, 4u);
> -
> -	overlay_planes_count = (rand() % overlay_planes_max) + 1;
> -	igt_debug("Using %d overlay planes\n", overlay_planes_count);
> -
> -	overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count);
> -
> -	for (i = 0; i < overlay_planes_count; i++) {
> -		struct igt_fb *overlay_fb = &overlay_fbs[i];
> -		igt_plane_t *plane =
> -			igt_output_get_plane_type_index(output,
> -							DRM_PLANE_TYPE_OVERLAY,
> -							i);
> -		igt_assert(plane);
> -
> -		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
> -					 result_surface, allow_scaling,
> -					 allow_yuv);
> -	}
> -
> -	cairo_surface_destroy(result_surface);
> -
> -	if (check == CHAMELIUM_CHECK_CRC)
> -		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> -								&result_fb);
> -
> -	igt_display_commit2(&data->display, COMMIT_ATOMIC);
> -
> -	if (check == CHAMELIUM_CHECK_CRC) {
> -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1);
> -		crc = chamelium_read_captured_crcs(data->chamelium,
> -						   &captured_frame_count);
> -
> -		igt_assert(captured_frame_count == 1);
> -
> -		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> -
> -		chamelium_assert_crc_eq_or_dump(data->chamelium,
> -						expected_crc, crc,
> -						&result_fb, 0);
> -
> -		free(expected_crc);
> -		free(crc);
> -	} else if (check == CHAMELIUM_CHECK_CHECKERBOARD) {
> -		struct chamelium_frame_dump *dump;
> -
> -		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> -						  0, 0);
> -		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> -						     dump, &result_fb, check);
> -		chamelium_destroy_frame_dump(dump);
> -	}
> -
> -	for (i = 0; i < overlay_planes_count; i++)
> -		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
> -
> -	free(overlay_fbs);
> -
> -	igt_remove_fb(data->drm_fd, &primary_fb);
> -	igt_remove_fb(data->drm_fd, &result_fb);
> -}
> -
> -static const char test_hpd_without_ddc_desc[] =
> -	"Disable DDC on a VGA connector, check we still get a uevent on hotplug";
> -static void
> -test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> -{
> -	struct udev_monitor *mon = igt_watch_uevents();
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -	igt_flush_uevents(mon);
> -
> -	/* Disable the DDC on the connector and make sure we still get a
> -	 * hotplug
> -	 */
> -	chamelium_port_set_ddc_state(data->chamelium, port, false);
> -	chamelium_plug(data->chamelium, port);
> -
> -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> -	igt_assert_eq(chamelium_reprobe_connector(&data->display,
> -						  data->chamelium, port),
> -						  DRM_MODE_CONNECTED);
> -
> -	igt_cleanup_uevents(mon);
> -}
> -
> -static const char test_hpd_storm_detect_desc[] =
> -	"Trigger a series of hotplugs in a very small timeframe to simulate a"
> -	"bad cable, check the kernel falls back to polling to avoid a hotplug "
> -	"storm";
> -static void
> -test_hpd_storm_detect(data_t *data, struct chamelium_port *port, int width)
> -{
> -	struct udev_monitor *mon;
> -	int count = 0;
> -
> -	igt_require_hpd_storm_ctl(data->drm_fd);
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	igt_hpd_storm_set_threshold(data->drm_fd, 1);
> -	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> -	igt_assert(igt_hpd_storm_detected(data->drm_fd));
> -
> -	mon = igt_watch_uevents();
> -	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> -
> -	/*
> -	 * Polling should have been enabled by the HPD storm at this point,
> -	 * so we should only get at most 1 hotplug event
> -	 */
> -	igt_until_timeout(5)
> -		count += igt_hotplug_detected(mon, 1);
> -	igt_assert_lt(count, 2);
> -
> -	igt_cleanup_uevents(mon);
> -	igt_hpd_storm_reset(data->drm_fd);
> -}
> -
> -static const char test_hpd_storm_disable_desc[] =
> -	"Disable HPD storm detection, trigger a storm and check the kernel "
> -	"doesn't detect one";
> -static void
> -test_hpd_storm_disable(data_t *data, struct chamelium_port *port, int width)
> -{
> -	igt_require_hpd_storm_ctl(data->drm_fd);
> -	igt_modeset_disable_all_outputs(&data->display);
> -	chamelium_reset_state(&data->display, data->chamelium,
> -			      port, data->ports, data->port_count);
> -
> -	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> -	chamelium_fire_hpd_pulses(data->chamelium, port,
> -				  width, 10);
> -	igt_assert(!igt_hpd_storm_detected(data->drm_fd));
> -
> -	igt_hpd_storm_reset(data->drm_fd);
> -}
> -
> -static const char igt_edid_stress_resolution_desc[] =
> -	"Stress test the DUT by testing multiple EDIDs, one right after the other,"
> -	"and ensure their validity by check the real screen resolution vs the"
> -	"advertised mode resultion.";
> -static void edid_stress_resolution(data_t *data, struct chamelium_port *port,
> -				   monitor_edid edids_list[],
> -				   size_t edids_list_len)
> -{
> -	int i;
> -	struct chamelium *chamelium = data->chamelium;
> -	struct udev_monitor *mon = igt_watch_uevents();
> -
> -	for (i = 0; i < edids_list_len; ++i) {
> -		struct chamelium_edid *chamelium_edid;
> -		drmModeModeInfo mode;
> -		struct igt_fb fb = { 0 };
> -		igt_output_t *output;
> -		enum pipe pipe;
> -		bool is_video_stable;
> -		int screen_res_w, screen_res_h;
> -
> -		monitor_edid *edid = &edids_list[i];
> -		igt_info("Testing out the EDID for %s\n",
> -			 monitor_edid_get_name(edid));
> -
> -		/* Getting and Setting the EDID on Chamelium. */
> -		chamelium_edid =
> -			get_chameleon_edid_from_monitor_edid(chamelium, edid);
> -		chamelium_port_set_edid(data->chamelium, port, chamelium_edid);
> -		free_chamelium_edid_from_monitor_edid(chamelium_edid);
> -
> -		igt_flush_uevents(mon);
> -		chamelium_plug(chamelium, port);
> -		wait_for_connector_after_hotplug(data, mon, port,
> -						 DRM_MODE_CONNECTED);
> -		igt_flush_uevents(mon);
> -
> -		/* Setting an output on the screen to turn it on. */
> -		mode = get_mode_for_port(chamelium, port);
> -		create_fb_for_mode(data, &fb, &mode);
> -		output = get_output_for_port(data, port);
> -		pipe = get_pipe_for_output(&data->display, output);
> -		igt_output_set_pipe(output, pipe);
> -		enable_output(data, port, output, &mode, &fb);
> -
> -		/* Capture the screen resolution and verify. */
> -		is_video_stable = chamelium_port_wait_video_input_stable(
> -			chamelium, port, 5);
> -		igt_assert(is_video_stable);
> -
> -		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> -					      &screen_res_h);
> -		igt_assert(screen_res_w == fb.width);
> -		igt_assert(screen_res_h == fb.height);
> -
> -		// Clean up
> -		igt_remove_fb(data->drm_fd, &fb);
> -		igt_modeset_disable_all_outputs(&data->display);
> -		chamelium_unplug(chamelium, port);
> -	}
> -
> -	chamelium_reset_state(&data->display, data->chamelium, port,
> -			      data->ports, data->port_count);
> -}
> -
> -static const char igt_edid_resolution_list_desc[] =
> -	"Get an EDID with many modes of different configurations, set them on the screen and check the"
> -	" screen resolution matches the mode resolution.";
> -
> -static void edid_resolution_list(data_t *data, struct chamelium_port *port)
> -{
> -	struct chamelium *chamelium = data->chamelium;
> -	struct udev_monitor *mon = igt_watch_uevents();
> -	drmModeConnector *connector;
> -	drmModeModeInfoPtr modes;
> -	int count_modes;
> -	int i;
> -	igt_output_t *output;
> -	enum pipe pipe;
> -
> -	chamelium_unplug(chamelium, port);
> -	set_edid(data, port, IGT_CUSTOM_EDID_FULL);
> -
> -	igt_flush_uevents(mon);
> -	chamelium_plug(chamelium, port);
> -	wait_for_connector_after_hotplug(data, mon, port, DRM_MODE_CONNECTED);
> -	igt_flush_uevents(mon);
> -
> -	connector = chamelium_port_get_connector(chamelium, port, true);
> -	modes = connector->modes;
> -	count_modes = connector->count_modes;
> -
> -	output = get_output_for_port(data, port);
> -	pipe = get_pipe_for_output(&data->display, output);
> -	igt_output_set_pipe(output, pipe);
> -
> -	for (i = 0; i < count_modes; ++i)
> -		igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh);
> -
> -	for (i = 0; i < count_modes; ++i) {
> -		struct igt_fb fb = { 0 };
> -		bool is_video_stable;
> -		int screen_res_w, screen_res_h;
> -
> -		igt_info("Testing #%d %s %uHz\n", i, modes[i].name,
> -			 modes[i].vrefresh);
> -
> -		/* Set the screen mode with the one we chose. */
> -		create_fb_for_mode(data, &fb, &modes[i]);
> -		enable_output(data, port, output, &modes[i], &fb);
> -		is_video_stable = chamelium_port_wait_video_input_stable(
> -			chamelium, port, 10);
> -		igt_assert(is_video_stable);
> -
> -		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> -					      &screen_res_h);
> -		igt_assert_eq(screen_res_w, modes[i].hdisplay);
> -		igt_assert_eq(screen_res_h, modes[i].vdisplay);
> -
> -		igt_remove_fb(data->drm_fd, &fb);
> -	}
> -
> -	igt_modeset_disable_all_outputs(&data->display);
> -	drmModeFreeConnector(connector);
> -}
> -
> -#define for_each_port(p, port)            \
> -	for (p = 0, port = data.ports[p]; \
> -	     p < data.port_count;         \
> -	     p++, port = data.ports[p])
> -
> -#define connector_subtest(name__, type__)                    \
> -	igt_subtest(name__)                                  \
> -		for_each_port(p, port)                       \
> -			if (chamelium_port_get_type(port) == \
> -			    DRM_MODE_CONNECTOR_ ## type__)
> -
> -#define connector_dynamic_subtest(name__, type__)            \
> -	igt_subtest_with_dynamic(name__)                     \
> -		for_each_port(p, port)                       \
> -			if (chamelium_port_get_type(port) == \
> -			    DRM_MODE_CONNECTOR_ ## type__)
> -
> -
> -static data_t data;
> -
> -IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board");
> -igt_main
> -{
> -	struct chamelium_port *port;
> -	int p;
> -	size_t i;
> -
> -	igt_fixture {
> -		/* So fbcon doesn't try to reprobe things itself */
> -		kmstest_set_vt_graphics_mode();
> -
> -		data.drm_fd = drm_open_driver_master(DRIVER_ANY);
> -		igt_display_require(&data.display, data.drm_fd);
> -		igt_require(data.display.is_atomic);
> -
> -		/*
> -		 * XXX: disabling modeset, can be removed when
> -		 * igt_display_require will start doing this for us
> -		 */
> -		igt_display_commit2(&data.display, COMMIT_ATOMIC);
> -
> -		/* we need to initalize chamelium after igt_display_require */
> -		data.chamelium = chamelium_init(data.drm_fd, &data.display);
> -		igt_require(data.chamelium);
> -
> -		data.ports = chamelium_get_ports(data.chamelium,
> -						 &data.port_count);
> -
> -		for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) {
> -			data.edids[i] = chamelium_new_edid(data.chamelium,
> -							   igt_kms_get_custom_edid(i));
> -		}
> -	}
> -
> -	igt_describe("DisplayPort tests");
> -	igt_subtest_group {
> -		igt_fixture {
> -			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> -							    data.port_count, 1);
> -		}
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("dp-hpd", DisplayPort)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_DP_HDMI,
> -				     TEST_MODESET_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("dp-hpd-fast", DisplayPort)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("dp-hpd-enable-disable-mode", DisplayPort)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_ON_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("dp-hpd-with-enabled-mode", DisplayPort)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_ON);
> -
> -		igt_describe(igt_custom_edid_type_read_desc);
> -		connector_subtest("dp-edid-read", DisplayPort) {
> -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
> -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
> -		}
> -
> -		igt_describe(igt_edid_stress_resolution_desc);
> -		connector_subtest("dp-edid-stress-resolution-4k", DisplayPort)
> -			edid_stress_resolution(&data, port, DP_EDIDS_4K,
> -					       ARRAY_SIZE(DP_EDIDS_4K));
> -
> -		igt_describe(igt_edid_stress_resolution_desc);
> -		connector_subtest("dp-edid-stress-resolution-non-4k",
> -				  DisplayPort)
> -			edid_stress_resolution(&data, port, DP_EDIDS_NON_4K,
> -					       ARRAY_SIZE(DP_EDIDS_NON_4K));
> -
> -		igt_describe(igt_edid_resolution_list_desc);
> -		connector_subtest("dp-edid-resolution-list", DisplayPort)
> -			edid_resolution_list(&data, port);
> -
> -		igt_describe(test_suspend_resume_hpd_desc);
> -		connector_subtest("dp-hpd-after-suspend", DisplayPort)
> -			test_suspend_resume_hpd(&data, port,
> -						SUSPEND_STATE_MEM,
> -						SUSPEND_TEST_NONE);
> -
> -		igt_describe(test_suspend_resume_hpd_desc);
> -		connector_subtest("dp-hpd-after-hibernate", DisplayPort)
> -			test_suspend_resume_hpd(&data, port,
> -						SUSPEND_STATE_DISK,
> -						SUSPEND_TEST_DEVICES);
> -
> -		igt_describe(test_hpd_storm_detect_desc);
> -		connector_subtest("dp-hpd-storm", DisplayPort)
> -			test_hpd_storm_detect(&data, port,
> -					      HPD_STORM_PULSE_INTERVAL_DP);
> -
> -		igt_describe(test_hpd_storm_disable_desc);
> -		connector_subtest("dp-hpd-storm-disable", DisplayPort)
> -			test_hpd_storm_disable(&data, port,
> -					       HPD_STORM_PULSE_INTERVAL_DP);
> -
> -		igt_describe(test_suspend_resume_edid_change_desc);
> -		connector_subtest("dp-edid-change-during-suspend", DisplayPort)
> -			test_suspend_resume_edid_change(&data, port,
> -							SUSPEND_STATE_MEM,
> -							SUSPEND_TEST_NONE,
> -							IGT_CUSTOM_EDID_BASE,
> -							IGT_CUSTOM_EDID_ALT);
> -
> -		igt_describe(test_suspend_resume_edid_change_desc);
> -		connector_subtest("dp-edid-change-during-hibernate", DisplayPort)
> -			test_suspend_resume_edid_change(&data, port,
> -							SUSPEND_STATE_DISK,
> -							SUSPEND_TEST_DEVICES,
> -							IGT_CUSTOM_EDID_BASE,
> -							IGT_CUSTOM_EDID_ALT);
> -
> -		igt_describe(test_display_all_modes_desc);
> -		connector_subtest("dp-crc-single", DisplayPort)
> -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> -					       CHAMELIUM_CHECK_CRC, 1);
> -
> -		igt_describe(test_display_one_mode_desc);
> -		connector_subtest("dp-crc-fast", DisplayPort)
> -			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> -					      CHAMELIUM_CHECK_CRC, 1);
> -
> -		igt_describe(test_display_all_modes_desc);
> -		connector_subtest("dp-crc-multiple", DisplayPort)
> -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> -					       CHAMELIUM_CHECK_CRC, 3);
> -
> -		igt_describe(test_display_frame_dump_desc);
> -		connector_subtest("dp-frame-dump", DisplayPort)
> -			test_display_frame_dump(&data, port);
> -
> -		igt_describe(test_mode_timings_desc);
> -		connector_subtest("dp-mode-timings", DisplayPort)
> -			test_mode_timings(&data, port);
> -
> -		igt_describe(test_display_audio_desc);
> -		connector_subtest("dp-audio", DisplayPort)
> -			test_display_audio(&data, port, "HDMI",
> -					   IGT_CUSTOM_EDID_DP_AUDIO);
> -
> -		igt_describe(test_display_audio_edid_desc);
> -		connector_subtest("dp-audio-edid", DisplayPort)
> -			test_display_audio_edid(&data, port,
> -						IGT_CUSTOM_EDID_DP_AUDIO);
> -
> -		igt_describe(test_hotplug_for_each_pipe_desc);
> -		connector_subtest("dp-hpd-for-each-pipe", DisplayPort)
> -			test_hotplug_for_each_pipe(&data, port);
> -	}
> -
> -	igt_describe("HDMI tests");
> -	igt_subtest_group {
> -		igt_fixture {
> -			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_HDMIA,
> -							    data.port_count, 1);
> -		}
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("hdmi-hpd", HDMIA)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_DP_HDMI,
> -				     TEST_MODESET_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("hdmi-hpd-fast", HDMIA)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_ON_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_ON);
> -
> -		igt_describe(igt_custom_edid_type_read_desc);
> -		connector_subtest("hdmi-edid-read", HDMIA) {
> -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
> -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
> -		}
> -
> -		igt_describe(igt_edid_stress_resolution_desc);
> -		connector_subtest("hdmi-edid-stress-resolution-4k", HDMIA)
> -			edid_stress_resolution(&data, port, HDMI_EDIDS_4K,
> -					       ARRAY_SIZE(HDMI_EDIDS_4K));
> -
> -		igt_describe(igt_edid_stress_resolution_desc);
> -		connector_subtest("hdmi-edid-stress-resolution-non-4k", HDMIA)
> -			edid_stress_resolution(&data, port, HDMI_EDIDS_NON_4K,
> -					       ARRAY_SIZE(HDMI_EDIDS_NON_4K));
> -
> -		igt_describe(test_suspend_resume_hpd_desc);
> -		connector_subtest("hdmi-hpd-after-suspend", HDMIA)
> -			test_suspend_resume_hpd(&data, port,
> -						SUSPEND_STATE_MEM,
> -						SUSPEND_TEST_NONE);
> -
> -		igt_describe(test_suspend_resume_hpd_desc);
> -		connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
> -			test_suspend_resume_hpd(&data, port,
> -						SUSPEND_STATE_DISK,
> -						SUSPEND_TEST_DEVICES);
> -
> -		igt_describe(test_hpd_storm_detect_desc);
> -		connector_subtest("hdmi-hpd-storm", HDMIA)
> -			test_hpd_storm_detect(&data, port,
> -					      HPD_STORM_PULSE_INTERVAL_HDMI);
> -
> -		igt_describe(test_hpd_storm_disable_desc);
> -		connector_subtest("hdmi-hpd-storm-disable", HDMIA)
> -			test_hpd_storm_disable(&data, port,
> -					       HPD_STORM_PULSE_INTERVAL_HDMI);
> -
> -		igt_describe(test_suspend_resume_edid_change_desc);
> -		connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
> -			test_suspend_resume_edid_change(&data, port,
> -							SUSPEND_STATE_MEM,
> -							SUSPEND_TEST_NONE,
> -							IGT_CUSTOM_EDID_BASE,
> -							IGT_CUSTOM_EDID_ALT);
> -
> -		igt_describe(test_suspend_resume_edid_change_desc);
> -		connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
> -			test_suspend_resume_edid_change(&data, port,
> -							SUSPEND_STATE_DISK,
> -							SUSPEND_TEST_DEVICES,
> -							IGT_CUSTOM_EDID_BASE,
> -							IGT_CUSTOM_EDID_ALT);
> -
> -		igt_describe(test_display_all_modes_desc);
> -		connector_subtest("hdmi-crc-single", HDMIA)
> -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> -					       CHAMELIUM_CHECK_CRC, 1);
> -
> -		igt_describe(test_display_one_mode_desc);
> -		connector_subtest("hdmi-crc-fast", HDMIA)
> -			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> -					      CHAMELIUM_CHECK_CRC, 1);
> -
> -		igt_describe(test_display_all_modes_desc);
> -		connector_subtest("hdmi-crc-multiple", HDMIA)
> -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> -					       CHAMELIUM_CHECK_CRC, 3);
> -
> -		igt_describe(test_display_one_mode_desc);
> -		connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA) {
> -			int k;
> -			igt_output_t *output;
> -			igt_plane_t *primary;
> -
> -			output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE);
> -			primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -			igt_assert(primary);
> -
> -			for (k = 0; k < primary->format_mod_count; k++) {
> -				if (!igt_fb_supported_format(primary->formats[k]))
> -					continue;
> -
> -				if (igt_format_is_yuv(primary->formats[k]))
> -					continue;
> -
> -				if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
> -					continue;
> -
> -				igt_dynamic_f("%s", igt_format_str(primary->formats[k]))
> -					test_display_one_mode(&data, port, primary->formats[k],
> -							      CHAMELIUM_CHECK_CRC, 1);
> -			}
> -		}
> -
> -		igt_describe(test_display_planes_random_desc);
> -		connector_subtest("hdmi-crc-planes-random", HDMIA)
> -			test_display_planes_random(&data, port,
> -						   CHAMELIUM_CHECK_CRC);
> -
> -		igt_describe(test_display_one_mode_desc);
> -		connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA) {
> -			int k;
> -			igt_output_t *output;
> -			igt_plane_t *primary;
> -
> -			output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE);
> -			primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> -			igt_assert(primary);
> -
> -			for (k = 0; k < primary->format_mod_count; k++) {
> -				if (!igt_fb_supported_format(primary->formats[k]))
> -					continue;
> -
> -				if (!igt_format_is_yuv(primary->formats[k]))
> -					continue;
> -
> -				if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
> -					continue;
> -
> -				igt_dynamic_f("%s", igt_format_str(primary->formats[k]))
> -					test_display_one_mode(&data, port, primary->formats[k],
> -							      CHAMELIUM_CHECK_CHECKERBOARD, 1);
> -			}
> -		}
> -
> -		igt_describe(test_display_planes_random_desc);
> -		connector_subtest("hdmi-cmp-planes-random", HDMIA)
> -			test_display_planes_random(&data, port,
> -						   CHAMELIUM_CHECK_CHECKERBOARD);
> -
> -		igt_describe(test_display_frame_dump_desc);
> -		connector_subtest("hdmi-frame-dump", HDMIA)
> -			test_display_frame_dump(&data, port);
> -
> -		igt_describe(test_mode_timings_desc);
> -		connector_subtest("hdmi-mode-timings", HDMIA)
> -			test_mode_timings(&data, port);
> -
> -		igt_describe(test_display_audio_desc);
> -		connector_subtest("hdmi-audio", HDMIA)
> -			test_display_audio(&data, port, "HDMI",
> -					   IGT_CUSTOM_EDID_HDMI_AUDIO);
> -
> -		igt_describe(test_display_audio_edid_desc);
> -		connector_subtest("hdmi-audio-edid", HDMIA)
> -			test_display_audio_edid(&data, port,
> -						IGT_CUSTOM_EDID_HDMI_AUDIO);
> -
> -		igt_describe(test_display_aspect_ratio_desc);
> -		connector_subtest("hdmi-aspect-ratio", HDMIA)
> -			test_display_aspect_ratio(&data, port);
> -
> -		igt_describe(test_hotplug_for_each_pipe_desc);
> -		connector_subtest("hdmi-hpd-for-each-pipe", HDMIA)
> -			test_hotplug_for_each_pipe(&data, port);
> -	}
> -
> -	igt_describe("VGA tests");
> -	igt_subtest_group {
> -		igt_fixture {
> -			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_VGA,
> -							    data.port_count, 1);
> -		}
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("vga-hpd", VGA)
> -			test_hotplug(&data, port, HPD_TOGGLE_COUNT_VGA,
> -				     TEST_MODESET_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("vga-hpd-fast", VGA)
> -			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("vga-hpd-enable-disable-mode", VGA)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_ON_OFF);
> -
> -		igt_describe(test_basic_hotplug_desc);
> -		connector_subtest("vga-hpd-with-enabled-mode", VGA)
> -			test_hotplug(&data, port,
> -				     HPD_TOGGLE_COUNT_FAST,
> -				     TEST_MODESET_ON);
> -
> -		igt_describe(igt_custom_edid_type_read_desc);
> -		connector_subtest("vga-edid-read", VGA) {
> -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
> -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
> -		}
> -
> -		igt_describe(test_suspend_resume_hpd_desc);
> -		connector_subtest("vga-hpd-after-suspend", VGA)
> -			test_suspend_resume_hpd(&data, port,
> -						SUSPEND_STATE_MEM,
> -						SUSPEND_TEST_NONE);
> -
> -		igt_describe(test_suspend_resume_hpd_desc);
> -		connector_subtest("vga-hpd-after-hibernate", VGA)
> -			test_suspend_resume_hpd(&data, port,
> -						SUSPEND_STATE_DISK,
> -						SUSPEND_TEST_DEVICES);
> -
> -		igt_describe(test_hpd_without_ddc_desc);
> -		connector_subtest("vga-hpd-without-ddc", VGA)
> -			test_hpd_without_ddc(&data, port);
> -
> -		igt_describe(test_display_all_modes_desc);
> -		connector_subtest("vga-frame-dump", VGA)
> -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> -					       CHAMELIUM_CHECK_ANALOG, 1);
> -	}
> -
> -	igt_describe("Tests that operate on all connectors");
> -	igt_subtest_group {
> -
> -		igt_fixture {
> -			igt_require(data.port_count);
> -		}
> -
> -		igt_describe(test_suspend_resume_hpd_common_desc);
> -		igt_subtest("common-hpd-after-suspend")
> -			test_suspend_resume_hpd_common(&data,
> -						       SUSPEND_STATE_MEM,
> -						       SUSPEND_TEST_NONE);
> -
> -		igt_describe(test_suspend_resume_hpd_common_desc);
> -		igt_subtest("common-hpd-after-hibernate")
> -			test_suspend_resume_hpd_common(&data,
> -						       SUSPEND_STATE_DISK,
> -						       SUSPEND_TEST_DEVICES);
> -	}
> -
> -	igt_describe(test_hotplug_for_each_pipe_desc);
> -	connector_subtest("vga-hpd-for-each-pipe", VGA)
> -		test_hotplug_for_each_pipe(&data, port);
> -
> -	igt_fixture {
> -		igt_display_fini(&data.display);
> -		close(data.drm_fd);
> -	}
> -}
> diff --git a/tests/chamelium/kms_chamelium_audio.c b/tests/chamelium/kms_chamelium_audio.c
> new file mode 100644
> index 00000000..4d13744c
> --- /dev/null
> +++ b/tests/chamelium/kms_chamelium_audio.c
> @@ -0,0 +1,858 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * A Chamelium test for testing the Audio functionality.
> + *
> + * Copyright 2022 Google LLC.
> + *
> + * Authors: Mark Yacoub <markyacoub@chromium.org>
> + */
> +
> +#include "igt_eld.h"
> +#include "igt_infoframe.h"
> +#include "kms_chamelium_helper.h"
> +
> +/* Playback parameters control the audio signal we synthesize and send */
> +#define PLAYBACK_CHANNELS 2
> +#define PLAYBACK_SAMPLES 1024
> +
> +/* Capture paremeters control the audio signal we receive */
> +#define CAPTURE_SAMPLES 2048
> +
> +#define AUDIO_TIMEOUT 2000 /* ms */
> +/* A streak of 3 gives confidence that the signal is good. */
> +#define MIN_STREAK 3
> +
> +#define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */
> +#define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */
> +#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */
> +
> +struct audio_state {
> +	struct alsa *alsa;
> +	struct chamelium *chamelium;
> +	struct chamelium_port *port;
> +	struct chamelium_stream *stream;
> +
> +	/* The capture format is only available after capture has started. */
> +	struct {
> +		snd_pcm_format_t format;
> +		int channels;
> +		int rate;
> +	} playback, capture;
> +
> +	char *name;
> +	struct audio_signal *signal; /* for frequencies test only */
> +	int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS];
> +
> +	size_t recv_pages;
> +	int msec;
> +
> +	int dump_fd;
> +	char *dump_path;
> +
> +	pthread_t thread;
> +	atomic_bool run;
> +	atomic_bool positive; /* for pulse test only */
> +};
> +
> +/* TODO: enable >48KHz rates, these are not reliable */
> +static int test_sampling_rates[] = {
> +	32000, 44100, 48000,
> +	/* 88200, */
> +	/* 96000, */
> +	/* 176400, */
> +	/* 192000, */
> +};
> +
> +static int test_sampling_rates_count =
> +	sizeof(test_sampling_rates) / sizeof(int);
> +
> +/* Test frequencies (Hz): a sine signal will be generated for each.
> + *
> + * Depending on the sampling rate chosen, it might not be possible to properly
> + * detect the generated sine (see Nyquist–Shannon sampling theorem).
> + * Frequencies that can't be reliably detected will be automatically pruned in
> + * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be
> + * tested with a 192KHz sampling rate.
> + */
> +static int test_frequencies[] = {
> +	300, 600, 1200, 10000, 80000,
> +};
> +
> +static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> +
> +static const snd_pcm_format_t test_formats[] = {
> +	SND_PCM_FORMAT_S16_LE,
> +	SND_PCM_FORMAT_S24_LE,
> +	SND_PCM_FORMAT_S32_LE,
> +};
> +
> +static const size_t test_formats_count =
> +	sizeof(test_formats) / sizeof(test_formats[0]);
> +
> +static void audio_state_init(struct audio_state *state, chamelium_data_t *data,
> +			     struct alsa *alsa, struct chamelium_port *port,
> +			     snd_pcm_format_t format, int channels, int rate)
> +{
> +	memset(state, 0, sizeof(*state));
> +	state->dump_fd = -1;
> +
> +	state->alsa = alsa;
> +	state->chamelium = data->chamelium;
> +	state->port = port;
> +
> +	state->playback.format = format;
> +	state->playback.channels = channels;
> +	state->playback.rate = rate;
> +
> +	alsa_configure_output(alsa, format, channels, rate);
> +
> +	state->stream = chamelium_stream_init();
> +	igt_assert_f(state->stream,
> +		     "Failed to initialize Chamelium stream client\n");
> +}
> +
> +static void audio_state_fini(struct audio_state *state)
> +{
> +	chamelium_stream_deinit(state->stream);
> +	free(state->name);
> +}
> +
> +static void *run_audio_thread(void *data)
> +{
> +	struct alsa *alsa = data;
> +
> +	alsa_run(alsa, -1);
> +	return NULL;
> +}
> +
> +static void audio_state_start(struct audio_state *state, const char *name)
> +{
> +	int ret;
> +	bool ok;
> +	size_t i, j;
> +	enum chamelium_stream_realtime_mode stream_mode;
> +	char dump_suffix[64];
> +
> +	free(state->name);
> +	state->name = strdup(name);
> +	state->recv_pages = 0;
> +	state->msec = 0;
> +
> +	igt_debug("Starting %s test with playback format %s, "
> +		  "sampling rate %d Hz and %d channels\n",
> +		  name, snd_pcm_format_name(state->playback.format),
> +		  state->playback.rate, state->playback.channels);
> +
> +	chamelium_start_capturing_audio(state->chamelium, state->port, false);
> +
> +	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
> +	ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode);
> +	igt_assert_f(ok, "Failed to start streaming audio capture\n");
> +
> +	/* Start playing audio */
> +	state->run = true;
> +	ret = pthread_create(&state->thread, NULL, run_audio_thread,
> +			     state->alsa);
> +	igt_assert_f(ret == 0, "Failed to start audio playback thread\n");
> +
> +	/* The Chamelium device only supports this PCM format. */
> +	state->capture.format = SND_PCM_FORMAT_S32_LE;
> +
> +	/* Only after we've started playing audio, we can retrieve the capture
> +	 * format used by the Chamelium device. */
> +	chamelium_get_audio_format(state->chamelium, state->port,
> +				   &state->capture.rate,
> +				   &state->capture.channels);
> +	if (state->capture.rate == 0) {
> +		igt_debug("Audio receiver doesn't indicate the capture "
> +			  "sampling rate, assuming it's %d Hz\n",
> +			  state->playback.rate);
> +		state->capture.rate = state->playback.rate;
> +	}
> +
> +	chamelium_get_audio_channel_mapping(state->chamelium, state->port,
> +					    state->channel_mapping);
> +	/* Make sure we can capture all channels we send. */
> +	for (i = 0; i < state->playback.channels; i++) {
> +		ok = false;
> +		for (j = 0; j < state->capture.channels; j++) {
> +			if (state->channel_mapping[j] == i) {
> +				ok = true;
> +				break;
> +			}
> +		}
> +		igt_assert_f(ok, "Cannot capture all channels\n");
> +	}
> +
> +	if (igt_frame_dump_is_enabled()) {
> +		snprintf(dump_suffix, sizeof(dump_suffix),
> +			 "capture-%s-%s-%dch-%dHz", name,
> +			 snd_pcm_format_name(state->playback.format),
> +			 state->playback.channels, state->playback.rate);
> +
> +		state->dump_fd = audio_create_wav_file_s32_le(
> +			dump_suffix, state->capture.rate,
> +			state->capture.channels, &state->dump_path);
> +		igt_assert_f(state->dump_fd >= 0,
> +			     "Failed to create audio dump file\n");
> +	}
> +}
> +
> +static void audio_state_receive(struct audio_state *state, int32_t **recv,
> +				size_t *recv_len)
> +{
> +	bool ok;
> +	size_t page_count;
> +	size_t recv_size;
> +
> +	ok = chamelium_stream_receive_realtime_audio(state->stream, &page_count,
> +						     recv, recv_len);
> +	igt_assert_f(ok, "Failed to receive audio from stream server\n");
> +
> +	state->msec = state->recv_pages * *recv_len /
> +		      (double)state->capture.channels /
> +		      (double)state->capture.rate * 1000;
> +	state->recv_pages++;
> +
> +	if (state->dump_fd >= 0) {
> +		recv_size = *recv_len * sizeof(int32_t);
> +		igt_assert_f(write(state->dump_fd, *recv, recv_size) ==
> +				     recv_size,
> +			     "Failed to write to audio dump file\n");
> +	}
> +}
> +
> +static void audio_state_stop(struct audio_state *state, bool success)
> +{
> +	bool ok;
> +	int ret;
> +	struct chamelium_audio_file *audio_file;
> +	enum igt_log_level log_level;
> +
> +	igt_debug("Stopping audio playback\n");
> +	state->run = false;
> +	ret = pthread_join(state->thread, NULL);
> +	igt_assert_f(ret == 0, "Failed to join audio playback thread\n");
> +
> +	ok = chamelium_stream_stop_realtime_audio(state->stream);
> +	igt_assert_f(ok, "Failed to stop streaming audio capture\n");
> +
> +	audio_file =
> +		chamelium_stop_capturing_audio(state->chamelium, state->port);
> +	if (audio_file) {
> +		igt_debug("Audio file saved on the Chamelium in %s\n",
> +			  audio_file->path);
> +		chamelium_destroy_audio_file(audio_file);
> +	}
> +
> +	if (state->dump_fd >= 0) {
> +		close(state->dump_fd);
> +		state->dump_fd = -1;
> +
> +		if (success) {
> +			/* Test succeeded, no need to keep the captured data */
> +			unlink(state->dump_path);
> +		} else
> +			igt_debug("Saved captured audio data to %s\n",
> +				  state->dump_path);
> +		free(state->dump_path);
> +		state->dump_path = NULL;
> +	}
> +
> +	if (success)
> +		log_level = IGT_LOG_DEBUG;
> +	else
> +		log_level = IGT_LOG_CRITICAL;
> +
> +	igt_log(IGT_LOG_DOMAIN, log_level,
> +		"Audio %s test result for format %s, "
> +		"sampling rate %d Hz and %d channels: %s\n",
> +		state->name, snd_pcm_format_name(state->playback.format),
> +		state->playback.rate, state->playback.channels,
> +		success ? "ALL GREEN" : "FAILED");
> +}
> +
> +static void check_audio_infoframe(struct audio_state *state)
> +{
> +	struct chamelium_infoframe *infoframe;
> +	struct infoframe_audio infoframe_audio;
> +	struct infoframe_audio expected = { 0 };
> +	bool ok;
> +
> +	if (!chamelium_supports_get_last_infoframe(state->chamelium)) {
> +		igt_debug("Skipping audio InfoFrame check: "
> +			  "Chamelium board doesn't support GetLastInfoFrame\n");
> +		return;
> +	}
> +
> +	expected.coding_type = INFOFRAME_AUDIO_CT_PCM;
> +	expected.channel_count = state->playback.channels;
> +	expected.sampling_freq = state->playback.rate;
> +	expected.sample_size = snd_pcm_format_width(state->playback.format);
> +
> +	infoframe = chamelium_get_last_infoframe(state->chamelium, state->port,
> +						 CHAMELIUM_INFOFRAME_AUDIO);
> +	if (infoframe == NULL && state->playback.channels <= 2) {
> +		/* Audio InfoFrames are optional for mono and stereo audio */
> +		igt_debug("Skipping audio InfoFrame check: "
> +			  "no InfoFrame received\n");
> +		return;
> +	}
> +	igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n");
> +
> +	ok = infoframe_audio_parse(&infoframe_audio, infoframe->version,
> +				   infoframe->payload, infoframe->payload_size);
> +	chamelium_infoframe_destroy(infoframe);
> +	igt_assert_f(ok, "failed to parse audio InfoFrame\n");
> +
> +	igt_debug("Checking audio InfoFrame:\n");
> +	igt_debug("coding_type: got %d, expected %d\n",
> +		  infoframe_audio.coding_type, expected.coding_type);
> +	igt_debug("channel_count: got %d, expected %d\n",
> +		  infoframe_audio.channel_count, expected.channel_count);
> +	igt_debug("sampling_freq: got %d, expected %d\n",
> +		  infoframe_audio.sampling_freq, expected.sampling_freq);
> +	igt_debug("sample_size: got %d, expected %d\n",
> +		  infoframe_audio.sample_size, expected.sample_size);
> +
> +	if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED)
> +		igt_assert(infoframe_audio.coding_type == expected.coding_type);
> +	if (infoframe_audio.channel_count >= 0)
> +		igt_assert(infoframe_audio.channel_count ==
> +			   expected.channel_count);
> +	if (infoframe_audio.sampling_freq >= 0)
> +		igt_assert(infoframe_audio.sampling_freq ==
> +			   expected.sampling_freq);
> +	if (infoframe_audio.sample_size >= 0)
> +		igt_assert(infoframe_audio.sample_size == expected.sample_size);
> +}
> +
> +static int audio_output_frequencies_callback(void *data, void *buffer,
> +					     int samples)
> +{
> +	struct audio_state *state = data;
> +	double *tmp;
> +	size_t len;
> +
> +	len = samples * state->playback.channels;
> +	tmp = malloc(len * sizeof(double));
> +	audio_signal_fill(state->signal, tmp, samples);
> +	audio_convert_to(buffer, tmp, len, state->playback.format);
> +	free(tmp);
> +
> +	return state->run ? 0 : -1;
> +}
> +
> +static bool test_audio_frequencies(struct audio_state *state)
> +{
> +	int freq, step;
> +	int32_t *recv, *buf;
> +	double *channel;
> +	size_t i, j, streak;
> +	size_t recv_len, buf_len, buf_cap, channel_len;
> +	bool success;
> +	int capture_chan;
> +
> +	state->signal = audio_signal_init(state->playback.channels,
> +					  state->playback.rate);
> +	igt_assert_f(state->signal, "Failed to initialize audio signal\n");
> +
> +	/* We'll choose different frequencies per channel to make sure they are
> +	 * independent from each other. To do so, we'll add a different offset
> +	 * to the base frequencies for each channel. We need to choose a big
> +	 * enough offset so that we're sure to detect mixed up channels. We
> +	 * choose an offset of two 2 bins in the final FFT to enforce a clear
> +	 * difference.
> +	 *
> +	 * Note that we assume capture_rate == playback_rate. We'll assert this
> +	 * later on. We cannot retrieve the capture rate before starting
> +	 * playing audio, so we don't really have the choice.
> +	 */
> +	step = 2 * state->playback.rate / CAPTURE_SAMPLES;
> +	for (i = 0; i < test_frequencies_count; i++) {
> +		for (j = 0; j < state->playback.channels; j++) {
> +			freq = test_frequencies[i] + j * step;
> +			audio_signal_add_frequency(state->signal, freq, j);
> +		}
> +	}
> +	audio_signal_synthesize(state->signal);
> +
> +	alsa_register_output_callback(state->alsa,
> +				      audio_output_frequencies_callback, state,
> +				      PLAYBACK_SAMPLES);
> +
> +	audio_state_start(state, "frequencies");
> +
> +	igt_assert_f(state->capture.rate == state->playback.rate,
> +		     "Capture rate (%dHz) doesn't match playback rate (%dHz)\n",
> +		     state->capture.rate, state->playback.rate);
> +
> +	/* Needs to be a multiple of 128, because that's the number of samples
> +	 * we get per channel each time we receive an audio page from the
> +	 * Chamelium device.
> +	 *
> +	 * Additionally, this value needs to be high enough to guarantee we
> +	 * capture a full period of each sine we generate. If we capture 2048
> +	 * samples at a 192KHz sampling rate, we get a full period for a >94Hz
> +	 * sines. For lower sampling rates, the capture duration will be
> +	 * longer.
> +	 */
> +	channel_len = CAPTURE_SAMPLES;
> +	channel = malloc(sizeof(double) * channel_len);
> +
> +	buf_cap = state->capture.channels * channel_len;
> +	buf = malloc(sizeof(int32_t) * buf_cap);
> +	buf_len = 0;
> +
> +	recv = NULL;
> +	recv_len = 0;
> +
> +	success = false;
> +	streak = 0;
> +	while (!success && state->msec < AUDIO_TIMEOUT) {
> +		audio_state_receive(state, &recv, &recv_len);
> +
> +		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
> +		buf_len += recv_len;
> +
> +		if (buf_len < buf_cap)
> +			continue;
> +		igt_assert(buf_len == buf_cap);
> +
> +		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> +
> +		for (j = 0; j < state->playback.channels; j++) {
> +			capture_chan = state->channel_mapping[j];
> +			igt_assert(capture_chan >= 0);
> +			igt_debug("Processing channel %zu (captured as "
> +				  "channel %d)\n",
> +				  j, capture_chan);
> +
> +			audio_extract_channel_s32_le(channel, channel_len, buf,
> +						     buf_len,
> +						     state->capture.channels,
> +						     capture_chan);
> +
> +			if (audio_signal_detect(state->signal,
> +						state->capture.rate, j, channel,
> +						channel_len))
> +				streak++;
> +			else
> +				streak = 0;
> +		}
> +
> +		buf_len = 0;
> +
> +		success = streak == MIN_STREAK * state->playback.channels;
> +	}
> +
> +	audio_state_stop(state, success);
> +
> +	free(recv);
> +	free(buf);
> +	free(channel);
> +	audio_signal_fini(state->signal);
> +
> +	check_audio_infoframe(state);
> +
> +	return success;
> +}
> +
> +static int audio_output_flatline_callback(void *data, void *buffer, int samples)
> +{
> +	struct audio_state *state = data;
> +	double *tmp;
> +	size_t len, i;
> +
> +	len = samples * state->playback.channels;
> +	tmp = malloc(len * sizeof(double));
> +	for (i = 0; i < len; i++)
> +		tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE;
> +	audio_convert_to(buffer, tmp, len, state->playback.format);
> +	free(tmp);
> +
> +	return state->run ? 0 : -1;
> +}
> +
> +static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos)
> +{
> +	double expected, min, max;
> +	size_t i;
> +	bool ok;
> +
> +	min = max = NAN;
> +	for (i = 0; i < buf_len; i++) {
> +		if (isnan(min) || buf[i] < min)
> +			min = buf[i];
> +		if (isnan(max) || buf[i] > max)
> +			max = buf[i];
> +	}
> +
> +	expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE;
> +	ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY &&
> +	      max <= expected + FLATLINE_AMPLITUDE_ACCURACY);
> +	if (ok)
> +		igt_debug("Flatline wave amplitude detected\n");
> +	else
> +		igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n",
> +			  min, max);
> +	return ok;
> +}
> +
> +static ssize_t detect_falling_edge(double *buf, size_t buf_len)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < buf_len; i++) {
> +		if (buf[i] < 0)
> +			return i;
> +	}
> +
> +	return -1;
> +}
> +
> +/** test_audio_flatline:
> + *
> + * Send a constant value (one positive, then a negative one) and check that:
> + *
> + * - The amplitude of the flatline is correct
> + * - All channels switch from a positive signal to a negative one at the same
> + *   time (ie. all channels are aligned)
> + */
> +static bool test_audio_flatline(struct audio_state *state)
> +{
> +	bool success, amp_success, align_success;
> +	int32_t *recv;
> +	size_t recv_len, i, channel_len;
> +	ssize_t j;
> +	int streak, capture_chan;
> +	double *channel;
> +	int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS];
> +
> +	alsa_register_output_callback(state->alsa,
> +				      audio_output_flatline_callback, state,
> +				      PLAYBACK_SAMPLES);
> +
> +	/* Start by sending a positive signal */
> +	state->positive = true;
> +
> +	audio_state_start(state, "flatline");
> +
> +	for (i = 0; i < state->playback.channels; i++)
> +		falling_edges[i] = -1;
> +
> +	recv = NULL;
> +	recv_len = 0;
> +	amp_success = false;
> +	streak = 0;
> +	while (!amp_success && state->msec < AUDIO_TIMEOUT) {
> +		audio_state_receive(state, &recv, &recv_len);
> +
> +		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> +
> +		for (i = 0; i < state->playback.channels; i++) {
> +			capture_chan = state->channel_mapping[i];
> +			igt_assert(capture_chan >= 0);
> +			igt_debug("Processing channel %zu (captured as "
> +				  "channel %d)\n",
> +				  i, capture_chan);
> +
> +			channel_len = audio_extract_channel_s32_le(
> +				NULL, 0, recv, recv_len,
> +				state->capture.channels, capture_chan);
> +			channel = malloc(channel_len * sizeof(double));
> +			audio_extract_channel_s32_le(channel, channel_len, recv,
> +						     recv_len,
> +						     state->capture.channels,
> +						     capture_chan);
> +
> +			/* Check whether the amplitude is fine */
> +			if (detect_flatline_amplitude(channel, channel_len,
> +						      state->positive))
> +				streak++;
> +			else
> +				streak = 0;
> +
> +			/* If we're now sending a negative signal, detect the
> +			 * falling edge */
> +			j = detect_falling_edge(channel, channel_len);
> +			if (!state->positive && j >= 0) {
> +				falling_edges[i] =
> +					recv_len * state->recv_pages + j;
> +			}
> +
> +			free(channel);
> +		}
> +
> +		amp_success = streak == MIN_STREAK * state->playback.channels;
> +
> +		if (amp_success && state->positive) {
> +			/* Switch to a negative signal after we've detected the
> +			 * positive one. */
> +			state->positive = false;
> +			amp_success = false;
> +			streak = 0;
> +			igt_debug("Switching to negative square wave\n");
> +		}
> +	}
> +
> +	/* Check alignment between all channels by comparing the index of the
> +	 * falling edge. */
> +	align_success = true;
> +	for (i = 0; i < state->playback.channels; i++) {
> +		if (falling_edges[i] < 0) {
> +			igt_critical(
> +				"Falling edge not detected for channel %zu\n",
> +				i);
> +			align_success = false;
> +			continue;
> +		}
> +
> +		if (abs(falling_edges[0] - falling_edges[i]) >
> +		    FLATLINE_ALIGN_ACCURACY) {
> +			igt_critical("Channel alignment mismatch: "
> +				     "channel 0 has a falling edge at index %d "
> +				     "while channel %zu has index %d\n",
> +				     falling_edges[0], i, falling_edges[i]);
> +			align_success = false;
> +		}
> +	}
> +
> +	success = amp_success && align_success;
> +	audio_state_stop(state, success);
> +
> +	free(recv);
> +
> +	return success;
> +}
> +
> +static bool check_audio_configuration(struct alsa *alsa,
> +				      snd_pcm_format_t format, int channels,
> +				      int sampling_rate)
> +{
> +	if (!alsa_test_output_configuration(alsa, format, channels,
> +					    sampling_rate)) {
> +		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> +			  "and %d channels because at least one of the "
> +			  "selected output devices doesn't support this "
> +			  "configuration\n",
> +			  snd_pcm_format_name(format), sampling_rate, channels);
> +		return false;
> +	}
> +	/* TODO: the Chamelium device sends a malformed signal for some audio
> +	 * configurations. See crbug.com/950917 */
> +	if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) ||
> +	    channels > 2) {
> +		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> +			  "and %d channels because the Chamelium device "
> +			  "doesn't support this configuration\n",
> +			  snd_pcm_format_name(format), sampling_rate, channels);
> +		return false;
> +	}
> +	return true;
> +}
> +
> +static const char test_display_audio_desc[] =
> +	"Playback various audio signals with various audio formats/rates, "
> +	"capture them and check they are correct";
> +static void test_display_audio(chamelium_data_t *data,
> +			       struct chamelium_port *port,
> +			       const char *audio_device,
> +			       enum igt_custom_edid_type edid)
> +{
> +	bool run, success;
> +	struct alsa *alsa;
> +	int ret;
> +	igt_output_t *output;
> +	igt_plane_t *primary;
> +	struct igt_fb fb;
> +	drmModeModeInfo *mode;
> +	drmModeConnector *connector;
> +	int fb_id, i, j;
> +	int channels, sampling_rate;
> +	snd_pcm_format_t format;
> +	struct audio_state state;
> +
> +	igt_require(alsa_has_exclusive_access());
> +
> +	/* Old Chamelium devices need an update for DisplayPort audio and
> +	 * chamelium_get_audio_format support. */
> +	igt_require(chamelium_has_audio_support(data->chamelium, port));
> +
> +	alsa = alsa_init();
> +	igt_assert(alsa);
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	output = chamelium_prepare_output(data, port, edid);
> +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	igt_assert(primary);
> +
> +	/* Enable the output because the receiver won't try to receive audio if
> +	 * it doesn't receive video. */
> +	igt_assert(connector->count_modes > 0);
> +	mode = &connector->modes[0];
> +
> +	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
> +					    mode->vdisplay, DRM_FORMAT_XRGB8888,
> +					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> +					    &fb);
> +	igt_assert(fb_id > 0);
> +
> +	chamelium_enable_output(data, port, output, mode, &fb);
> +
> +	run = false;
> +	success = true;
> +	for (i = 0; i < test_sampling_rates_count; i++) {
> +		for (j = 0; j < test_formats_count; j++) {
> +			ret = alsa_open_output(alsa, audio_device);
> +			igt_assert_f(ret >= 0, "Failed to open ALSA output\n");
> +
> +			/* TODO: playback on all 8 available channels (this
> +			 * isn't supported by Chamelium devices yet, see
> +			 * https://crbug.com/950917) */
> +			format = test_formats[j];
> +			channels = PLAYBACK_CHANNELS;
> +			sampling_rate = test_sampling_rates[i];
> +
> +			if (!check_audio_configuration(alsa, format, channels,
> +						       sampling_rate))
> +				continue;
> +
> +			run = true;
> +
> +			audio_state_init(&state, data, alsa, port, format,
> +					 channels, sampling_rate);
> +			success &= test_audio_frequencies(&state);
> +			success &= test_audio_flatline(&state);
> +			audio_state_fini(&state);
> +
> +			alsa_close_output(alsa);
> +		}
> +	}
> +
> +	/* Make sure we tested at least one frequency and format. */
> +	igt_assert(run);
> +	/* Make sure all runs were successful. */
> +	igt_assert(success);
> +
> +	igt_remove_fb(data->drm_fd, &fb);
> +
> +	drmModeFreeConnector(connector);
> +
> +	free(alsa);
> +}
> +
> +static const char test_display_audio_edid_desc[] =
> +	"Plug a connector with an EDID suitable for audio, check ALSA's "
> +	"EDID-Like Data reports the correct audio parameters";
> +static void test_display_audio_edid(chamelium_data_t *data,
> +				    struct chamelium_port *port,
> +				    enum igt_custom_edid_type edid)
> +{
> +	igt_output_t *output;
> +	igt_plane_t *primary;
> +	struct igt_fb fb;
> +	drmModeModeInfo *mode;
> +	drmModeConnector *connector;
> +	int fb_id;
> +	struct eld_entry eld;
> +	struct eld_sad *sad;
> +
> +	igt_require(eld_is_supported());
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	output = chamelium_prepare_output(data, port, edid);
> +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	igt_assert(primary);
> +
> +	/* Enable the output because audio cannot be played on inactive
> +	 * connectors. */
> +	igt_assert(connector->count_modes > 0);
> +	mode = &connector->modes[0];
> +
> +	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
> +					    mode->vdisplay, DRM_FORMAT_XRGB8888,
> +					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> +					    &fb);
> +	igt_assert(fb_id > 0);
> +
> +	chamelium_enable_output(data, port, output, mode, &fb);
> +
> +	igt_assert(eld_get_igt(&eld));
> +	igt_assert(eld.sads_len == 1);
> +
> +	sad = &eld.sads[0];
> +	igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM);
> +	igt_assert(sad->channels == 2);
> +	igt_assert(sad->rates ==
> +		   (CEA_SAD_SAMPLING_RATE_32KHZ | CEA_SAD_SAMPLING_RATE_44KHZ |
> +		    CEA_SAD_SAMPLING_RATE_48KHZ));
> +	igt_assert(sad->bits ==
> +		   (CEA_SAD_SAMPLE_SIZE_16 | CEA_SAD_SAMPLE_SIZE_20 |
> +		    CEA_SAD_SAMPLE_SIZE_24));
> +
> +	igt_remove_fb(data->drm_fd, &fb);
> +
> +	drmModeFreeConnector(connector);
> +}
> +
> +IGT_TEST_DESCRIPTION("Testing Audio with a Chamelium board");
> +igt_main
> +{
> +	chamelium_data_t data;
> +	struct chamelium_port *port;
> +	int p;
> +
> +	igt_fixture {
> +		chamelium_init_test(&data);
> +	}
> +
> +	igt_describe("DisplayPort tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_display_audio_desc);
> +		connector_subtest("dp-audio", DisplayPort) test_display_audio(
> +			&data, port, "HDMI", IGT_CUSTOM_EDID_DP_AUDIO);
> +
> +		igt_describe(test_display_audio_edid_desc);
> +		connector_subtest("dp-audio-edid", DisplayPort)
> +			test_display_audio_edid(&data, port,
> +						IGT_CUSTOM_EDID_DP_AUDIO);
> +	}
> +
> +	igt_describe("HDMI tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_display_audio_desc);
> +		connector_subtest("hdmi-audio", HDMIA) test_display_audio(
> +			&data, port, "HDMI", IGT_CUSTOM_EDID_HDMI_AUDIO);
> +
> +		igt_describe(test_display_audio_edid_desc);
> +		connector_subtest("hdmi-audio-edid", HDMIA)
> +			test_display_audio_edid(&data, port,
> +						IGT_CUSTOM_EDID_HDMI_AUDIO);
> +	}
> +
> +	igt_fixture {
> +		igt_display_fini(&data.display);
> +		close(data.drm_fd);
> +	}
> +}
> diff --git a/tests/chamelium/kms_color_chamelium.c b/tests/chamelium/kms_chamelium_color.c
> similarity index 100%
> rename from tests/chamelium/kms_color_chamelium.c
> rename to tests/chamelium/kms_chamelium_color.c
> diff --git a/tests/chamelium/kms_chamelium_edid.c b/tests/chamelium/kms_chamelium_edid.c
> new file mode 100644
> index 00000000..c9e15f41
> --- /dev/null
> +++ b/tests/chamelium/kms_chamelium_edid.c
> @@ -0,0 +1,543 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * A Chamelium test for testing the EDID functionality.
> + *
> + * Copyright 2022 Google LLC.
> + *
> + * Authors: Mark Yacoub <markyacoub@chromium.org>
> + */
> +
> +#include <fcntl.h>
> +#include <stdint.h>
> +#include <string.h>
> +#include <stdatomic.h>
> +#include <xf86drmMode.h>
> +
> +#include "config.h"
> +#include "igt.h"
> +#include "igt_chamelium.h"
> +#include "igt_edid.h"
> +#include "igt_eld.h"
> +#include "igt_vc4.h"
> +#include "igt_infoframe.h"
> +#include "kms_chamelium_helper.h"
> +#include "monitor_edids/dp_edids.h"
> +#include "monitor_edids/hdmi_edids.h"
> +#include "monitor_edids/monitor_edids_helper.h"
> +
> +#define MODE_CLOCK_ACCURACY 0.05 /* 5% */
> +
> +static void get_connectors_link_status_failed(chamelium_data_t *data,
> +					      bool *link_status_failed)
> +{
> +	drmModeConnector *connector;
> +	uint64_t link_status;
> +	drmModePropertyPtr prop;
> +	int p;
> +
> +	for (p = 0; p < data->port_count; p++) {
> +		connector = chamelium_port_get_connector(data->chamelium,
> +							 data->ports[p], false);
> +
> +		igt_assert(kmstest_get_property(
> +			data->drm_fd, connector->connector_id,
> +			DRM_MODE_OBJECT_CONNECTOR, "link-status", NULL,
> +			&link_status, &prop));
> +
> +		link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD;
> +
> +		drmModeFreeProperty(prop);
> +		drmModeFreeConnector(connector);
> +	}
> +}
> +
> +static void check_mode(struct chamelium *chamelium, struct chamelium_port *port,
> +		       drmModeModeInfo *mode)
> +{
> +	struct chamelium_video_params video_params = { 0 };
> +	double mode_clock;
> +	int mode_hsync_offset, mode_vsync_offset;
> +	int mode_hsync_width, mode_vsync_width;
> +	int mode_hsync_polarity, mode_vsync_polarity;
> +
> +	chamelium_port_get_video_params(chamelium, port, &video_params);
> +
> +	mode_clock = (double)mode->clock / 1000;
> +
> +	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) {
> +		/* this is what chamelium understands as offsets for DP */
> +		mode_hsync_offset = mode->htotal - mode->hsync_start;
> +		mode_vsync_offset = mode->vtotal - mode->vsync_start;
> +	} else {
> +		/* and this is what they are for other connectors */
> +		mode_hsync_offset = mode->hsync_start - mode->hdisplay;
> +		mode_vsync_offset = mode->vsync_start - mode->vdisplay;
> +	}
> +
> +	mode_hsync_width = mode->hsync_end - mode->hsync_start;
> +	mode_vsync_width = mode->vsync_end - mode->vsync_start;
> +
> +	mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
> +	mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
> +
> +	igt_debug("Checking video mode:\n");
> +	igt_debug("clock: got %f, expected %f ± %f%%\n", video_params.clock,
> +		  mode_clock, MODE_CLOCK_ACCURACY * 100);
> +	igt_debug("hactive: got %d, expected %d\n", video_params.hactive,
> +		  mode->hdisplay);
> +	igt_debug("vactive: got %d, expected %d\n", video_params.vactive,
> +		  mode->vdisplay);
> +	igt_debug("hsync_offset: got %d, expected %d\n",
> +		  video_params.hsync_offset, mode_hsync_offset);
> +	igt_debug("vsync_offset: got %d, expected %d\n",
> +		  video_params.vsync_offset, mode_vsync_offset);
> +	igt_debug("htotal: got %d, expected %d\n", video_params.htotal,
> +		  mode->htotal);
> +	igt_debug("vtotal: got %d, expected %d\n", video_params.vtotal,
> +		  mode->vtotal);
> +	igt_debug("hsync_width: got %d, expected %d\n",
> +		  video_params.hsync_width, mode_hsync_width);
> +	igt_debug("vsync_width: got %d, expected %d\n",
> +		  video_params.vsync_width, mode_vsync_width);
> +	igt_debug("hsync_polarity: got %d, expected %d\n",
> +		  video_params.hsync_polarity, mode_hsync_polarity);
> +	igt_debug("vsync_polarity: got %d, expected %d\n",
> +		  video_params.vsync_polarity, mode_vsync_polarity);
> +
> +	if (!isnan(video_params.clock)) {
> +		igt_assert(video_params.clock >
> +			   mode_clock * (1 - MODE_CLOCK_ACCURACY));
> +		igt_assert(video_params.clock <
> +			   mode_clock * (1 + MODE_CLOCK_ACCURACY));
> +	}
> +	igt_assert(video_params.hactive == mode->hdisplay);
> +	igt_assert(video_params.vactive == mode->vdisplay);
> +	igt_assert(video_params.hsync_offset == mode_hsync_offset);
> +	igt_assert(video_params.vsync_offset == mode_vsync_offset);
> +	igt_assert(video_params.htotal == mode->htotal);
> +	igt_assert(video_params.vtotal == mode->vtotal);
> +	igt_assert(video_params.hsync_width == mode_hsync_width);
> +	igt_assert(video_params.vsync_width == mode_vsync_width);
> +	igt_assert(video_params.hsync_polarity == mode_hsync_polarity);
> +	igt_assert(video_params.vsync_polarity == mode_vsync_polarity);
> +}
> +
> +static const char igt_custom_edid_type_read_desc[] =
> +	"Make sure the EDID exposed by KMS is the same as the screen's";
> +static void igt_custom_edid_type_read(chamelium_data_t *data,
> +				      struct chamelium_port *port,
> +				      enum igt_custom_edid_type edid)
> +{
> +	drmModePropertyBlobPtr edid_blob = NULL;
> +	drmModeConnector *connector;
> +	size_t raw_edid_size;
> +	const struct edid *raw_edid;
> +	uint64_t edid_blob_id;
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	chamelium_set_edid(data, port, edid);
> +	chamelium_plug(data->chamelium, port);
> +	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> +					      port, DRM_MODE_CONNECTED);
> +
> +	igt_skip_on(chamelium_check_analog_bridge(data, port));
> +
> +	connector = chamelium_port_get_connector(data->chamelium, port, true);
> +	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> +					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> +					&edid_blob_id, NULL));
> +	igt_assert(edid_blob_id != 0);
> +	edid_blob = drmModeGetPropertyBlob(data->drm_fd, edid_blob_id);
> +	igt_assert(edid_blob);
> +
> +	raw_edid = chamelium_edid_get_raw(data->edids[edid], port);
> +	raw_edid_size = edid_get_size(raw_edid);
> +	igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0);
> +
> +	drmModeFreePropertyBlob(edid_blob);
> +	drmModeFreeConnector(connector);
> +}
> +
> +static const char igt_edid_stress_resolution_desc[] =
> +	"Stress test the DUT by testing multiple EDIDs, one right after the other,"
> +	"and ensure their validity by check the real screen resolution vs the"
> +	"advertised mode resultion.";
> +static void edid_stress_resolution(chamelium_data_t *data,
> +				   struct chamelium_port *port,
> +				   monitor_edid edids_list[],
> +				   size_t edids_list_len)
> +{
> +	int i;
> +	struct chamelium *chamelium = data->chamelium;
> +	struct udev_monitor *mon = igt_watch_uevents();
> +
> +	for (i = 0; i < edids_list_len; ++i) {
> +		struct chamelium_edid *chamelium_edid;
> +		drmModeModeInfo mode;
> +		struct igt_fb fb = { 0 };
> +		igt_output_t *output;
> +		enum pipe pipe;
> +		bool is_video_stable;
> +		int screen_res_w, screen_res_h;
> +
> +		monitor_edid *edid = &edids_list[i];
> +		igt_info("Testing out the EDID for %s\n",
> +			 monitor_edid_get_name(edid));
> +
> +		/* Getting and Setting the EDID on Chamelium. */
> +		chamelium_edid =
> +			get_chameleon_edid_from_monitor_edid(chamelium, edid);
> +		chamelium_port_set_edid(data->chamelium, port, chamelium_edid);
> +		free_chamelium_edid_from_monitor_edid(chamelium_edid);
> +
> +		igt_flush_uevents(mon);
> +		chamelium_plug(chamelium, port);
> +		chamelium_wait_for_connector_after_hotplug(data, mon, port,
> +							   DRM_MODE_CONNECTED);
> +		igt_flush_uevents(mon);
> +
> +		/* Setting an output on the screen to turn it on. */
> +		mode = chamelium_get_mode_for_port(chamelium, port);
> +		chamelium_create_fb_for_mode(data, &fb, &mode);
> +		output = chamelium_get_output_for_port(data, port);
> +		pipe = chamelium_get_pipe_for_output(&data->display, output);
> +		igt_output_set_pipe(output, pipe);
> +		chamelium_enable_output(data, port, output, &mode, &fb);
> +
> +		/* Capture the screen resolution and verify. */
> +		is_video_stable = chamelium_port_wait_video_input_stable(
> +			chamelium, port, 5);
> +		igt_assert(is_video_stable);
> +
> +		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> +					      &screen_res_h);
> +		igt_assert(screen_res_w == fb.width);
> +		igt_assert(screen_res_h == fb.height);
> +
> +		// Clean up
> +		igt_remove_fb(data->drm_fd, &fb);
> +		igt_modeset_disable_all_outputs(&data->display);
> +		chamelium_unplug(chamelium, port);
> +	}
> +
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +}
> +
> +static const char igt_edid_resolution_list_desc[] =
> +	"Get an EDID with many modes of different configurations, set them on the screen and check the"
> +	" screen resolution matches the mode resolution.";
> +
> +static void edid_resolution_list(chamelium_data_t *data,
> +				 struct chamelium_port *port)
> +{
> +	struct chamelium *chamelium = data->chamelium;
> +	struct udev_monitor *mon = igt_watch_uevents();
> +	drmModeConnector *connector;
> +	drmModeModeInfoPtr modes;
> +	int count_modes;
> +	int i;
> +	igt_output_t *output;
> +	enum pipe pipe;
> +
> +	chamelium_unplug(chamelium, port);
> +	chamelium_set_edid(data, port, IGT_CUSTOM_EDID_FULL);
> +
> +	igt_flush_uevents(mon);
> +	chamelium_plug(chamelium, port);
> +	chamelium_wait_for_connector_after_hotplug(data, mon, port,
> +						   DRM_MODE_CONNECTED);
> +	igt_flush_uevents(mon);
> +
> +	connector = chamelium_port_get_connector(chamelium, port, true);
> +	modes = connector->modes;
> +	count_modes = connector->count_modes;
> +
> +	output = chamelium_get_output_for_port(data, port);
> +	pipe = chamelium_get_pipe_for_output(&data->display, output);
> +	igt_output_set_pipe(output, pipe);
> +
> +	for (i = 0; i < count_modes; ++i)
> +		igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh);
> +
> +	for (i = 0; i < count_modes; ++i) {
> +		struct igt_fb fb = { 0 };
> +		bool is_video_stable;
> +		int screen_res_w, screen_res_h;
> +
> +		igt_info("Testing #%d %s %uHz\n", i, modes[i].name,
> +			 modes[i].vrefresh);
> +
> +		/* Set the screen mode with the one we chose. */
> +		chamelium_create_fb_for_mode(data, &fb, &modes[i]);
> +		chamelium_enable_output(data, port, output, &modes[i], &fb);
> +		is_video_stable = chamelium_port_wait_video_input_stable(
> +			chamelium, port, 10);
> +		igt_assert(is_video_stable);
> +
> +		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> +					      &screen_res_h);
> +		igt_assert_eq(screen_res_w, modes[i].hdisplay);
> +		igt_assert_eq(screen_res_h, modes[i].vdisplay);
> +
> +		igt_remove_fb(data->drm_fd, &fb);
> +	}
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	drmModeFreeConnector(connector);
> +}
> +
> +static const char test_suspend_resume_edid_change_desc[] =
> +	"Simulate a screen being unplugged and another screen being plugged "
> +	"during suspend, check that a uevent is sent and connector status is "
> +	"updated";
> +static void test_suspend_resume_edid_change(chamelium_data_t *data,
> +					    struct chamelium_port *port,
> +					    enum igt_suspend_state state,
> +					    enum igt_suspend_test test,
> +					    enum igt_custom_edid_type edid,
> +					    enum igt_custom_edid_type alt_edid)
> +{
> +	struct udev_monitor *mon = igt_watch_uevents();
> +	bool link_status_failed[2][data->port_count];
> +	int p;
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	/* Catch the event and flush all remaining ones. */
> +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> +	igt_flush_uevents(mon);
> +
> +	/* First plug in the port */
> +	chamelium_set_edid(data, port, edid);
> +	chamelium_plug(data->chamelium, port);
> +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> +
> +	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> +					      port, DRM_MODE_CONNECTED);
> +
> +	/*
> +	 * Change the edid before we suspend. On resume, the machine should
> +	 * notice the EDID change and fire a hotplug event.
> +	 */
> +	chamelium_set_edid(data, port, alt_edid);
> +
> +	get_connectors_link_status_failed(data, link_status_failed[0]);
> +
> +	igt_flush_uevents(mon);
> +
> +	igt_system_suspend_autoresume(state, test);
> +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> +	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> +
> +	get_connectors_link_status_failed(data, link_status_failed[1]);
> +
> +	for (p = 0; p < data->port_count; p++)
> +		igt_skip_on(!link_status_failed[0][p] &&
> +			    link_status_failed[1][p]);
> +}
> +
> +static const char test_mode_timings_desc[] =
> +	"For each mode of the IGT base EDID, perform a modeset and check the "
> +	"mode detected by the Chamelium receiver matches the mode we set";
> +static void test_mode_timings(chamelium_data_t *data,
> +			      struct chamelium_port *port)
> +{
> +	int i, count_modes;
> +
> +	i = 0;
> +	igt_require(chamelium_supports_get_video_params(data->chamelium));
> +	do {
> +		igt_output_t *output;
> +		igt_plane_t *primary;
> +		drmModeConnector *connector;
> +		drmModeModeInfo *mode;
> +		int fb_id;
> +		struct igt_fb fb;
> +
> +		/*
> +		 * let's reset state each mode so we will get the
> +		 * HPD pulses realibably
> +		 */
> +		igt_modeset_disable_all_outputs(&data->display);
> +		chamelium_reset_state(&data->display, data->chamelium, port,
> +				      data->ports, data->port_count);
> +
> +		/*
> +		 * modes may change due to mode pruining and link issues, so we
> +		 * need to refresh the connector
> +		 */
> +		output = chamelium_prepare_output(data, port,
> +						  IGT_CUSTOM_EDID_BASE);
> +		connector = chamelium_port_get_connector(data->chamelium, port,
> +							 false);
> +		primary = igt_output_get_plane_type(output,
> +						    DRM_PLANE_TYPE_PRIMARY);
> +		igt_assert(primary);
> +
> +		/* we may skip some modes due to above but that's ok */
> +		count_modes = connector->count_modes;
> +		if (i >= count_modes)
> +			break;
> +
> +		mode = &connector->modes[i];
> +
> +		fb_id = igt_create_color_pattern_fb(
> +			data->drm_fd, mode->hdisplay, mode->vdisplay,
> +			DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> +			&fb);
> +		igt_assert(fb_id > 0);
> +
> +		chamelium_enable_output(data, port, output, mode, &fb);
> +
> +		/* Trigger the FSM */
> +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0);
> +
> +		check_mode(data->chamelium, port, mode);
> +
> +		igt_remove_fb(data->drm_fd, &fb);
> +		drmModeFreeConnector(connector);
> +	} while (++i < count_modes);
> +}
> +
> +IGT_TEST_DESCRIPTION("Testing EDID with a Chamelium board");
> +igt_main
> +{
> +	chamelium_data_t data;
> +	struct chamelium_port *port;
> +	int p;
> +
> +	igt_fixture {
> +		chamelium_init_test(&data);
> +	}
> +
> +	igt_describe("DisplayPort tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(igt_custom_edid_type_read_desc);
> +		connector_subtest("dp-edid-read", DisplayPort)
> +		{
> +			igt_custom_edid_type_read(&data, port,
> +						  IGT_CUSTOM_EDID_BASE);
> +			igt_custom_edid_type_read(&data, port,
> +						  IGT_CUSTOM_EDID_ALT);
> +		}
> +
> +		igt_describe(igt_edid_stress_resolution_desc);
> +		connector_subtest("dp-edid-stress-resolution-4k", DisplayPort)
> +			edid_stress_resolution(&data, port, DP_EDIDS_4K,
> +					       ARRAY_SIZE(DP_EDIDS_4K));
> +
> +		igt_describe(igt_edid_stress_resolution_desc);
> +		connector_subtest("dp-edid-stress-resolution-non-4k",
> +				  DisplayPort)
> +			edid_stress_resolution(&data, port, DP_EDIDS_NON_4K,
> +					       ARRAY_SIZE(DP_EDIDS_NON_4K));
> +
> +		igt_describe(igt_edid_resolution_list_desc);
> +		connector_subtest("dp-edid-resolution-list", DisplayPort)
> +			edid_resolution_list(&data, port);
> +
> +		igt_describe(test_suspend_resume_edid_change_desc);
> +		connector_subtest("dp-edid-change-during-suspend", DisplayPort)
> +			test_suspend_resume_edid_change(&data, port,
> +							SUSPEND_STATE_MEM,
> +							SUSPEND_TEST_NONE,
> +							IGT_CUSTOM_EDID_BASE,
> +							IGT_CUSTOM_EDID_ALT);
> +
> +		igt_describe(test_suspend_resume_edid_change_desc);
> +		connector_subtest("dp-edid-change-during-hibernate",
> +				  DisplayPort)
> +			test_suspend_resume_edid_change(&data, port,
> +							SUSPEND_STATE_DISK,
> +							SUSPEND_TEST_DEVICES,
> +							IGT_CUSTOM_EDID_BASE,
> +							IGT_CUSTOM_EDID_ALT);
> +
> +		igt_describe(test_mode_timings_desc);
> +		connector_subtest("dp-mode-timings", DisplayPort)
> +			test_mode_timings(&data, port);
> +	}
> +
> +	igt_describe("HDMI tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(igt_custom_edid_type_read_desc);
> +		connector_subtest("hdmi-edid-read", HDMIA)
> +		{
> +			igt_custom_edid_type_read(&data, port,
> +						  IGT_CUSTOM_EDID_BASE);
> +			igt_custom_edid_type_read(&data, port,
> +						  IGT_CUSTOM_EDID_ALT);
> +		}
> +
> +		igt_describe(igt_edid_stress_resolution_desc);
> +		connector_subtest("hdmi-edid-stress-resolution-4k", HDMIA)
> +			edid_stress_resolution(&data, port, HDMI_EDIDS_4K,
> +					       ARRAY_SIZE(HDMI_EDIDS_4K));
> +
> +		igt_describe(igt_edid_stress_resolution_desc);
> +		connector_subtest("hdmi-edid-stress-resolution-non-4k", HDMIA)
> +			edid_stress_resolution(&data, port, HDMI_EDIDS_NON_4K,
> +					       ARRAY_SIZE(HDMI_EDIDS_NON_4K));
> +
> +		igt_describe(test_suspend_resume_edid_change_desc);
> +		connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
> +			test_suspend_resume_edid_change(&data, port,
> +							SUSPEND_STATE_MEM,
> +							SUSPEND_TEST_NONE,
> +							IGT_CUSTOM_EDID_BASE,
> +							IGT_CUSTOM_EDID_ALT);
> +
> +		igt_describe(test_suspend_resume_edid_change_desc);
> +		connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
> +			test_suspend_resume_edid_change(&data, port,
> +							SUSPEND_STATE_DISK,
> +							SUSPEND_TEST_DEVICES,
> +							IGT_CUSTOM_EDID_BASE,
> +							IGT_CUSTOM_EDID_ALT);
> +
> +		igt_describe(test_mode_timings_desc);
> +		connector_subtest("hdmi-mode-timings", HDMIA)
> +			test_mode_timings(&data, port);
> +	}
> +
> +	igt_describe("VGA tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_VGA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(igt_custom_edid_type_read_desc);
> +		connector_subtest("vga-edid-read", VGA)
> +		{
> +			igt_custom_edid_type_read(&data, port,
> +						  IGT_CUSTOM_EDID_BASE);
> +			igt_custom_edid_type_read(&data, port,
> +						  IGT_CUSTOM_EDID_ALT);
> +		}
> +	}
> +
> +	igt_fixture {
> +		igt_display_fini(&data.display);
> +		close(data.drm_fd);
> +	}
> +}
> diff --git a/tests/chamelium/kms_chamelium_frames.c b/tests/chamelium/kms_chamelium_frames.c
> new file mode 100644
> index 00000000..008bc34b
> --- /dev/null
> +++ b/tests/chamelium/kms_chamelium_frames.c
> @@ -0,0 +1,1085 @@
> +/*
> + * Copyright © 2016 Red Hat 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 (including the next
> + * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
> + *
> + * Authors:
> + *    Lyude Paul <lyude@redhat.com>
> + */
> +
> +#include "igt_eld.h"
> +#include "igt_infoframe.h"
> +#include "kms_chamelium_helper.h"
> +
> +#define connector_dynamic_subtest(name__, type__)                   \
> +	igt_subtest_with_dynamic(name__)                            \
> +	for_each_port(p, port) if (chamelium_port_get_type(port) == \
> +				   DRM_MODE_CONNECTOR_##type__)
> +
> +struct vic_mode {
> +	int hactive, vactive;
> +	int vrefresh; /* Hz */
> +	uint32_t picture_ar;
> +};
> +
> +static int chamelium_vga_modes[][2] = {
> +	{ 1600, 1200 }, { 1920, 1200 }, { 1920, 1080 }, { 1680, 1050 },
> +	{ 1280, 1024 }, { 1280, 960 },	{ 1440, 900 },	{ 1280, 800 },
> +	{ 1024, 768 },	{ 1360, 768 },	{ 1280, 720 },	{ 800, 600 },
> +	{ 640, 480 },	{ -1, -1 },
> +};
> +
> +/* Maps Video Identification Codes to a mode */
> +static const struct vic_mode vic_modes[] = {
> +	[16] = {
> +		.hactive = 1920,
> +		.vactive = 1080,
> +		.vrefresh = 60,
> +		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
> +	},
> +};
> +
> +/* Maps aspect ratios to their mode flag */
> +static const uint32_t mode_ar_flags[] = {
> +	[DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9,
> +};
> +
> +static bool prune_vga_mode(chamelium_data_t *data, drmModeModeInfo *mode)
> +{
> +	int i = 0;
> +
> +	while (chamelium_vga_modes[i][0] != -1) {
> +		if (mode->hdisplay == chamelium_vga_modes[i][0] &&
> +		    mode->vdisplay == chamelium_vga_modes[i][1])
> +			return false;
> +
> +		i++;
> +	}
> +
> +	return true;
> +}
> +
> +static void do_test_display(chamelium_data_t *data, struct chamelium_port *port,
> +			    igt_output_t *output, drmModeModeInfo *mode,
> +			    uint32_t fourcc, enum chamelium_check check,
> +			    int count)
> +{
> +	struct chamelium_fb_crc_async_data *fb_crc;
> +	struct igt_fb frame_fb, fb;
> +	int i, fb_id, captured_frame_count;
> +	int frame_id;
> +
> +	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> +					 DRM_FORMAT_XRGB8888, 64, &fb);
> +	igt_assert(fb_id > 0);
> +
> +	frame_id =
> +		igt_fb_convert(&frame_fb, &fb, fourcc, DRM_FORMAT_MOD_LINEAR);
> +	igt_assert(frame_id > 0);
> +
> +	if (check == CHAMELIUM_CHECK_CRC)
> +		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> +								&fb);
> +
> +	chamelium_enable_output(data, port, output, mode, &frame_fb);
> +
> +	if (check == CHAMELIUM_CHECK_CRC) {
> +		igt_crc_t *expected_crc;
> +		igt_crc_t *crc;
> +
> +		/* We want to keep the display running for a little bit, since
> +		 * there's always the potential the driver isn't able to keep
> +		 * the display running properly for very long
> +		 */
> +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count);
> +		crc = chamelium_read_captured_crcs(data->chamelium,
> +						   &captured_frame_count);
> +
> +		igt_assert(captured_frame_count == count);
> +
> +		igt_debug("Captured %d frames\n", captured_frame_count);
> +
> +		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> +
> +		for (i = 0; i < captured_frame_count; i++)
> +			chamelium_assert_crc_eq_or_dump(
> +				data->chamelium, expected_crc, &crc[i], &fb, i);
> +
> +		free(expected_crc);
> +		free(crc);
> +	} else if (check == CHAMELIUM_CHECK_ANALOG ||
> +		   check == CHAMELIUM_CHECK_CHECKERBOARD) {
> +		struct chamelium_frame_dump *dump;
> +
> +		igt_assert(count == 1);
> +
> +		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> +						  0, 0);
> +
> +		if (check == CHAMELIUM_CHECK_ANALOG)
> +			chamelium_crop_analog_frame(dump, mode->hdisplay,
> +						    mode->vdisplay);
> +
> +		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> +						     dump, &fb, check);
> +		chamelium_destroy_frame_dump(dump);
> +	}
> +
> +	igt_remove_fb(data->drm_fd, &frame_fb);
> +	igt_remove_fb(data->drm_fd, &fb);
> +}
> +
> +static enum infoframe_avi_picture_aspect_ratio
> +get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
> +{
> +	/* The AVI picture aspect ratio field only supports 4:3 and 16:9 */
> +	switch (aspect_ratio) {
> +	case DRM_MODE_PICTURE_ASPECT_4_3:
> +		return INFOFRAME_AVI_PIC_AR_4_3;
> +	case DRM_MODE_PICTURE_ASPECT_16_9:
> +		return INFOFRAME_AVI_PIC_AR_16_9;
> +	default:
> +		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
> +	}
> +}
> +
> +static bool vic_mode_matches_drm(const struct vic_mode *vic_mode,
> +				 drmModeModeInfo *drm_mode)
> +{
> +	uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar];
> +
> +	return vic_mode->hactive == drm_mode->hdisplay &&
> +	       vic_mode->vactive == drm_mode->vdisplay &&
> +	       vic_mode->vrefresh == drm_mode->vrefresh &&
> +	       ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
> +}
> +
> +static void randomize_plane_stride(chamelium_data_t *data, uint32_t width,
> +				   uint32_t height, uint32_t format,
> +				   uint64_t modifier, size_t *stride)
> +{
> +	size_t stride_min;
> +	uint32_t max_tile_w = 4, tile_w, tile_h;
> +	int i;
> +	struct igt_fb dummy;
> +
> +	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
> +
> +	/* Randomize the stride to less than twice the minimum. */
> +	*stride = (rand() % stride_min) + stride_min;
> +
> +	/*
> +	 * Create a dummy FB to determine bpp for each plane, and calculate
> +	 * the maximum tile width from that.
> +	 */
> +	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
> +	for (i = 0; i < dummy.num_planes; i++) {
> +		igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i],
> +				     &tile_w, &tile_h);
> +
> +		if (tile_w > max_tile_w)
> +			max_tile_w = tile_w;
> +	}
> +	igt_remove_fb(data->drm_fd, &dummy);
> +
> +	/*
> +	 * Pixman requires the stride to be aligned to 32-bits, which is
> +	 * reflected in the initial value of max_tile_w and the hw
> +	 * may require a multiple of tile width, choose biggest of the 2.
> +	 */
> +	*stride = ALIGN(*stride, max_tile_w);
> +}
> +
> +static void update_tiled_modifier(igt_plane_t *plane, uint32_t width,
> +				  uint32_t height, uint32_t format,
> +				  uint64_t *modifier)
> +{
> +	if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) {
> +		/* Randomize the column height to less than twice the minimum.
> +		 */
> +		size_t column_height = (rand() % height) + height;
> +
> +		igt_debug(
> +			"Selecting VC4 SAND256 tiling with column height %ld\n",
> +			column_height);
> +
> +		*modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(
> +			column_height);
> +	}
> +}
> +
> +static void randomize_plane_setup(chamelium_data_t *data, igt_plane_t *plane,
> +				  drmModeModeInfo *mode, uint32_t *width,
> +				  uint32_t *height, uint32_t *format,
> +				  uint64_t *modifier, bool allow_yuv)
> +{
> +	int min_dim;
> +	uint32_t idx[plane->format_mod_count];
> +	unsigned int count = 0;
> +	unsigned int i;
> +
> +	/* First pass to count the supported formats. */
> +	for (i = 0; i < plane->format_mod_count; i++)
> +		if (igt_fb_supported_format(plane->formats[i]) &&
> +		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
> +			idx[count++] = i;
> +
> +	igt_assert(count > 0);
> +
> +	i = idx[rand() % count];
> +	*format = plane->formats[i];
> +	*modifier = plane->modifiers[i];
> +
> +	update_tiled_modifier(plane, *width, *height, *format, modifier);
> +
> +	/*
> +	 * Randomize width and height in the mode dimensions range.
> +	 *
> +	 * Restrict to a min of 2 * min_dim, this way src_w/h are always at
> +	 * least min_dim, because src_w = width - (rand % w / 2).
> +	 *
> +	 * Use a minimum dimension of 16 for YUV, because planar YUV
> +	 * subsamples the UV plane.
> +	 */
> +	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
> +
> +	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
> +	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
> +}
> +
> +static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h,
> +			    uint32_t src_x, uint32_t src_y, uint32_t crtc_w,
> +			    uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y,
> +			    struct igt_fb *fb)
> +{
> +	igt_plane_set_fb(plane, fb);
> +
> +	igt_plane_set_position(plane, crtc_x, crtc_y);
> +	igt_plane_set_size(plane, crtc_w, crtc_h);
> +
> +	igt_fb_set_position(fb, plane, src_x, src_y);
> +	igt_fb_set_size(fb, plane, src_w, src_h);
> +}
> +
> +static void randomize_plane_coordinates(
> +	chamelium_data_t *data, igt_plane_t *plane, drmModeModeInfo *mode,
> +	struct igt_fb *fb, uint32_t *src_w, uint32_t *src_h, uint32_t *src_x,
> +	uint32_t *src_y, uint32_t *crtc_w, uint32_t *crtc_h, int32_t *crtc_x,
> +	int32_t *crtc_y, bool allow_scaling)
> +{
> +	bool is_yuv = igt_format_is_yuv(fb->drm_format);
> +	uint32_t width = fb->width, height = fb->height;
> +	double ratio;
> +	int ret;
> +
> +	/* Randomize source offset in the first half of the original size. */
> +	*src_x = rand() % (width / 2);
> +	*src_y = rand() % (height / 2);
> +
> +	/* The source size only includes the active source area. */
> +	*src_w = width - *src_x;
> +	*src_h = height - *src_y;
> +
> +	if (allow_scaling) {
> +		*crtc_w = (rand() % mode->hdisplay) + 1;
> +		*crtc_h = (rand() % mode->vdisplay) + 1;
> +
> +		/*
> +		 * Don't bother with scaling if dimensions are quite close in
> +		 * order to get non-scaling cases more frequently. Also limit
> +		 * scaling to 3x to avoid aggressive filtering that makes
> +		 * comparison less reliable, and don't go above 2x downsampling
> +		 * to avoid possible hw limitations.
> +		 */
> +
> +		ratio = ((double)*crtc_w / *src_w);
> +		if (ratio < 0.5)
> +			*src_w = *crtc_w * 2;
> +		else if (ratio > 0.8 && ratio < 1.2)
> +			*crtc_w = *src_w;
> +		else if (ratio > 3.0)
> +			*crtc_w = *src_w * 3;
> +
> +		ratio = ((double)*crtc_h / *src_h);
> +		if (ratio < 0.5)
> +			*src_h = *crtc_h * 2;
> +		else if (ratio > 0.8 && ratio < 1.2)
> +			*crtc_h = *src_h;
> +		else if (ratio > 3.0)
> +			*crtc_h = *src_h * 3;
> +	} else {
> +		*crtc_w = *src_w;
> +		*crtc_h = *src_h;
> +	}
> +
> +	if (*crtc_w != *src_w || *crtc_h != *src_h) {
> +		/*
> +		 * When scaling is involved, make sure to not go off-bounds or
> +		 * scaled clipping may result in decimal dimensions, that most
> +		 * drivers don't support.
> +		 */
> +		if (*crtc_w < mode->hdisplay)
> +			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
> +		else
> +			*crtc_x = 0;
> +
> +		if (*crtc_h < mode->vdisplay)
> +			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
> +		else
> +			*crtc_y = 0;
> +	} else {
> +		/*
> +		 * Randomize the on-crtc position and allow the plane to go
> +		 * off-display by less than half of its on-crtc dimensions.
> +		 */
> +		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
> +		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
> +	}
> +
> +	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, *crtc_h,
> +			*crtc_x, *crtc_y, fb);
> +	ret = igt_display_try_commit_atomic(
> +		&data->display,
> +		DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
> +		NULL);
> +	if (!ret)
> +		return;
> +
> +	/* Coordinates are logged in the dumped debug log, so only report w/h on
> +	 * failure here. */
> +	igt_assert_f(ret != -ENOSPC,
> +		     "Failure in testcase, invalid coordinates on a %ux%u fb\n",
> +		     width, height);
> +
> +	/* Make YUV coordinates a multiple of 2 and retry the math. */
> +	if (is_yuv) {
> +		*src_x &= ~1;
> +		*src_y &= ~1;
> +		*src_w &= ~1;
> +		*src_h &= ~1;
> +		/* To handle 1:1 scaling, clear crtc_w/h too. */
> +		*crtc_w &= ~1;
> +		*crtc_h &= ~1;
> +
> +		if (*crtc_x < 0 && (*crtc_x & 1))
> +			(*crtc_x)++;
> +		else
> +			*crtc_x &= ~1;
> +
> +		/* If negative, round up to 0 instead of down */
> +		if (*crtc_y < 0 && (*crtc_y & 1))
> +			(*crtc_y)++;
> +		else
> +			*crtc_y &= ~1;
> +
> +		configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
> +				*crtc_h, *crtc_x, *crtc_y, fb);
> +		ret = igt_display_try_commit_atomic(
> +			&data->display,
> +			DRM_MODE_ATOMIC_TEST_ONLY |
> +				DRM_MODE_ATOMIC_ALLOW_MODESET,
> +			NULL);
> +		if (!ret)
> +			return;
> +	}
> +
> +	igt_assert(!ret || allow_scaling);
> +	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
> +		 ((double)*crtc_w / *src_w), ((double)*crtc_h / *src_h));
> +
> +	*crtc_w = *src_w;
> +	*crtc_h = *src_h;
> +
> +	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, *crtc_h,
> +			*crtc_x, *crtc_y, fb);
> +	igt_display_commit_atomic(&data->display,
> +				  DRM_MODE_ATOMIC_TEST_ONLY |
> +					  DRM_MODE_ATOMIC_ALLOW_MODESET,
> +				  NULL);
> +}
> +
> +static void blit_plane_cairo(chamelium_data_t *data, cairo_surface_t *result,
> +			     uint32_t src_w, uint32_t src_h, uint32_t src_x,
> +			     uint32_t src_y, uint32_t crtc_w, uint32_t crtc_h,
> +			     int32_t crtc_x, int32_t crtc_y, struct igt_fb *fb)
> +{
> +	cairo_surface_t *surface;
> +	cairo_surface_t *clipped_surface;
> +	cairo_t *cr;
> +
> +	surface = igt_get_cairo_surface(data->drm_fd, fb);
> +
> +	if (src_x || src_y) {
> +		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
> +							     src_w, src_h);
> +
> +		cr = cairo_create(clipped_surface);
> +
> +		cairo_translate(cr, -1. * src_x, -1. * src_y);
> +
> +		cairo_set_source_surface(cr, surface, 0, 0);
> +
> +		cairo_paint(cr);
> +		cairo_surface_flush(clipped_surface);
> +
> +		cairo_destroy(cr);
> +	} else {
> +		clipped_surface = surface;
> +	}
> +
> +	cr = cairo_create(result);
> +
> +	cairo_translate(cr, crtc_x, crtc_y);
> +
> +	if (src_w != crtc_w || src_h != crtc_h) {
> +		cairo_scale(cr, (double)crtc_w / src_w, (double)crtc_h / src_h);
> +	}
> +
> +	cairo_set_source_surface(cr, clipped_surface, 0, 0);
> +	cairo_surface_destroy(clipped_surface);
> +
> +	if (src_w != crtc_w || src_h != crtc_h) {
> +		cairo_pattern_set_filter(cairo_get_source(cr),
> +					 CAIRO_FILTER_BILINEAR);
> +		cairo_pattern_set_extend(cairo_get_source(cr),
> +					 CAIRO_EXTEND_NONE);
> +	}
> +
> +	cairo_paint(cr);
> +	cairo_surface_flush(result);
> +
> +	cairo_destroy(cr);
> +}
> +
> +static void prepare_randomized_plane(chamelium_data_t *data,
> +				     drmModeModeInfo *mode, igt_plane_t *plane,
> +				     struct igt_fb *overlay_fb,
> +				     unsigned int index,
> +				     cairo_surface_t *result_surface,
> +				     bool allow_scaling, bool allow_yuv)
> +{
> +	struct igt_fb pattern_fb;
> +	uint32_t overlay_fb_w, overlay_fb_h;
> +	uint32_t overlay_src_w, overlay_src_h;
> +	uint32_t overlay_src_x, overlay_src_y;
> +	int32_t overlay_crtc_x, overlay_crtc_y;
> +	uint32_t overlay_crtc_w, overlay_crtc_h;
> +	uint32_t format;
> +	uint64_t modifier;
> +	size_t stride;
> +	bool tiled;
> +	int fb_id;
> +
> +	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
> +			      &format, &modifier, allow_yuv);
> +
> +	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
> +	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n", index,
> +		  overlay_fb_w, overlay_fb_h, igt_format_str(format),
> +		  tiled ? "tiled" : "linear");
> +
> +	/* Get a pattern framebuffer for the overlay plane. */
> +	fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h,
> +					 DRM_FORMAT_XRGB8888, 32, &pattern_fb);
> +	igt_assert(fb_id > 0);
> +
> +	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h, format,
> +			       modifier, &stride);
> +
> +	igt_debug("Plane %d: stride %ld\n", index, stride);
> +
> +	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
> +					   modifier, stride);
> +	igt_assert(fb_id > 0);
> +
> +	randomize_plane_coordinates(data, plane, mode, overlay_fb,
> +				    &overlay_src_w, &overlay_src_h,
> +				    &overlay_src_x, &overlay_src_y,
> +				    &overlay_crtc_w, &overlay_crtc_h,
> +				    &overlay_crtc_x, &overlay_crtc_y,
> +				    allow_scaling);
> +
> +	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index, overlay_src_w,
> +		  overlay_src_h);
> +	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
> +		  overlay_src_x, overlay_src_y);
> +	igt_debug("Plane %d: on-crtc size %dx%d\n", index, overlay_crtc_w,
> +		  overlay_crtc_h);
> +	igt_debug("Plane %d: on-crtc position %dx%d\n", index, overlay_crtc_x,
> +		  overlay_crtc_y);
> +
> +	blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h,
> +			 overlay_src_x, overlay_src_y, overlay_crtc_w,
> +			 overlay_crtc_h, overlay_crtc_x, overlay_crtc_y,
> +			 &pattern_fb);
> +
> +	/* Remove the original pattern framebuffer. */
> +	igt_remove_fb(data->drm_fd, &pattern_fb);
> +}
> +
> +static const char test_display_one_mode_desc[] =
> +	"Pick the first mode of the IGT base EDID, display and capture a few "
> +	"frames, then check captured frames are correct";
> +static void test_display_one_mode(chamelium_data_t *data,
> +				  struct chamelium_port *port, uint32_t fourcc,
> +				  enum chamelium_check check, int count)
> +{
> +	drmModeConnector *connector;
> +	drmModeModeInfo *mode;
> +	igt_output_t *output;
> +	igt_plane_t *primary;
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	output = chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	igt_assert(primary);
> +
> +	igt_require(igt_plane_has_format_mod(primary, fourcc,
> +					     DRM_FORMAT_MOD_LINEAR));
> +
> +	mode = &connector->modes[0];
> +	if (check == CHAMELIUM_CHECK_ANALOG) {
> +		bool bridge = chamelium_check_analog_bridge(data, port);
> +
> +		igt_assert(!(bridge && prune_vga_mode(data, mode)));
> +	}
> +
> +	do_test_display(data, port, output, mode, fourcc, check, count);
> +
> +	drmModeFreeConnector(connector);
> +}
> +
> +static const char test_display_all_modes_desc[] =
> +	"For each mode of the IGT base EDID, display and capture a few "
> +	"frames, then check captured frames are correct";
> +static void test_display_all_modes(chamelium_data_t *data,
> +				   struct chamelium_port *port, uint32_t fourcc,
> +				   enum chamelium_check check, int count)
> +{
> +	bool bridge;
> +	int i, count_modes;
> +
> +	if (check == CHAMELIUM_CHECK_ANALOG)
> +		bridge = chamelium_check_analog_bridge(data, port);
> +
> +	i = 0;
> +	do {
> +		igt_output_t *output;
> +		igt_plane_t *primary;
> +		drmModeConnector *connector;
> +		drmModeModeInfo *mode;
> +
> +		/*
> +		 * let's reset state each mode so we will get the
> +		 * HPD pulses realibably
> +		 */
> +		igt_modeset_disable_all_outputs(&data->display);
> +		chamelium_reset_state(&data->display, data->chamelium, port,
> +				      data->ports, data->port_count);
> +
> +		/*
> +		 * modes may change due to mode pruining and link issues, so we
> +		 * need to refresh the connector
> +		 */
> +		output = chamelium_prepare_output(data, port,
> +						  IGT_CUSTOM_EDID_BASE);
> +		connector = chamelium_port_get_connector(data->chamelium, port,
> +							 false);
> +		primary = igt_output_get_plane_type(output,
> +						    DRM_PLANE_TYPE_PRIMARY);
> +		igt_assert(primary);
> +		igt_require(igt_plane_has_format_mod(primary, fourcc,
> +						     DRM_FORMAT_MOD_LINEAR));
> +
> +		/* we may skip some modes due to above but that's ok */
> +		count_modes = connector->count_modes;
> +		if (i >= count_modes)
> +			break;
> +
> +		mode = &connector->modes[i];
> +
> +		if (check == CHAMELIUM_CHECK_ANALOG && bridge &&
> +		    prune_vga_mode(data, mode))
> +			continue;
> +
> +		do_test_display(data, port, output, mode, fourcc, check, count);
> +		drmModeFreeConnector(connector);
> +	} while (++i < count_modes);
> +}
> +
> +static const char test_display_frame_dump_desc[] =
> +	"For each mode of the IGT base EDID, display and capture a few "
> +	"frames, then download the captured frames and compare them "
> +	"bit-by-bit to the sent ones";
> +static void test_display_frame_dump(chamelium_data_t *data,
> +				    struct chamelium_port *port)
> +{
> +	int i, count_modes;
> +
> +	i = 0;
> +	do {
> +		igt_output_t *output;
> +		igt_plane_t *primary;
> +		struct igt_fb fb;
> +		struct chamelium_frame_dump *frame;
> +		drmModeModeInfo *mode;
> +		drmModeConnector *connector;
> +		int fb_id, j;
> +
> +		/*
> +		 * let's reset state each mode so we will get the
> +		 * HPD pulses realibably
> +		 */
> +		igt_modeset_disable_all_outputs(&data->display);
> +		chamelium_reset_state(&data->display, data->chamelium, port,
> +				      data->ports, data->port_count);
> +
> +		/*
> +		 * modes may change due to mode pruining and link issues, so we
> +		 * need to refresh the connector
> +		 */
> +		output = chamelium_prepare_output(data, port,
> +						  IGT_CUSTOM_EDID_BASE);
> +		connector = chamelium_port_get_connector(data->chamelium, port,
> +							 false);
> +		primary = igt_output_get_plane_type(output,
> +						    DRM_PLANE_TYPE_PRIMARY);
> +		igt_assert(primary);
> +
> +		/* we may skip some modes due to above but that's ok */
> +		count_modes = connector->count_modes;
> +		if (i >= count_modes)
> +			break;
> +
> +		mode = &connector->modes[i];
> +
> +		fb_id = igt_create_color_pattern_fb(
> +			data->drm_fd, mode->hdisplay, mode->vdisplay,
> +			DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> +			&fb);
> +		igt_assert(fb_id > 0);
> +
> +		chamelium_enable_output(data, port, output, mode, &fb);
> +
> +		igt_debug("Reading frame dumps from Chamelium...\n");
> +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
> +		for (j = 0; j < 5; j++) {
> +			frame = chamelium_read_captured_frame(data->chamelium,
> +							      j);
> +			chamelium_assert_frame_eq(data->chamelium, frame, &fb);
> +			chamelium_destroy_frame_dump(frame);
> +		}
> +
> +		igt_remove_fb(data->drm_fd, &fb);
> +		drmModeFreeConnector(connector);
> +	} while (++i < count_modes);
> +}
> +
> +static const char test_display_aspect_ratio_desc[] =
> +	"Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and "
> +	"check they include the relevant fields";
> +static void test_display_aspect_ratio(chamelium_data_t *data,
> +				      struct chamelium_port *port)
> +{
> +	igt_output_t *output;
> +	igt_plane_t *primary;
> +	drmModeConnector *connector;
> +	drmModeModeInfo *mode;
> +	int fb_id, i;
> +	struct igt_fb fb;
> +	bool found, ok;
> +	struct chamelium_infoframe *infoframe;
> +	struct infoframe_avi infoframe_avi;
> +	uint8_t vic = 16; /* TODO: test more VICs */
> +	const struct vic_mode *vic_mode;
> +	uint32_t aspect_ratio;
> +	enum infoframe_avi_picture_aspect_ratio frame_ar;
> +
> +	igt_require(chamelium_supports_get_last_infoframe(data->chamelium));
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	output = chamelium_prepare_output(data, port,
> +					  IGT_CUSTOM_EDID_ASPECT_RATIO);
> +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	igt_assert(primary);
> +
> +	vic_mode = &vic_modes[vic];
> +	aspect_ratio = vic_mode->picture_ar;
> +
> +	found = false;
> +	igt_assert(connector->count_modes > 0);
> +	for (i = 0; i < connector->count_modes; i++) {
> +		mode = &connector->modes[i];
> +
> +		if (vic_mode_matches_drm(vic_mode, mode)) {
> +			found = true;
> +			break;
> +		}
> +	}
> +	igt_assert_f(found,
> +		     "Failed to find mode with the correct aspect ratio\n");
> +
> +	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
> +					    mode->vdisplay, DRM_FORMAT_XRGB8888,
> +					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> +					    &fb);
> +	igt_assert(fb_id > 0);
> +
> +	chamelium_enable_output(data, port, output, mode, &fb);
> +
> +	infoframe = chamelium_get_last_infoframe(data->chamelium, port,
> +						 CHAMELIUM_INFOFRAME_AVI);
> +	igt_assert_f(infoframe, "AVI InfoFrame not received\n");
> +
> +	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
> +				 infoframe->payload, infoframe->payload_size);
> +	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
> +
> +	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
> +
> +	igt_debug("Checking AVI InfoFrame\n");
> +	igt_debug("Picture aspect ratio: got %d, expected %d\n",
> +		  infoframe_avi.picture_aspect_ratio, frame_ar);
> +	igt_debug("Video Identification Code (VIC): got %d, expected %d\n",
> +		  infoframe_avi.vic, vic);
> +
> +	igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar);
> +	igt_assert(infoframe_avi.vic == vic);
> +
> +	chamelium_infoframe_destroy(infoframe);
> +	igt_remove_fb(data->drm_fd, &fb);
> +	drmModeFreeConnector(connector);
> +}
> +
> +static const char test_display_planes_random_desc[] =
> +	"Setup a few overlay planes with random parameters, capture the frame "
> +	"and check it matches the expected output";
> +static void test_display_planes_random(chamelium_data_t *data,
> +				       struct chamelium_port *port,
> +				       enum chamelium_check check)
> +{
> +	igt_output_t *output;
> +	drmModeModeInfo *mode;
> +	igt_plane_t *primary_plane;
> +	struct igt_fb primary_fb;
> +	struct igt_fb result_fb;
> +	struct igt_fb *overlay_fbs;
> +	igt_crc_t *crc;
> +	igt_crc_t *expected_crc;
> +	struct chamelium_fb_crc_async_data *fb_crc;
> +	unsigned int overlay_planes_max = 0;
> +	unsigned int overlay_planes_count;
> +	cairo_surface_t *result_surface;
> +	int captured_frame_count;
> +	bool allow_scaling;
> +	bool allow_yuv;
> +	unsigned int i;
> +	unsigned int fb_id;
> +
> +	switch (check) {
> +	case CHAMELIUM_CHECK_CRC:
> +		allow_scaling = false;
> +		allow_yuv = false;
> +		break;
> +	case CHAMELIUM_CHECK_CHECKERBOARD:
> +		allow_scaling = true;
> +		allow_yuv = true;
> +		break;
> +	default:
> +		igt_assert(false);
> +	}
> +
> +	srand(time(NULL));
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	/* Find the connector and pipe. */
> +	output = chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> +
> +	mode = igt_output_get_mode(output);
> +
> +	/* Get a framebuffer for the primary plane. */
> +	primary_plane =
> +		igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	igt_assert(primary_plane);
> +
> +	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> +					 DRM_FORMAT_XRGB8888, 64, &primary_fb);
> +	igt_assert(fb_id > 0);
> +
> +	/* Get a framebuffer for the cairo composition result. */
> +	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay, mode->vdisplay,
> +			      DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
> +			      &result_fb);
> +	igt_assert(fb_id > 0);
> +
> +	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
> +
> +	/* Paint the primary framebuffer on the result surface. */
> +	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
> +			 &primary_fb);
> +
> +	/* Configure the primary plane. */
> +	igt_plane_set_fb(primary_plane, &primary_fb);
> +
> +	overlay_planes_max =
> +		igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY);
> +
> +	/* Limit the number of planes to a reasonable scene. */
> +	overlay_planes_max = min(overlay_planes_max, 4u);
> +
> +	overlay_planes_count = (rand() % overlay_planes_max) + 1;
> +	igt_debug("Using %d overlay planes\n", overlay_planes_count);
> +
> +	overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count);
> +
> +	for (i = 0; i < overlay_planes_count; i++) {
> +		struct igt_fb *overlay_fb = &overlay_fbs[i];
> +		igt_plane_t *plane = igt_output_get_plane_type_index(
> +			output, DRM_PLANE_TYPE_OVERLAY, i);
> +		igt_assert(plane);
> +
> +		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
> +					 result_surface, allow_scaling,
> +					 allow_yuv);
> +	}
> +
> +	cairo_surface_destroy(result_surface);
> +
> +	if (check == CHAMELIUM_CHECK_CRC)
> +		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> +								&result_fb);
> +
> +	igt_display_commit2(&data->display, COMMIT_ATOMIC);
> +
> +	if (check == CHAMELIUM_CHECK_CRC) {
> +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1);
> +		crc = chamelium_read_captured_crcs(data->chamelium,
> +						   &captured_frame_count);
> +
> +		igt_assert(captured_frame_count == 1);
> +
> +		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> +
> +		chamelium_assert_crc_eq_or_dump(data->chamelium, expected_crc,
> +						crc, &result_fb, 0);
> +
> +		free(expected_crc);
> +		free(crc);
> +	} else if (check == CHAMELIUM_CHECK_CHECKERBOARD) {
> +		struct chamelium_frame_dump *dump;
> +
> +		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> +						  0, 0);
> +		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> +						     dump, &result_fb, check);
> +		chamelium_destroy_frame_dump(dump);
> +	}
> +
> +	for (i = 0; i < overlay_planes_count; i++)
> +		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
> +
> +	free(overlay_fbs);
> +
> +	igt_remove_fb(data->drm_fd, &primary_fb);
> +	igt_remove_fb(data->drm_fd, &result_fb);
> +}
> +
> +IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board");
> +igt_main
> +{
> +	chamelium_data_t data;
> +	struct chamelium_port *port;
> +	int p;
> +
> +	igt_fixture {
> +		chamelium_init_test(&data);
> +	}
> +
> +	igt_describe("DisplayPort tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_display_all_modes_desc);
> +		connector_subtest("dp-crc-single", DisplayPort)
> +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> +					       CHAMELIUM_CHECK_CRC, 1);
> +
> +		igt_describe(test_display_one_mode_desc);
> +		connector_subtest("dp-crc-fast", DisplayPort)
> +			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> +					      CHAMELIUM_CHECK_CRC, 1);
> +
> +		igt_describe(test_display_all_modes_desc);
> +		connector_subtest("dp-crc-multiple", DisplayPort)
> +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> +					       CHAMELIUM_CHECK_CRC, 3);
> +
> +		igt_describe(test_display_frame_dump_desc);
> +		connector_subtest("dp-frame-dump", DisplayPort)
> +			test_display_frame_dump(&data, port);
> +	}
> +
> +	igt_describe("HDMI tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_display_all_modes_desc);
> +		connector_subtest("hdmi-crc-single", HDMIA)
> +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> +					       CHAMELIUM_CHECK_CRC, 1);
> +
> +		igt_describe(test_display_one_mode_desc);
> +		connector_subtest("hdmi-crc-fast", HDMIA)
> +			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> +					      CHAMELIUM_CHECK_CRC, 1);
> +
> +		igt_describe(test_display_all_modes_desc);
> +		connector_subtest("hdmi-crc-multiple", HDMIA)
> +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> +					       CHAMELIUM_CHECK_CRC, 3);
> +
> +		igt_describe(test_display_one_mode_desc);
> +		connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA)
> +		{
> +			int k;
> +			igt_output_t *output;
> +			igt_plane_t *primary;
> +
> +			output = chamelium_prepare_output(&data, port,
> +							  IGT_CUSTOM_EDID_BASE);
> +			primary = igt_output_get_plane_type(
> +				output, DRM_PLANE_TYPE_PRIMARY);
> +			igt_assert(primary);
> +
> +			for (k = 0; k < primary->format_mod_count; k++) {
> +				if (!igt_fb_supported_format(
> +					    primary->formats[k]))
> +					continue;
> +
> +				if (igt_format_is_yuv(primary->formats[k]))
> +					continue;
> +
> +				if (primary->modifiers[k] !=
> +				    DRM_FORMAT_MOD_LINEAR)
> +					continue;
> +
> +				igt_dynamic_f(
> +					"%s",
> +					igt_format_str(primary->formats[k]))
> +					test_display_one_mode(
> +						&data, port,
> +						primary->formats[k],
> +						CHAMELIUM_CHECK_CRC, 1);
> +			}
> +		}
> +
> +		igt_describe(test_display_planes_random_desc);
> +		connector_subtest("hdmi-crc-planes-random", HDMIA)
> +			test_display_planes_random(&data, port,
> +						   CHAMELIUM_CHECK_CRC);
> +
> +		igt_describe(test_display_one_mode_desc);
> +		connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA)
> +		{
> +			int k;
> +			igt_output_t *output;
> +			igt_plane_t *primary;
> +
> +			output = chamelium_prepare_output(&data, port,
> +							  IGT_CUSTOM_EDID_BASE);
> +			primary = igt_output_get_plane_type(
> +				output, DRM_PLANE_TYPE_PRIMARY);
> +			igt_assert(primary);
> +
> +			for (k = 0; k < primary->format_mod_count; k++) {
> +				if (!igt_fb_supported_format(
> +					    primary->formats[k]))
> +					continue;
> +
> +				if (!igt_format_is_yuv(primary->formats[k]))
> +					continue;
> +
> +				if (primary->modifiers[k] !=
> +				    DRM_FORMAT_MOD_LINEAR)
> +					continue;
> +
> +				igt_dynamic_f(
> +					"%s",
> +					igt_format_str(primary->formats[k]))
> +					test_display_one_mode(
> +						&data, port,
> +						primary->formats[k],
> +						CHAMELIUM_CHECK_CHECKERBOARD,
> +						1);
> +			}
> +		}
> +
> +		igt_describe(test_display_planes_random_desc);
> +		connector_subtest("hdmi-cmp-planes-random", HDMIA)
> +			test_display_planes_random(
> +				&data, port, CHAMELIUM_CHECK_CHECKERBOARD);
> +
> +		igt_describe(test_display_frame_dump_desc);
> +		connector_subtest("hdmi-frame-dump", HDMIA)
> +			test_display_frame_dump(&data, port);
> +
> +		igt_describe(test_display_aspect_ratio_desc);
> +		connector_subtest("hdmi-aspect-ratio", HDMIA)
> +			test_display_aspect_ratio(&data, port);
> +	}
> +
> +	igt_describe("VGA tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_VGA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_display_all_modes_desc);
> +		connector_subtest("vga-frame-dump", VGA)
> +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> +					       CHAMELIUM_CHECK_ANALOG, 1);
> +	}
> +
> +	igt_fixture {
> +		igt_display_fini(&data.display);
> +		close(data.drm_fd);
> +	}
> +}
> diff --git a/tests/chamelium/kms_chamelium_helper.c b/tests/chamelium/kms_chamelium_helper.c
> new file mode 100644
> index 00000000..b9544288
> --- /dev/null
> +++ b/tests/chamelium/kms_chamelium_helper.c
> @@ -0,0 +1,330 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * A helper library for all Chamelium tests.
> + *
> + * Copyright 2022 Google LLC.
> + *
> + * Authors: Mark Yacoub <markyacoub@chromium.org>
> + */
> +
> +#include "igt_edid.h"
> +#include "kms_chamelium_helper.h"
> +
> +void chamelium_init_test(chamelium_data_t *data)
> +{
> +	int i;
> +
> +	/* So fbcon doesn't try to reprobe things itself */
> +	kmstest_set_vt_graphics_mode();
> +
> +	data->drm_fd = drm_open_driver_master(DRIVER_ANY);
> +	igt_display_require(&data->display, data->drm_fd);
> +	igt_require(data->display.is_atomic);
> +
> +	/*
> +	 * XXX: disabling modeset, can be removed when
> +	 * igt_display_require will start doing this for us
> +	 */
> +	igt_display_commit2(&data->display, COMMIT_ATOMIC);
> +
> +	/* we need to initalize chamelium after igt_display_require */
> +	data->chamelium = chamelium_init(data->drm_fd, &data->display);
> +	igt_require(data->chamelium);
> +
> +	data->ports = chamelium_get_ports(data->chamelium, &data->port_count);
> +
> +	for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) {
> +		data->edids[i] = chamelium_new_edid(data->chamelium,
> +						    igt_kms_get_custom_edid(i));
> +	}
> +}
> +
> +/* Wait for hotplug and return the remaining time left from timeout */
> +bool chamelium_wait_for_hotplug(struct udev_monitor *mon, int *timeout)
> +{
> +	struct timespec start, end;
> +	int elapsed;
> +	bool detected;
> +
> +	igt_assert_eq(igt_gettime(&start), 0);
> +	detected = igt_hotplug_detected(mon, *timeout);
> +	igt_assert_eq(igt_gettime(&end), 0);
> +
> +	elapsed = igt_time_elapsed(&start, &end);
> +	igt_assert_lte(0, elapsed);
> +	*timeout = max(0, *timeout - elapsed);
> +
> +	return detected;
> +}
> +
> +/**
> + * chamelium_wait_for_connector_after_hotplug:
> + *
> + * Waits for the connector attached to @port to have a status of @status after
> + * it's plugged/unplugged.
> + *
> + */
> +void chamelium_wait_for_connector_after_hotplug(chamelium_data_t *data,
> +						struct udev_monitor *mon,
> +						struct chamelium_port *port,
> +						drmModeConnection status)
> +{
> +	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> +	int hotplug_count = 0;
> +
> +	igt_debug("Waiting for %s to get %s after a hotplug event...\n",
> +		  chamelium_port_get_name(port),
> +		  kmstest_connector_status_str(status));
> +
> +	while (timeout > 0) {
> +		if (!chamelium_wait_for_hotplug(mon, &timeout))
> +			break;
> +
> +		hotplug_count++;
> +
> +		if (chamelium_reprobe_connector(&data->display, data->chamelium,
> +						port) == status)
> +			return;
> +	}
> +
> +	igt_assert_f(
> +		false,
> +		"Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n",
> +		chamelium_port_get_name(port),
> +		kmstest_connector_status_str(status),
> +		kmstest_connector_status_str(chamelium_reprobe_connector(
> +			&data->display, data->chamelium, port)),
> +		hotplug_count);
> +}
> +
> +/**
> + * chamelium_port_get_connector:
> + * @data: The Chamelium data instance to use
> + * @port: The chamelium port to prepare its connector
> + * @edid: The chamelium's default EDID has a lot of resolutions, way more then
> + * 		  we need to test. Additionally the default EDID doesn't support
> + *        HDMI audio.
> + *
> + * Makes sure the output display of the connector attached to @port is connected
> + * and ready for use.
> + *
> + * Returns: a pointer to the enabled igt_output_t
> + */
> +igt_output_t *chamelium_prepare_output(chamelium_data_t *data,
> +				       struct chamelium_port *port,
> +				       enum igt_custom_edid_type edid)
> +{
> +	igt_display_t *display = &data->display;
> +	igt_output_t *output;
> +	enum pipe pipe;
> +
> +	/* The chamelium's default EDID has a lot of resolutions, way more then
> +	 * we need to test. Additionally the default EDID doesn't support HDMI
> +	 * audio.
> +	 */
> +	chamelium_set_edid(data, port, edid);
> +
> +	chamelium_plug(data->chamelium, port);
> +	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> +					      port, DRM_MODE_CONNECTED);
> +
> +	igt_display_reset(display);
> +
> +	output = chamelium_get_output_for_port(data, port);
> +
> +	/* Refresh pipe to update connected status */
> +	igt_output_set_pipe(output, PIPE_NONE);
> +
> +	pipe = chamelium_get_pipe_for_output(display, output);
> +	igt_output_set_pipe(output, pipe);
> +
> +	return output;
> +}
> +
> +/**
> + * chamelium_enable_output:
> + *
> + * Modesets the connector attached to @port for the assigned @mode and draws the
> + * @fb.
> + *
> + */
> +void chamelium_enable_output(chamelium_data_t *data,
> +			     struct chamelium_port *port, igt_output_t *output,
> +			     drmModeModeInfo *mode, struct igt_fb *fb)
> +{
> +	igt_display_t *display = output->display;
> +	igt_plane_t *primary =
> +		igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	drmModeConnector *connector =
> +		chamelium_port_get_connector(data->chamelium, port, false);
> +
> +	igt_assert(primary);
> +
> +	igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
> +	igt_plane_set_fb(primary, fb);
> +	igt_output_override_mode(output, mode);
> +
> +	/* Clear any color correction values that might be enabled */
> +	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT))
> +		igt_pipe_obj_replace_prop_blob(primary->pipe,
> +					       IGT_CRTC_DEGAMMA_LUT, NULL, 0);
> +	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT))
> +		igt_pipe_obj_replace_prop_blob(primary->pipe,
> +					       IGT_CRTC_GAMMA_LUT, NULL, 0);
> +	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM))
> +		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM,
> +					       NULL, 0);
> +
> +	igt_display_commit2(display, COMMIT_ATOMIC);
> +
> +	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA)
> +		usleep(250000);
> +
> +	drmModeFreeConnector(connector);
> +}
> +
> +/* Return pipe attached to @outpu.t */
> +enum pipe chamelium_get_pipe_for_output(igt_display_t *display,
> +					igt_output_t *output)
> +{
> +	enum pipe pipe;
> +
> +	for_each_pipe(display, pipe) {
> +		if (igt_pipe_connector_valid(pipe, output)) {
> +			return pipe;
> +		}
> +	}
> +
> +	igt_assert_f(false, "No pipe found for output %s\n",
> +		     igt_output_name(output));
> +}
> +
> +static void chamelium_paint_xr24_pattern(uint32_t *data, size_t width,
> +					 size_t height, size_t stride,
> +					 size_t block_size)
> +{
> +	uint32_t colors[] = { 0xff000000, 0xffff0000, 0xff00ff00, 0xff0000ff,
> +			      0xffffffff };
> +	unsigned i, j;
> +
> +	for (i = 0; i < height; i++)
> +		for (j = 0; j < width; j++)
> +			*(data + i * stride / 4 +
> +			  j) = colors[((j / block_size) + (i / block_size)) % 5];
> +}
> +
> +/**
> + * chamelium_get_pattern_fb:
> + *
> + * Creates an @fb with an xr24 pattern and returns the fb_id.
> + *
> + */
> +int chamelium_get_pattern_fb(chamelium_data_t *data, size_t width,
> +			     size_t height, uint32_t fourcc, size_t block_size,
> +			     struct igt_fb *fb)
> +{
> +	int fb_id;
> +	void *ptr;
> +
> +	igt_assert(fourcc == DRM_FORMAT_XRGB8888);
> +
> +	fb_id = igt_create_fb(data->drm_fd, width, height, fourcc,
> +			      DRM_FORMAT_MOD_LINEAR, fb);
> +	igt_assert(fb_id > 0);
> +
> +	ptr = igt_fb_map_buffer(fb->fd, fb);
> +	igt_assert(ptr);
> +
> +	chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0],
> +				     block_size);
> +	igt_fb_unmap_buffer(fb, ptr);
> +
> +	return fb_id;
> +}
> +
> +/* Generate a simple @fb for the size of @mode. */
> +void chamelium_create_fb_for_mode(chamelium_data_t *data, struct igt_fb *fb,
> +				  drmModeModeInfo *mode)
> +{
> +	int fb_id;
> +
> +	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> +					 DRM_FORMAT_XRGB8888, 64, fb);
> +
> +	igt_assert(fb_id > 0);
> +}
> +
> +/* Returns the first preferred mode for the connector attached to @port. */
> +drmModeModeInfo chamelium_get_mode_for_port(struct chamelium *chamelium,
> +					    struct chamelium_port *port)
> +{
> +	drmModeConnector *connector =
> +		chamelium_port_get_connector(chamelium, port, false);
> +	drmModeModeInfo mode;
> +	igt_assert(&connector->modes[0] != NULL);
> +	memcpy(&mode, &connector->modes[0], sizeof(mode));
> +	drmModeFreeConnector(connector);
> +	return mode;
> +}
> +
> +/* Returns the igt display output for the connector attached to @port. */
> +igt_output_t *chamelium_get_output_for_port(chamelium_data_t *data,
> +					    struct chamelium_port *port)
> +{
> +	drmModeConnector *connector =
> +		chamelium_port_get_connector(data->chamelium, port, true);
> +	igt_output_t *output =
> +		igt_output_from_connector(&data->display, connector);
> +	drmModeFreeConnector(connector);
> +	igt_assert(output != NULL);
> +	return output;
> +}
> +
> +/* Set the EDID of index @edid to Chamelium's @port. */
> +void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *port,
> +			enum igt_custom_edid_type edid)
> +{
> +	chamelium_port_set_edid(data->chamelium, port, data->edids[edid]);
> +}
> +
> +/**
> + * chamelium_check_analog_bridge:
> + *
> + * Check if the connector associalted to @port is an analog bridge by checking
> + * if it has its own EDID.
> + *
> + */
> +bool chamelium_check_analog_bridge(chamelium_data_t *data,
> +				   struct chamelium_port *port)
> +{
> +	drmModePropertyBlobPtr edid_blob = NULL;
> +	drmModeConnector *connector =
> +		chamelium_port_get_connector(data->chamelium, port, false);
> +	uint64_t edid_blob_id;
> +	const struct edid *edid;
> +	char edid_vendor[3];
> +
> +	if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) {
> +		drmModeFreeConnector(connector);
> +		return false;
> +	}
> +
> +	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> +					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> +					&edid_blob_id, NULL));
> +	igt_assert(edid_blob =
> +			   drmModeGetPropertyBlob(data->drm_fd, edid_blob_id));
> +
> +	edid = (const struct edid *)edid_blob->data;
> +	edid_get_mfg(edid, edid_vendor);
> +
> +	drmModeFreePropertyBlob(edid_blob);
> +	drmModeFreeConnector(connector);
> +
> +	/* Analog bridges provide their own EDID */
> +	if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' ||
> +	    edid_vendor[2] != 'T')
> +		return true;
> +
> +	return false;
> +}
> \ No newline at end of file
> diff --git a/tests/chamelium/kms_chamelium_helper.h b/tests/chamelium/kms_chamelium_helper.h
> new file mode 100644
> index 00000000..09fa4829
> --- /dev/null
> +++ b/tests/chamelium/kms_chamelium_helper.h
> @@ -0,0 +1,74 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * A helper library for all Chamelium tests.
> + *
> + * Copyright 2022 Google LLC.
> + *
> + * Authors: Mark Yacoub <markyacoub@chromium.org>
> + */
> +
> +#ifndef TESTS_CHAMELIUM_CHAMELIUM_HELPER_H
> +#define TESTS_CHAMELIUM_CHAMELIUM_HELPER_H
> +
> +#include "igt.h"
> +
> +#define ONLINE_TIMEOUT 20 /* seconds */
> +
> +#define for_each_port(p, port)                                 \
> +	for (p = 0, port = data.ports[p]; p < data.port_count; \
> +	     p++, port = data.ports[p])
> +
> +#define connector_subtest(name__, type__)                           \
> +	igt_subtest(name__)                                         \
> +	for_each_port(p, port) if (chamelium_port_get_type(port) == \
> +				   DRM_MODE_CONNECTOR_##type__)
> +
> +/*
> + * The chamelium data structure is used to store all the information known about
> + * chamelium to run the tests.
> + */
> +typedef struct {
> +	struct chamelium *chamelium;
> +	struct chamelium_port **ports;
> +	igt_display_t display;
> +	int port_count;
> +
> +	int drm_fd;
> +
> +	struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT];
> +} chamelium_data_t;
> +
> +void chamelium_init_test(chamelium_data_t *data);
> +
> +bool chamelium_wait_for_hotplug(struct udev_monitor *mon, int *timeout);
> +void chamelium_wait_for_connector_after_hotplug(chamelium_data_t *data,
> +						struct udev_monitor *mon,
> +						struct chamelium_port *port,
> +						drmModeConnection status);
> +
> +igt_output_t *chamelium_prepare_output(chamelium_data_t *data,
> +				       struct chamelium_port *port,
> +				       enum igt_custom_edid_type edid);
> +void chamelium_enable_output(chamelium_data_t *data,
> +			     struct chamelium_port *port, igt_output_t *output,
> +			     drmModeModeInfo *mode, struct igt_fb *fb);
> +enum pipe chamelium_get_pipe_for_output(igt_display_t *display,
> +					igt_output_t *output);
> +
> +int chamelium_get_pattern_fb(chamelium_data_t *data, size_t width,
> +			     size_t height, uint32_t fourcc, size_t block_size,
> +			     struct igt_fb *fb);
> +void chamelium_create_fb_for_mode(chamelium_data_t *data, struct igt_fb *fb,
> +				  drmModeModeInfo *mode);
> +drmModeModeInfo chamelium_get_mode_for_port(struct chamelium *chamelium,
> +					    struct chamelium_port *port);
> +igt_output_t *chamelium_get_output_for_port(chamelium_data_t *data,
> +					    struct chamelium_port *port);
> +
> +void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *port,
> +			enum igt_custom_edid_type edid);
> +
> +bool chamelium_check_analog_bridge(chamelium_data_t *data,
> +				   struct chamelium_port *port);
> +
> +#endif /* TESTS_CHAMELIUM_CHAMELIUM_HELPER_H */
> diff --git a/tests/chamelium/kms_chamelium_hpd.c b/tests/chamelium/kms_chamelium_hpd.c
> new file mode 100644
> index 00000000..8a4e1aba
> --- /dev/null
> +++ b/tests/chamelium/kms_chamelium_hpd.c
> @@ -0,0 +1,512 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * A Chamelium test for testing the HPD functionality.
> + *
> + * Copyright 2022 Google LLC.
> + *
> + * Authors: Mark Yacoub <markyacoub@chromium.org>
> + */
> +
> +#include "kms_chamelium_helper.h"
> +
> +#define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */
> +#define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */
> +
> +#define HPD_TOGGLE_COUNT_VGA 5
> +#define HPD_TOGGLE_COUNT_DP_HDMI 15
> +#define HPD_TOGGLE_COUNT_FAST 3
> +
> +enum test_modeset_mode {
> +	TEST_MODESET_ON,
> +	TEST_MODESET_ON_OFF,
> +	TEST_MODESET_OFF,
> +};
> +
> +static void try_suspend_resume_hpd(chamelium_data_t *data,
> +				   struct chamelium_port *port,
> +				   enum igt_suspend_state state,
> +				   enum igt_suspend_test test,
> +				   struct udev_monitor *mon, bool connected)
> +{
> +	drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED :
> +						     DRM_MODE_CONNECTED;
> +	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> +	int delay;
> +	int p;
> +
> +	igt_flush_uevents(mon);
> +
> +	delay = igt_get_autoresume_delay(state) * 1000 / 2;
> +
> +	if (port) {
> +		chamelium_schedule_hpd_toggle(data->chamelium, port, delay,
> +					      !connected);
> +	} else {
> +		for (p = 0; p < data->port_count; p++) {
> +			port = data->ports[p];
> +			chamelium_schedule_hpd_toggle(data->chamelium, port,
> +						      delay, !connected);
> +		}
> +
> +		port = NULL;
> +	}
> +
> +	igt_system_suspend_autoresume(state, test);
> +	igt_assert(chamelium_wait_for_hotplug(mon, &timeout));
> +	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> +
> +	if (port) {
> +		igt_assert_eq(chamelium_reprobe_connector(
> +				      &data->display, data->chamelium, port),
> +			      target_state);
> +	} else {
> +		for (p = 0; p < data->port_count; p++) {
> +			drmModeConnection current_state;
> +
> +			port = data->ports[p];
> +			/*
> +			 * There could be as many hotplug events sent by
> +			 * driver as connectors we scheduled an HPD toggle on
> +			 * above, depending on timing. So if we're not seeing
> +			 * the expected connector state try to wait for an HPD
> +			 * event for each connector/port.
> +			 */
> +			current_state = chamelium_reprobe_connector(
> +				&data->display, data->chamelium, port);
> +			if (p > 0 && current_state != target_state) {
> +				igt_assert(chamelium_wait_for_hotplug(
> +					mon, &timeout));
> +				current_state = chamelium_reprobe_connector(
> +					&data->display, data->chamelium, port);
> +			}
> +
> +			igt_assert_eq(current_state, target_state);
> +		}
> +
> +		port = NULL;
> +	}
> +}
> +
> +static const char test_basic_hotplug_desc[] =
> +	"Check that we get uevents and updated connector status on "
> +	"hotplug and unplug";
> +static void test_hotplug(chamelium_data_t *data, struct chamelium_port *port,
> +			 int toggle_count, enum test_modeset_mode modeset_mode)
> +{
> +	int i;
> +	enum pipe pipe;
> +	struct igt_fb fb = { 0 };
> +	drmModeModeInfo mode;
> +	struct udev_monitor *mon = igt_watch_uevents();
> +	igt_output_t *output = chamelium_get_output_for_port(data, port);
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, NULL,
> +			      data->ports, data->port_count);
> +
> +	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> +
> +	for (i = 0; i < toggle_count; i++) {
> +		igt_flush_uevents(mon);
> +
> +		/* Check if we get a sysfs hotplug event */
> +		chamelium_plug(data->chamelium, port);
> +
> +		chamelium_wait_for_connector_after_hotplug(data, mon, port,
> +							   DRM_MODE_CONNECTED);
> +		igt_flush_uevents(mon);
> +
> +		if (modeset_mode == TEST_MODESET_ON_OFF ||
> +		    (modeset_mode == TEST_MODESET_ON && i == 0)) {
> +			if (i == 0) {
> +				/* We can only get mode and pipe once we are
> +				 * connected */
> +				output = chamelium_get_output_for_port(data,
> +								       port);
> +				pipe = chamelium_get_pipe_for_output(
> +					&data->display, output);
> +				mode = chamelium_get_mode_for_port(
> +					data->chamelium, port);
> +				chamelium_create_fb_for_mode(data, &fb, &mode);
> +			}
> +
> +			igt_output_set_pipe(output, pipe);
> +			chamelium_enable_output(data, port, output, &mode, &fb);
> +		}
> +
> +		/* Now check if we get a hotplug from disconnection */
> +		chamelium_unplug(data->chamelium, port);
> +
> +		chamelium_wait_for_connector_after_hotplug(
> +			data, mon, port, DRM_MODE_DISCONNECTED);
> +
> +		igt_flush_uevents(mon);
> +
> +		if (modeset_mode == TEST_MODESET_ON_OFF) {
> +			igt_output_set_pipe(output, PIPE_NONE);
> +			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> +		}
> +	}
> +
> +	igt_cleanup_uevents(mon);
> +	igt_hpd_storm_reset(data->drm_fd);
> +	igt_remove_fb(data->drm_fd, &fb);
> +}
> +
> +static const char test_hotplug_for_each_pipe_desc[] =
> +	"Check that we get uevents and updated connector status on "
> +	"hotplug and unplug for each pipe with valid output";
> +static void test_hotplug_for_each_pipe(chamelium_data_t *data,
> +				       struct chamelium_port *port)
> +{
> +	igt_output_t *output;
> +	enum pipe pipe;
> +	struct udev_monitor *mon = igt_watch_uevents();
> +
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> +	/* Disconnect if any port got connected */
> +	chamelium_unplug(data->chamelium, port);
> +	chamelium_wait_for_connector_after_hotplug(data, mon, port,
> +						   DRM_MODE_DISCONNECTED);
> +
> +	for_each_pipe(&data->display, pipe) {
> +		igt_flush_uevents(mon);
> +		/* Check if we get a sysfs hotplug event */
> +		chamelium_plug(data->chamelium, port);
> +		chamelium_wait_for_connector_after_hotplug(data, mon, port,
> +							   DRM_MODE_CONNECTED);
> +		igt_flush_uevents(mon);
> +		output = chamelium_get_output_for_port(data, port);
> +
> +		/* If pipe is valid for output then set it */
> +		if (igt_pipe_connector_valid(pipe, output)) {
> +			igt_output_set_pipe(output, pipe);
> +			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> +		}
> +
> +		chamelium_unplug(data->chamelium, port);
> +		chamelium_wait_for_connector_after_hotplug(
> +			data, mon, port, DRM_MODE_DISCONNECTED);
> +		igt_flush_uevents(mon);
> +	}
> +
> +	igt_cleanup_uevents(mon);
> +	igt_hpd_storm_reset(data->drm_fd);
> +}
> +
> +static const char test_suspend_resume_hpd_desc[] =
> +	"Toggle HPD during suspend, check that uevents are sent and connector "
> +	"status is updated";
> +static void test_suspend_resume_hpd(chamelium_data_t *data,
> +				    struct chamelium_port *port,
> +				    enum igt_suspend_state state,
> +				    enum igt_suspend_test test)
> +{
> +	struct udev_monitor *mon = igt_watch_uevents();
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	/* Make sure we notice new connectors after resuming */
> +	try_suspend_resume_hpd(data, port, state, test, mon, false);
> +
> +	/* Now make sure we notice disconnected connectors after resuming */
> +	try_suspend_resume_hpd(data, port, state, test, mon, true);
> +
> +	igt_cleanup_uevents(mon);
> +}
> +
> +static const char test_suspend_resume_hpd_common_desc[] =
> +	"Toggle HPD during suspend on all connectors, check that uevents are "
> +	"sent and connector status is updated";
> +static void test_suspend_resume_hpd_common(chamelium_data_t *data,
> +					   enum igt_suspend_state state,
> +					   enum igt_suspend_test test)
> +{
> +	struct udev_monitor *mon = igt_watch_uevents();
> +	struct chamelium_port *port;
> +	int p;
> +
> +	for (p = 0; p < data->port_count; p++) {
> +		port = data->ports[p];
> +		igt_debug("Testing port %s\n", chamelium_port_get_name(port));
> +	}
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, NULL,
> +			      data->ports, data->port_count);
> +
> +	/* Make sure we notice new connectors after resuming */
> +	try_suspend_resume_hpd(data, NULL, state, test, mon, false);
> +
> +	/* Now make sure we notice disconnected connectors after resuming */
> +	try_suspend_resume_hpd(data, NULL, state, test, mon, true);
> +
> +	igt_cleanup_uevents(mon);
> +}
> +
> +static const char test_hpd_without_ddc_desc[] =
> +	"Disable DDC on a VGA connector, check we still get a uevent on hotplug";
> +static void test_hpd_without_ddc(chamelium_data_t *data,
> +				 struct chamelium_port *port)
> +{
> +	struct udev_monitor *mon = igt_watch_uevents();
> +
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +	igt_flush_uevents(mon);
> +
> +	/* Disable the DDC on the connector and make sure we still get a
> +	 * hotplug
> +	 */
> +	chamelium_port_set_ddc_state(data->chamelium, port, false);
> +	chamelium_plug(data->chamelium, port);
> +
> +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> +	igt_assert_eq(chamelium_reprobe_connector(&data->display,
> +						  data->chamelium, port),
> +		      DRM_MODE_CONNECTED);
> +
> +	igt_cleanup_uevents(mon);
> +}
> +
> +static const char test_hpd_storm_detect_desc[] =
> +	"Trigger a series of hotplugs in a very small timeframe to simulate a"
> +	"bad cable, check the kernel falls back to polling to avoid a hotplug "
> +	"storm";
> +static void test_hpd_storm_detect(chamelium_data_t *data,
> +				  struct chamelium_port *port, int width)
> +{
> +	struct udev_monitor *mon;
> +	int count = 0;
> +
> +	igt_require_hpd_storm_ctl(data->drm_fd);
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	igt_hpd_storm_set_threshold(data->drm_fd, 1);
> +	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> +	igt_assert(igt_hpd_storm_detected(data->drm_fd));
> +
> +	mon = igt_watch_uevents();
> +	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> +
> +	/*
> +	 * Polling should have been enabled by the HPD storm at this point,
> +	 * so we should only get at most 1 hotplug event
> +	 */
> +	igt_until_timeout(5)
> +		count += igt_hotplug_detected(mon, 1);
> +	igt_assert_lt(count, 2);
> +
> +	igt_cleanup_uevents(mon);
> +	igt_hpd_storm_reset(data->drm_fd);
> +}
> +
> +static const char test_hpd_storm_disable_desc[] =
> +	"Disable HPD storm detection, trigger a storm and check the kernel "
> +	"doesn't detect one";
> +static void test_hpd_storm_disable(chamelium_data_t *data,
> +				   struct chamelium_port *port, int width)
> +{
> +	igt_require_hpd_storm_ctl(data->drm_fd);
> +	igt_modeset_disable_all_outputs(&data->display);
> +	chamelium_reset_state(&data->display, data->chamelium, port,
> +			      data->ports, data->port_count);
> +
> +	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> +	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> +	igt_assert(!igt_hpd_storm_detected(data->drm_fd));
> +
> +	igt_hpd_storm_reset(data->drm_fd);
> +}
> +
> +IGT_TEST_DESCRIPTION("Testing HPD with a Chamelium board");
> +igt_main
> +{
> +	chamelium_data_t data;
> +	struct chamelium_port *port;
> +	int p;
> +
> +	igt_fixture {
> +		chamelium_init_test(&data);
> +	}
> +
> +	igt_describe("DisplayPort tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("dp-hpd", DisplayPort)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_DP_HDMI,
> +				     TEST_MODESET_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("dp-hpd-fast", DisplayPort) test_hotplug(
> +			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("dp-hpd-enable-disable-mode", DisplayPort)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> +				     TEST_MODESET_ON_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("dp-hpd-with-enabled-mode", DisplayPort)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> +				     TEST_MODESET_ON);
> +
> +		igt_describe(test_hotplug_for_each_pipe_desc);
> +		connector_subtest("dp-hpd-for-each-pipe", DisplayPort)
> +			test_hotplug_for_each_pipe(&data, port);
> +
> +		igt_describe(test_suspend_resume_hpd_desc);
> +		connector_subtest("dp-hpd-after-suspend", DisplayPort)
> +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
> +						SUSPEND_TEST_NONE);
> +
> +		igt_describe(test_suspend_resume_hpd_desc);
> +		connector_subtest("dp-hpd-after-hibernate", DisplayPort)
> +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
> +						SUSPEND_TEST_DEVICES);
> +
> +		igt_describe(test_hpd_storm_detect_desc);
> +		connector_subtest("dp-hpd-storm", DisplayPort)
> +			test_hpd_storm_detect(&data, port,
> +					      HPD_STORM_PULSE_INTERVAL_DP);
> +
> +		igt_describe(test_hpd_storm_disable_desc);
> +		connector_subtest("dp-hpd-storm-disable", DisplayPort)
> +			test_hpd_storm_disable(&data, port,
> +					       HPD_STORM_PULSE_INTERVAL_DP);
> +	}
> +
> +	igt_describe("HDMI tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("hdmi-hpd", HDMIA)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_DP_HDMI,
> +				     TEST_MODESET_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("hdmi-hpd-fast", HDMIA) test_hotplug(
> +			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> +				     TEST_MODESET_ON_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> +				     TEST_MODESET_ON);
> +
> +		igt_describe(test_hotplug_for_each_pipe_desc);
> +		connector_subtest("hdmi-hpd-for-each-pipe", HDMIA)
> +			test_hotplug_for_each_pipe(&data, port);
> +
> +		igt_describe(test_suspend_resume_hpd_desc);
> +		connector_subtest("hdmi-hpd-after-suspend", HDMIA)
> +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
> +						SUSPEND_TEST_NONE);
> +
> +		igt_describe(test_suspend_resume_hpd_desc);
> +		connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
> +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
> +						SUSPEND_TEST_DEVICES);
> +
> +		igt_describe(test_hpd_storm_detect_desc);
> +		connector_subtest("hdmi-hpd-storm", HDMIA)
> +			test_hpd_storm_detect(&data, port,
> +					      HPD_STORM_PULSE_INTERVAL_HDMI);
> +
> +		igt_describe(test_hpd_storm_disable_desc);
> +		connector_subtest("hdmi-hpd-storm-disable", HDMIA)
> +			test_hpd_storm_disable(&data, port,
> +					       HPD_STORM_PULSE_INTERVAL_HDMI);
> +	}
> +
> +	igt_describe("VGA tests");
> +	igt_subtest_group {
> +		igt_fixture {
> +			chamelium_require_connector_present(
> +				data.ports, DRM_MODE_CONNECTOR_VGA,
> +				data.port_count, 1);
> +		}
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("vga-hpd", VGA) test_hotplug(
> +			&data, port, HPD_TOGGLE_COUNT_VGA, TEST_MODESET_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("vga-hpd-fast", VGA) test_hotplug(
> +			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("vga-hpd-enable-disable-mode", VGA)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> +				     TEST_MODESET_ON_OFF);
> +
> +		igt_describe(test_basic_hotplug_desc);
> +		connector_subtest("vga-hpd-with-enabled-mode", VGA)
> +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> +				     TEST_MODESET_ON);
> +
> +		igt_describe(test_suspend_resume_hpd_desc);
> +		connector_subtest("vga-hpd-after-suspend", VGA)
> +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
> +						SUSPEND_TEST_NONE);
> +
> +		igt_describe(test_suspend_resume_hpd_desc);
> +		connector_subtest("vga-hpd-after-hibernate", VGA)
> +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
> +						SUSPEND_TEST_DEVICES);
> +
> +		igt_describe(test_hpd_without_ddc_desc);
> +		connector_subtest("vga-hpd-without-ddc", VGA)
> +			test_hpd_without_ddc(&data, port);
> +	}
> +
> +	igt_describe("Tests that operate on all connectors");
> +	igt_subtest_group {
> +		igt_fixture {
> +			igt_require(data.port_count);
> +		}
> +
> +		igt_describe(test_suspend_resume_hpd_common_desc);
> +		igt_subtest("common-hpd-after-suspend")
> +			test_suspend_resume_hpd_common(&data, SUSPEND_STATE_MEM,
> +						       SUSPEND_TEST_NONE);
> +
> +		igt_describe(test_suspend_resume_hpd_common_desc);
> +		igt_subtest("common-hpd-after-hibernate")
> +			test_suspend_resume_hpd_common(&data,
> +						       SUSPEND_STATE_DISK,
> +						       SUSPEND_TEST_DEVICES);
> +	}
> +
> +	igt_describe(test_hotplug_for_each_pipe_desc);
> +	connector_subtest("vga-hpd-for-each-pipe", VGA)
> +		test_hotplug_for_each_pipe(&data, port);
> +
> +	igt_fixture {
> +		igt_display_fini(&data.display);
> +		close(data.drm_fd);
> +	}
> +}
> diff --git a/tests/intel-ci/blacklist.txt b/tests/intel-ci/blacklist.txt
> index 0d307730..6e5cc436 100644
> --- a/tests/intel-ci/blacklist.txt
> +++ b/tests/intel-ci/blacklist.txt
> @@ -77,7 +77,7 @@ igt@kms_frontbuffer_tracking@.*drrs.*
>  # is too costly in comparison to the value
>  # provided.
>  ###############################################
> -igt@kms_chamelium@hdmi-.*-planes-random
> +igt@kms_chamelium_frames@hdmi-.*-planes-random
>  ###############################################
>  # Broadcom
>  ###############################################
> diff --git a/tests/intel-ci/fast-feedback.testlist b/tests/intel-ci/fast-feedback.testlist
> index f57f8ff3..fb4c0f73 100644
> --- a/tests/intel-ci/fast-feedback.testlist
> +++ b/tests/intel-ci/fast-feedback.testlist
> @@ -92,14 +92,14 @@ igt@kms_addfb_basic@unused-modifier
>  igt@kms_addfb_basic@unused-offsets
>  igt@kms_addfb_basic@unused-pitches
>  igt@kms_busy@basic
> -igt@kms_chamelium@dp-hpd-fast
> -igt@kms_chamelium@dp-edid-read
> -igt@kms_chamelium@dp-crc-fast
> -igt@kms_chamelium@hdmi-hpd-fast
> -igt@kms_chamelium@hdmi-edid-read
> -igt@kms_chamelium@hdmi-crc-fast
> -igt@kms_chamelium@vga-hpd-fast
> -igt@kms_chamelium@vga-edid-read
> +igt@kms_chamelium_hpd@dp-hpd-fast
> +igt@kms_chamelium_edid@dp-edid-read
> +igt@kms_chamelium_frames@dp-crc-fast
> +igt@kms_chamelium_hpd@hdmi-hpd-fast
> +igt@kms_chamelium_edid@hdmi-edid-read
> +igt@kms_chamelium_frames@hdmi-crc-fast
> +igt@kms_chamelium_hpd@vga-hpd-fast
> +igt@kms_chamelium_edid@vga-edid-read
>  igt@kms_prop_blob@basic
>  igt@kms_cursor_legacy@basic-busy-flip-before-cursor
>  igt@kms_cursor_legacy@basic-flip-after-cursor
> @@ -174,5 +174,5 @@ igt@i915_suspend@basic-s2idle-without-i915
>  igt@i915_suspend@basic-s3-without-i915
>  igt@gem_exec_suspend@basic-s0
>  igt@gem_exec_suspend@basic-s3
> -igt@kms_chamelium@common-hpd-after-suspend
> +igt@kms_chamelium_hpd@common-hpd-after-suspend
>  igt@kms_pipe_crc_basic@suspend-read-crc
> diff --git a/tests/kms_color_helper.h b/tests/kms_color_helper.h
> index f0ae30e3..f9242232 100644
> --- a/tests/kms_color_helper.h
> +++ b/tests/kms_color_helper.h
> @@ -27,7 +27,7 @@
>  
>  /*
>   * This header is for code that is shared between kms_color.c and
> - * kms_color_chamelium.c. Reusability elsewhere can be questionable.
> + * kms_chamelium_color.c. Reusability elsewhere can be questionable.
>   */
>  
>  #include <math.h>
> diff --git a/tests/meson.build b/tests/meson.build
> index 5c052e73..b52399d5 100644
> --- a/tests/meson.build
> +++ b/tests/meson.build
> @@ -260,7 +260,10 @@ msm_progs = [
>  ]
>  
>  chamelium_progs = [
> -	'kms_chamelium',
> +	'kms_chamelium_audio',
> +	'kms_chamelium_edid',
> +	'kms_chamelium_frames',
> +	'kms_chamelium_hpd',
>  ]
>  
>  test_deps = [ igt_deps ]
> @@ -309,7 +312,8 @@ endforeach
>  if chamelium.found()
>  	foreach prog : chamelium_progs
>  		test_executables += executable(prog,
> -				 join_paths('chamelium', prog + '.c'),
> +				 [join_paths('chamelium', prog + '.c'), 
> +				 	join_paths('chamelium', 'kms_chamelium_helper.c')],
>  				 dependencies : test_deps,
>  				 install_dir : libexecdir,
>  				 install_rpath : libexecdir_rpathdir,
> @@ -436,13 +440,13 @@ test_executables += executable('kms_color',
>  test_list += 'kms_color'
>  
>  if chamelium.found()
> -       test_executables += executable('kms_color_chamelium',
> -                             [ 'chamelium/kms_color_chamelium.c', 'kms_color_helper.c' ],
> +       test_executables += executable('kms_chamelium_color',
> +                             [ 'chamelium/kms_chamelium_color.c', 'kms_color_helper.c' ],
>                               dependencies : test_deps + [ chamelium ],
>                               install_dir : libexecdir,
>                               install_rpath : libexecdir_rpathdir,
>                               install : true)
> -       test_list += 'kms_color_chamelium'
> +       test_list += 'kms_chamelium_color'
>  endif
>  
>  test_executables += executable('sw_sync', 'sw_sync.c',
> diff --git a/tests/vc4_ci/vc4-chamelium-fast.testlist b/tests/vc4_ci/vc4-chamelium-fast.testlist
> index dd45d12a..a5521021 100644
> --- a/tests/vc4_ci/vc4-chamelium-fast.testlist
> +++ b/tests/vc4_ci/vc4-chamelium-fast.testlist
> @@ -1,14 +1,14 @@
> -igt@kms_chamelium@hdmi-crc-abgr8888
> -igt@kms_chamelium@hdmi-crc-argb1555
> -igt@kms_chamelium@hdmi-crc-argb8888
> -igt@kms_chamelium@hdmi-crc-bgr565
> -igt@kms_chamelium@hdmi-crc-bgr888
> -igt@kms_chamelium@hdmi-crc-fast
> -igt@kms_chamelium@hdmi-crc-rgb565
> -igt@kms_chamelium@hdmi-crc-rgb888
> -igt@kms_chamelium@hdmi-crc-xbgr8888
> -igt@kms_chamelium@hdmi-crc-xrgb1555
> -igt@kms_chamelium@hdmi-crc-xrgb8888
> -igt@kms_chamelium@hdmi-edid-read
> -igt@kms_chamelium@hdmi-hpd
> -igt@kms_chamelium@hdmi-hpd-fast
> +igt@kms_chamelium_frames@hdmi-crc-abgr8888
> +igt@kms_chamelium_frames@hdmi-crc-argb1555
> +igt@kms_chamelium_frames@hdmi-crc-argb8888
> +igt@kms_chamelium_frames@hdmi-crc-bgr565
> +igt@kms_chamelium_frames@hdmi-crc-bgr888
> +igt@kms_chamelium_frames@hdmi-crc-fast
> +igt@kms_chamelium_frames@hdmi-crc-rgb565
> +igt@kms_chamelium_frames@hdmi-crc-rgb888
> +igt@kms_chamelium_frames@hdmi-crc-xbgr8888
> +igt@kms_chamelium_frames@hdmi-crc-xrgb1555
> +igt@kms_chamelium_frames@hdmi-crc-xrgb8888
> +igt@kms_chamelium_edid@hdmi-edid-read
> +igt@kms_chamelium_hpd@hdmi-hpd
> +igt@kms_chamelium_hpd@hdmi-hpd-fast

-- 
Jani Nikula, Intel Open Source Graphics Center

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI
  2022-12-30 12:51 ` [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Jani Nikula
@ 2023-01-02 16:50   ` Kamil Konieczny
  0 siblings, 0 replies; 5+ messages in thread
From: Kamil Konieczny @ 2023-01-02 16:50 UTC (permalink / raw)
  To: igt-dev; +Cc: Mark Yacoub

Hi,

On 2022-12-30 at 14:51:13 +0200, Jani Nikula wrote:
> On Thu, 29 Dec 2022, Mark Yacoub <markyacoub@chromium.org> wrote:
> > From: Mark Yacoub <markyacoub@google.com>
> 
> A commit message would be appreciated to let us know what you're doing,
> regardless of the "DO NOT REVIEW" part.
> 
> Thanks,
> Jani.
> 

There is even better solution, just use igt-trybot
intel-gfx-trybot@lists.freedesktop.org

Example: https://patchwork.freedesktop.org/series/112048/

Regards,
Kamil

> >
> > ---
> >  docs/chamelium.txt                            |    2 +-
> >  lib/igt_edid.h                                |    1 +
> >  lib/igt_eld.h                                 |    1 +
> >  lib/monitor_edids/monitor_edids_helper.c      |    2 +-
> >  tests/chamelium/kms_chamelium.c               | 3132 -----------------
> >  tests/chamelium/kms_chamelium_audio.c         |  858 +++++
> >  ...olor_chamelium.c => kms_chamelium_color.c} |    0
> >  tests/chamelium/kms_chamelium_edid.c          |  543 +++
> >  tests/chamelium/kms_chamelium_frames.c        | 1085 ++++++
> >  tests/chamelium/kms_chamelium_helper.c        |  330 ++
> >  tests/chamelium/kms_chamelium_helper.h        |   74 +
> >  tests/chamelium/kms_chamelium_hpd.c           |  512 +++
> >  tests/intel-ci/blacklist.txt                  |    2 +-
> >  tests/intel-ci/fast-feedback.testlist         |   18 +-
> >  tests/kms_color_helper.h                      |    2 +-
> >  tests/meson.build                             |   14 +-
> >  tests/vc4_ci/vc4-chamelium-fast.testlist      |   28 +-
> >  17 files changed, 3440 insertions(+), 3164 deletions(-)
> >  delete mode 100644 tests/chamelium/kms_chamelium.c
> >  create mode 100644 tests/chamelium/kms_chamelium_audio.c
> >  rename tests/chamelium/{kms_color_chamelium.c => kms_chamelium_color.c} (100%)
> >  create mode 100644 tests/chamelium/kms_chamelium_edid.c
> >  create mode 100644 tests/chamelium/kms_chamelium_frames.c
> >  create mode 100644 tests/chamelium/kms_chamelium_helper.c
> >  create mode 100644 tests/chamelium/kms_chamelium_helper.h
> >  create mode 100644 tests/chamelium/kms_chamelium_hpd.c
> >
> > diff --git a/docs/chamelium.txt b/docs/chamelium.txt
> > index c4c22468..f82c8b0c 100644
> > --- a/docs/chamelium.txt
> > +++ b/docs/chamelium.txt
> > @@ -241,7 +241,7 @@ Current Support in IGT
> >  
> >  Support for the Chamelium platform in IGT is found in the following places:
> >  * lib/igt_chamelium.c: library with Chamelium-related helpers
> > -* tests/kms_chamelium.c: sub-tests using the Chamelium
> > +* tests/kms_chamelium_*.c: sub-tests using the Chamelium
> >  
> >  As of early April 2019, the following features are tested by IGT:
> >  * Pixel-by-pixel frame integrity tests for DP and HDMI
> > diff --git a/lib/igt_edid.h b/lib/igt_edid.h
> > index 477f16c2..85a9ef5e 100644
> > --- a/lib/igt_edid.h
> > +++ b/lib/igt_edid.h
> > @@ -29,6 +29,7 @@
> >  #include "config.h"
> >  
> >  #include <stdint.h>
> > +#include <stddef.h>
> >  
> >  #include <xf86drmMode.h>
> >  
> > diff --git a/lib/igt_eld.h b/lib/igt_eld.h
> > index 30d7012d..1a46b6d2 100644
> > --- a/lib/igt_eld.h
> > +++ b/lib/igt_eld.h
> > @@ -29,6 +29,7 @@
> >  #include "config.h"
> >  
> >  #include <stdbool.h>
> > +#include <stddef.h>
> >  
> >  #include "igt_edid.h"
> >  
> > diff --git a/lib/monitor_edids/monitor_edids_helper.c b/lib/monitor_edids/monitor_edids_helper.c
> > index 41f199bd..1cbf1c22 100644
> > --- a/lib/monitor_edids/monitor_edids_helper.c
> > +++ b/lib/monitor_edids/monitor_edids_helper.c
> > @@ -1,4 +1,4 @@
> > -// SPDX-License-Identifier: GPL-2.0
> > +// SPDX-License-Identifier: MIT
> >  /*
> >   * A helper library for parsing and making use of real EDID data from monitors
> >   * and make them compatible with IGT and Chamelium.
> > diff --git a/tests/chamelium/kms_chamelium.c b/tests/chamelium/kms_chamelium.c
> > deleted file mode 100644
> > index 3c4b4d75..00000000
> > --- a/tests/chamelium/kms_chamelium.c
> > +++ /dev/null
> > @@ -1,3132 +0,0 @@
> > -/*
> > - * Copyright © 2016 Red Hat 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 (including the next
> > - * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
> > - *
> > - * Authors:
> > - *    Lyude Paul <lyude@redhat.com>
> > - */
> > -
> > -#include "config.h"
> > -#include "igt.h"
> > -#include "igt_vc4.h"
> > -#include "igt_edid.h"
> > -#include "igt_eld.h"
> > -#include "igt_infoframe.h"
> > -#include "monitor_edids/dp_edids.h"
> > -#include "monitor_edids/hdmi_edids.h"
> > -#include "monitor_edids/monitor_edids_helper.h"
> > -
> > -#include <fcntl.h>
> > -#include <pthread.h>
> > -#include <string.h>
> > -#include <stdatomic.h>
> > -// #include <stdio.h>
> > -
> > -// struct chamelium_edid;
> > -
> > -enum test_modeset_mode {
> > -	TEST_MODESET_ON,
> > -	TEST_MODESET_ON_OFF,
> > -	TEST_MODESET_OFF,
> > -};
> > -
> > -typedef struct {
> > -	struct chamelium *chamelium;
> > -	struct chamelium_port **ports;
> > -	igt_display_t display;
> > -	int port_count;
> > -
> > -	int drm_fd;
> > -
> > -	struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT];
> > -} data_t;
> > -
> > -#define ONLINE_TIMEOUT 20 /* seconds */
> > -
> > -#define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */
> > -#define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */
> > -
> > -#define HPD_TOGGLE_COUNT_VGA 5
> > -#define HPD_TOGGLE_COUNT_DP_HDMI 15
> > -#define HPD_TOGGLE_COUNT_FAST 3
> > -
> > -static void
> > -get_connectors_link_status_failed(data_t *data, bool *link_status_failed)
> > -{
> > -	drmModeConnector *connector;
> > -	uint64_t link_status;
> > -	drmModePropertyPtr prop;
> > -	int p;
> > -
> > -	for (p = 0; p < data->port_count; p++) {
> > -		connector = chamelium_port_get_connector(data->chamelium,
> > -							 data->ports[p], false);
> > -
> > -		igt_assert(kmstest_get_property(data->drm_fd,
> > -						connector->connector_id,
> > -						DRM_MODE_OBJECT_CONNECTOR,
> > -						"link-status", NULL,
> > -						&link_status, &prop));
> > -
> > -		link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD;
> > -
> > -		drmModeFreeProperty(prop);
> > -		drmModeFreeConnector(connector);
> > -	}
> > -}
> > -
> > -/* Wait for hotplug and return the remaining time left from timeout */
> > -static bool wait_for_hotplug(struct udev_monitor *mon, int *timeout)
> > -{
> > -	struct timespec start, end;
> > -	int elapsed;
> > -	bool detected;
> > -
> > -	igt_assert_eq(igt_gettime(&start), 0);
> > -	detected = igt_hotplug_detected(mon, *timeout);
> > -	igt_assert_eq(igt_gettime(&end), 0);
> > -
> > -	elapsed = igt_time_elapsed(&start, &end);
> > -	igt_assert_lte(0, elapsed);
> > -	*timeout = max(0, *timeout - elapsed);
> > -
> > -	return detected;
> > -}
> > -
> > -static void
> > -wait_for_connector_after_hotplug(data_t *data, struct udev_monitor *mon,
> > -				 struct chamelium_port *port,
> > -				 drmModeConnection status)
> > -{
> > -	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> > -	int hotplug_count = 0;
> > -
> > -	igt_debug("Waiting for %s to get %s after a hotplug event...\n",
> > -			  chamelium_port_get_name(port),
> > -			  kmstest_connector_status_str(status));
> > -
> > -	while (timeout > 0) {
> > -		if (!wait_for_hotplug(mon, &timeout))
> > -			break;
> > -
> > -		hotplug_count++;
> > -
> > -		if (chamelium_reprobe_connector(&data->display, data->chamelium,
> > -						port) == status)
> > -			return;
> > -	}
> > -
> > -	igt_assert_f(false, "Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n",
> > -			    chamelium_port_get_name(port),
> > -			    kmstest_connector_status_str(status),
> > -			    kmstest_connector_status_str(chamelium_reprobe_connector(&data->display, data->chamelium, port)), hotplug_count);
> > -}
> > -
> > -
> > -static int chamelium_vga_modes[][2] = {
> > -	{ 1600, 1200 },
> > -	{ 1920, 1200 },
> > -	{ 1920, 1080 },
> > -	{ 1680, 1050 },
> > -	{ 1280, 1024 },
> > -	{ 1280, 960 },
> > -	{ 1440, 900 },
> > -	{ 1280, 800 },
> > -	{ 1024, 768 },
> > -	{ 1360, 768 },
> > -	{ 1280, 720 },
> > -	{ 800, 600 },
> > -	{ 640, 480 },
> > -	{ -1, -1 },
> > -};
> > -
> > -static bool
> > -prune_vga_mode(data_t *data, drmModeModeInfo *mode)
> > -{
> > -	int i = 0;
> > -
> > -	while (chamelium_vga_modes[i][0] != -1) {
> > -		if (mode->hdisplay == chamelium_vga_modes[i][0] &&
> > -		    mode->vdisplay == chamelium_vga_modes[i][1])
> > -			return false;
> > -
> > -		i++;
> > -	}
> > -
> > -	return true;
> > -}
> > -
> > -static bool
> > -check_analog_bridge(data_t *data, struct chamelium_port *port)
> > -{
> > -	drmModePropertyBlobPtr edid_blob = NULL;
> > -	drmModeConnector *connector = chamelium_port_get_connector(
> > -	    data->chamelium, port, false);
> > -	uint64_t edid_blob_id;
> > -	const struct edid *edid;
> > -	char edid_vendor[3];
> > -
> > -	if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) {
> > -		drmModeFreeConnector(connector);
> > -		return false;
> > -	}
> > -
> > -	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> > -					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> > -					&edid_blob_id, NULL));
> > -	igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
> > -						      edid_blob_id));
> > -
> > -	edid = (const struct edid *) edid_blob->data;
> > -	edid_get_mfg(edid, edid_vendor);
> > -
> > -	drmModeFreePropertyBlob(edid_blob);
> > -	drmModeFreeConnector(connector);
> > -
> > -	/* Analog bridges provide their own EDID */
> > -	if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' ||
> > -	    edid_vendor[2] != 'T')
> > -		return true;
> > -
> > -	return false;
> > -}
> > -
> > -static void chamelium_paint_xr24_pattern(uint32_t *data,
> > -					 size_t width, size_t height,
> > -					 size_t stride, size_t block_size)
> > -{
> > -	uint32_t colors[] = { 0xff000000,
> > -			      0xffff0000,
> > -			      0xff00ff00,
> > -			      0xff0000ff,
> > -			      0xffffffff };
> > -	unsigned i, j;
> > -
> > -	for (i = 0; i < height; i++)
> > -		for (j = 0; j < width; j++)
> > -			*(data + i * stride / 4 + j) = colors[((j / block_size) + (i / block_size)) % 5];
> > -}
> > -
> > -static int chamelium_get_pattern_fb(data_t *data, size_t width, size_t height,
> > -				    uint32_t fourcc, size_t block_size,
> > -				    struct igt_fb *fb)
> > -{
> > -	int fb_id;
> > -	void *ptr;
> > -
> > -	igt_assert(fourcc == DRM_FORMAT_XRGB8888);
> > -
> > -	fb_id = igt_create_fb(data->drm_fd, width, height, fourcc,
> > -			      DRM_FORMAT_MOD_LINEAR, fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	ptr = igt_fb_map_buffer(fb->fd, fb);
> > -	igt_assert(ptr);
> > -
> > -	chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0],
> > -				     block_size);
> > -	igt_fb_unmap_buffer(fb, ptr);
> > -
> > -	return fb_id;
> > -}
> > -
> > -static void
> > -enable_output(data_t *data,
> > -	      struct chamelium_port *port,
> > -	      igt_output_t *output,
> > -	      drmModeModeInfo *mode,
> > -	      struct igt_fb *fb)
> > -{
> > -	igt_display_t *display = output->display;
> > -	igt_plane_t *primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -	drmModeConnector *connector = chamelium_port_get_connector(
> > -	    data->chamelium, port, false);
> > -
> > -	igt_assert(primary);
> > -
> > -	igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
> > -	igt_plane_set_fb(primary, fb);
> > -	igt_output_override_mode(output, mode);
> > -
> > -	/* Clear any color correction values that might be enabled */
> > -	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT))
> > -		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_DEGAMMA_LUT, NULL, 0);
> > -	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT))
> > -		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_GAMMA_LUT, NULL, 0);
> > -	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM))
> > -		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM, NULL, 0);
> > -
> > -	igt_display_commit2(display, COMMIT_ATOMIC);
> > -
> > -	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA)
> > -		usleep(250000);
> > -
> > -	drmModeFreeConnector(connector);
> > -}
> > -
> > -static enum pipe get_pipe_for_output(igt_display_t *display, igt_output_t *output)
> > -{
> > -	enum pipe pipe;
> > -
> > -	for_each_pipe(display, pipe) {
> > -		if (igt_pipe_connector_valid(pipe, output)) {
> > -			return pipe;
> > -		}
> > -	}
> > -
> > -	igt_assert_f(false, "No pipe found for output %s\n",
> > -		     igt_output_name(output));
> > -}
> > -
> > -static void create_fb_for_mode(data_t *data, struct igt_fb *fb, drmModeModeInfo *mode)
> > -{
> > -	int fb_id;
> > -
> > -	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> > -					 DRM_FORMAT_XRGB8888, 64, fb);
> > -
> > -	igt_assert(fb_id > 0);
> > -}
> > -
> > -static drmModeModeInfo get_mode_for_port(struct chamelium *chamelium,
> > -					 struct chamelium_port *port)
> > -{
> > -	drmModeConnector *connector = chamelium_port_get_connector(chamelium,
> > -								   port, false);
> > -	drmModeModeInfo mode;
> > -	igt_assert(&connector->modes[0] != NULL);
> > -	memcpy(&mode, &connector->modes[0], sizeof(mode));
> > -	drmModeFreeConnector(connector);
> > -	return mode;
> > -}
> > -
> > -static igt_output_t *get_output_for_port(data_t *data,
> > -					 struct chamelium_port *port)
> > -{
> > -	drmModeConnector *connector =
> > -		chamelium_port_get_connector(data->chamelium, port, true);
> > -	igt_output_t *output = igt_output_from_connector(&data->display,
> > -							 connector);
> > -	drmModeFreeConnector(connector);
> > -	igt_assert(output != NULL);
> > -	return output;
> > -}
> > -
> > -static const char test_hotplug_for_each_pipe_desc[] =
> > -	"Check that we get uevents and updated connector status on "
> > -	"hotplug and unplug for each pipe with valid output";
> > -static void
> > -test_hotplug_for_each_pipe(data_t *data, struct chamelium_port *port)
> > -{
> > -	igt_output_t *output;
> > -	enum pipe pipe;
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -
> > -	chamelium_reset_state(&data->display,
> > -			      data->chamelium,
> > -			      port,
> > -			      data->ports,
> > -			      data->port_count);
> > -
> > -	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> > -	/* Disconnect if any port got connected */
> > -	chamelium_unplug(data->chamelium, port);
> > -	wait_for_connector_after_hotplug(data, mon, port,
> > -			DRM_MODE_DISCONNECTED);
> > -
> > -	for_each_pipe(&data->display, pipe) {
> > -		igt_flush_uevents(mon);
> > -		/* Check if we get a sysfs hotplug event */
> > -		chamelium_plug(data->chamelium, port);
> > -		wait_for_connector_after_hotplug(data, mon, port,
> > -				DRM_MODE_CONNECTED);
> > -		igt_flush_uevents(mon);
> > -		output = get_output_for_port(data, port);
> > -
> > -		/* If pipe is valid for output then set it */
> > -		if (igt_pipe_connector_valid(pipe, output)) {
> > -			igt_output_set_pipe(output, pipe);
> > -			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > -		}
> > -
> > -		chamelium_unplug(data->chamelium, port);
> > -		wait_for_connector_after_hotplug(data, mon, port,
> > -				DRM_MODE_DISCONNECTED);
> > -		igt_flush_uevents(mon);
> > -	}
> > -
> > -	igt_cleanup_uevents(mon);
> > -	igt_hpd_storm_reset(data->drm_fd);
> > -}
> > -
> > -static const char test_basic_hotplug_desc[] =
> > -	"Check that we get uevents and updated connector status on "
> > -	"hotplug and unplug";
> > -static void
> > -test_hotplug(data_t *data, struct chamelium_port *port, int toggle_count,
> > -	     enum test_modeset_mode modeset_mode)
> > -{
> > -	int i;
> > -	enum pipe pipe;
> > -	struct igt_fb fb = {0};
> > -	drmModeModeInfo mode;
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -	igt_output_t *output = get_output_for_port(data, port);
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium, NULL,
> > -			      data->ports, data->port_count);
> > -
> > -
> > -	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> > -
> > -	for (i = 0; i < toggle_count; i++) {
> > -		igt_flush_uevents(mon);
> > -
> > -		/* Check if we get a sysfs hotplug event */
> > -		chamelium_plug(data->chamelium, port);
> > -
> > -		wait_for_connector_after_hotplug(data, mon, port,
> > -						 DRM_MODE_CONNECTED);
> > -		igt_flush_uevents(mon);
> > -
> > -		if (modeset_mode == TEST_MODESET_ON_OFF ||
> > -		    (modeset_mode == TEST_MODESET_ON && i == 0 )) {
> > -			if (i == 0) {
> > -				/* We can only get mode and pipe once we are connected */
> > -				output = get_output_for_port(data, port);
> > -				pipe = get_pipe_for_output(&data->display, output);
> > -				mode = get_mode_for_port(data->chamelium, port);
> > -				create_fb_for_mode(data, &fb, &mode);
> > -			}
> > -
> > -			igt_output_set_pipe(output, pipe);
> > -			enable_output(data, port, output, &mode, &fb);
> > -		}
> > -
> > -		/* Now check if we get a hotplug from disconnection */
> > -		chamelium_unplug(data->chamelium, port);
> > -
> > -		wait_for_connector_after_hotplug(data, mon, port,
> > -						 DRM_MODE_DISCONNECTED);
> > -
> > -		igt_flush_uevents(mon);
> > -
> > -		if (modeset_mode == TEST_MODESET_ON_OFF) {
> > -			igt_output_set_pipe(output, PIPE_NONE);
> > -			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > -		}
> > -	}
> > -
> > -	igt_cleanup_uevents(mon);
> > -	igt_hpd_storm_reset(data->drm_fd);
> > -	igt_remove_fb(data->drm_fd, &fb);
> > -}
> > -
> > -static void set_edid(data_t *data, struct chamelium_port *port,
> > -		     enum igt_custom_edid_type edid)
> > -{
> > -	chamelium_port_set_edid(data->chamelium, port, data->edids[edid]);
> > -}
> > -
> > -static const char igt_custom_edid_type_read_desc[] =
> > -	"Make sure the EDID exposed by KMS is the same as the screen's";
> > -static void
> > -igt_custom_edid_type_read(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid)
> > -{
> > -	drmModePropertyBlobPtr edid_blob = NULL;
> > -	drmModeConnector *connector;
> > -	size_t raw_edid_size;
> > -	const struct edid *raw_edid;
> > -	uint64_t edid_blob_id;
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	set_edid(data, port, edid);
> > -	chamelium_plug(data->chamelium, port);
> > -	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> > -					      port, DRM_MODE_CONNECTED);
> > -
> > -	igt_skip_on(check_analog_bridge(data, port));
> > -
> > -	connector = chamelium_port_get_connector(data->chamelium, port, true);
> > -	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> > -					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> > -					&edid_blob_id, NULL));
> > -	igt_assert(edid_blob_id != 0);
> > -	igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
> > -						      edid_blob_id));
> > -
> > -	raw_edid = chamelium_edid_get_raw(data->edids[edid], port);
> > -	raw_edid_size = edid_get_size(raw_edid);
> > -	igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0);
> > -
> > -	drmModeFreePropertyBlob(edid_blob);
> > -	drmModeFreeConnector(connector);
> > -}
> > -
> > -static void
> > -try_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> > -		       enum igt_suspend_state state, enum igt_suspend_test test,
> > -		       struct udev_monitor *mon, bool connected)
> > -{
> > -	drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED :
> > -						     DRM_MODE_CONNECTED;
> > -	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> > -	int delay;
> > -	int p;
> > -
> > -	igt_flush_uevents(mon);
> > -
> > -	delay = igt_get_autoresume_delay(state) * 1000 / 2;
> > -
> > -	if (port) {
> > -		chamelium_schedule_hpd_toggle(data->chamelium, port, delay,
> > -					      !connected);
> > -	} else {
> > -		for (p = 0; p < data->port_count; p++) {
> > -			port = data->ports[p];
> > -			chamelium_schedule_hpd_toggle(data->chamelium, port,
> > -						      delay, !connected);
> > -		}
> > -
> > -		port = NULL;
> > -	}
> > -
> > -	igt_system_suspend_autoresume(state, test);
> > -	igt_assert(wait_for_hotplug(mon, &timeout));
> > -	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> > -
> > -	if (port) {
> > -		igt_assert_eq(chamelium_reprobe_connector(&data->display,
> > -							  data->chamelium,
> > -							  port),
> > -							  target_state);
> > -	} else {
> > -		for (p = 0; p < data->port_count; p++) {
> > -			drmModeConnection current_state;
> > -
> > -			port = data->ports[p];
> > -			/*
> > -			 * There could be as many hotplug events sent by
> > -			 * driver as connectors we scheduled an HPD toggle on
> > -			 * above, depending on timing. So if we're not seeing
> > -			 * the expected connector state try to wait for an HPD
> > -			 * event for each connector/port.
> > -			 */
> > -			current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port);
> > -			if (p > 0 && current_state != target_state) {
> > -				igt_assert(wait_for_hotplug(mon, &timeout));
> > -				current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port);
> > -			}
> > -
> > -			igt_assert_eq(current_state, target_state);
> > -		}
> > -
> > -		port = NULL;
> > -	}
> > -}
> > -
> > -static const char test_suspend_resume_hpd_desc[] =
> > -	"Toggle HPD during suspend, check that uevents are sent and connector "
> > -	"status is updated";
> > -static void
> > -test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> > -			enum igt_suspend_state state,
> > -			enum igt_suspend_test test)
> > -{
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	/* Make sure we notice new connectors after resuming */
> > -	try_suspend_resume_hpd(data, port, state, test, mon, false);
> > -
> > -	/* Now make sure we notice disconnected connectors after resuming */
> > -	try_suspend_resume_hpd(data, port, state, test, mon, true);
> > -
> > -	igt_cleanup_uevents(mon);
> > -}
> > -
> > -static const char test_suspend_resume_hpd_common_desc[] =
> > -	"Toggle HPD during suspend on all connectors, check that uevents are "
> > -	"sent and connector status is updated";
> > -static void
> > -test_suspend_resume_hpd_common(data_t *data, enum igt_suspend_state state,
> > -			       enum igt_suspend_test test)
> > -{
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -	struct chamelium_port *port;
> > -	int p;
> > -
> > -	for (p = 0; p < data->port_count; p++) {
> > -		port = data->ports[p];
> > -		igt_debug("Testing port %s\n", chamelium_port_get_name(port));
> > -	}
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium, NULL,
> > -			      data->ports, data->port_count);
> > -
> > -	/* Make sure we notice new connectors after resuming */
> > -	try_suspend_resume_hpd(data, NULL, state, test, mon, false);
> > -
> > -	/* Now make sure we notice disconnected connectors after resuming */
> > -	try_suspend_resume_hpd(data, NULL, state, test, mon, true);
> > -
> > -	igt_cleanup_uevents(mon);
> > -}
> > -
> > -static const char test_suspend_resume_edid_change_desc[] =
> > -	"Simulate a screen being unplugged and another screen being plugged "
> > -	"during suspend, check that a uevent is sent and connector status is "
> > -	"updated";
> > -static void
> > -test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
> > -				enum igt_suspend_state state,
> > -				enum igt_suspend_test test,
> > -				enum igt_custom_edid_type edid,
> > -				enum igt_custom_edid_type alt_edid)
> > -{
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -	bool link_status_failed[2][data->port_count];
> > -	int p;
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	/* Catch the event and flush all remaining ones. */
> > -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > -	igt_flush_uevents(mon);
> > -
> > -	/* First plug in the port */
> > -	set_edid(data, port, edid);
> > -	chamelium_plug(data->chamelium, port);
> > -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > -
> > -	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> > -					      port, DRM_MODE_CONNECTED);
> > -
> > -	/*
> > -	 * Change the edid before we suspend. On resume, the machine should
> > -	 * notice the EDID change and fire a hotplug event.
> > -	 */
> > -	set_edid(data, port, alt_edid);
> > -
> > -	get_connectors_link_status_failed(data, link_status_failed[0]);
> > -
> > -	igt_flush_uevents(mon);
> > -
> > -	igt_system_suspend_autoresume(state, test);
> > -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > -	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> > -
> > -	get_connectors_link_status_failed(data, link_status_failed[1]);
> > -
> > -	for (p = 0; p < data->port_count; p++)
> > -		igt_skip_on(!link_status_failed[0][p] && link_status_failed[1][p]);
> > -}
> > -
> > -static igt_output_t *
> > -prepare_output(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid)
> > -{
> > -	igt_display_t *display = &data->display;
> > -	igt_output_t *output;
> > -	enum pipe pipe;
> > -
> > -	/* The chamelium's default EDID has a lot of resolutions, way more then
> > -	 * we need to test. Additionally the default EDID doesn't support HDMI
> > -	 * audio.
> > -	 */
> > -	set_edid(data, port, edid);
> > -
> > -	chamelium_plug(data->chamelium, port);
> > -	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> > -					      port, DRM_MODE_CONNECTED);
> > -
> > -	igt_display_reset(display);
> > -
> > -	output = get_output_for_port(data, port);
> > -
> > -	/* Refresh pipe to update connected status */
> > -	igt_output_set_pipe(output, PIPE_NONE);
> > -
> > -	pipe = get_pipe_for_output(display, output);
> > -	igt_output_set_pipe(output, pipe);
> > -
> > -	return output;
> > -}
> > -
> > -static void do_test_display(data_t *data, struct chamelium_port *port,
> > -			    igt_output_t *output, drmModeModeInfo *mode,
> > -			    uint32_t fourcc, enum chamelium_check check,
> > -			    int count)
> > -{
> > -	struct chamelium_fb_crc_async_data *fb_crc;
> > -	struct igt_fb frame_fb, fb;
> > -	int i, fb_id, captured_frame_count;
> > -	int frame_id;
> > -
> > -	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> > -					 DRM_FORMAT_XRGB8888, 64, &fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	frame_id = igt_fb_convert(&frame_fb, &fb, fourcc,
> > -				  DRM_FORMAT_MOD_LINEAR);
> > -	igt_assert(frame_id > 0);
> > -
> > -	if (check == CHAMELIUM_CHECK_CRC)
> > -		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> > -								&fb);
> > -
> > -	enable_output(data, port, output, mode, &frame_fb);
> > -
> > -	if (check == CHAMELIUM_CHECK_CRC) {
> > -		igt_crc_t *expected_crc;
> > -		igt_crc_t *crc;
> > -
> > -		/* We want to keep the display running for a little bit, since
> > -		 * there's always the potential the driver isn't able to keep
> > -		 * the display running properly for very long
> > -		 */
> > -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count);
> > -		crc = chamelium_read_captured_crcs(data->chamelium,
> > -						   &captured_frame_count);
> > -
> > -		igt_assert(captured_frame_count == count);
> > -
> > -		igt_debug("Captured %d frames\n", captured_frame_count);
> > -
> > -		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> > -
> > -		for (i = 0; i < captured_frame_count; i++)
> > -			chamelium_assert_crc_eq_or_dump(data->chamelium,
> > -							expected_crc, &crc[i],
> > -							&fb, i);
> > -
> > -		free(expected_crc);
> > -		free(crc);
> > -	} else if (check == CHAMELIUM_CHECK_ANALOG ||
> > -		   check == CHAMELIUM_CHECK_CHECKERBOARD) {
> > -		struct chamelium_frame_dump *dump;
> > -
> > -		igt_assert(count == 1);
> > -
> > -		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> > -						  0, 0);
> > -
> > -		if (check == CHAMELIUM_CHECK_ANALOG)
> > -			chamelium_crop_analog_frame(dump, mode->hdisplay,
> > -						    mode->vdisplay);
> > -
> > -		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> > -						     dump, &fb, check);
> > -		chamelium_destroy_frame_dump(dump);
> > -	}
> > -
> > -	igt_remove_fb(data->drm_fd, &frame_fb);
> > -	igt_remove_fb(data->drm_fd, &fb);
> > -}
> > -
> > -static const char test_display_one_mode_desc[] =
> > -	"Pick the first mode of the IGT base EDID, display and capture a few "
> > -	"frames, then check captured frames are correct";
> > -static void test_display_one_mode(data_t *data, struct chamelium_port *port,
> > -				  uint32_t fourcc, enum chamelium_check check,
> > -				  int count)
> > -{
> > -	drmModeConnector *connector;
> > -	drmModeModeInfo *mode;
> > -	igt_output_t *output;
> > -	igt_plane_t *primary;
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -	igt_assert(primary);
> > -
> > -	igt_require(igt_plane_has_format_mod(primary, fourcc, DRM_FORMAT_MOD_LINEAR));
> > -
> > -	mode = &connector->modes[0];
> > -	if (check == CHAMELIUM_CHECK_ANALOG) {
> > -		bool bridge = check_analog_bridge(data, port);
> > -
> > -		igt_assert(!(bridge && prune_vga_mode(data, mode)));
> > -	}
> > -
> > -	do_test_display(data, port, output, mode, fourcc, check, count);
> > -
> > -	drmModeFreeConnector(connector);
> > -}
> > -
> > -static const char test_display_all_modes_desc[] =
> > -	"For each mode of the IGT base EDID, display and capture a few "
> > -	"frames, then check captured frames are correct";
> > -static void test_display_all_modes(data_t *data, struct chamelium_port *port,
> > -				   uint32_t fourcc, enum chamelium_check check,
> > -				   int count)
> > -{
> > -	bool bridge;
> > -	int i, count_modes;
> > -
> > -	if (check == CHAMELIUM_CHECK_ANALOG)
> > -		bridge = check_analog_bridge(data, port);
> > -
> > -	i = 0;
> > -	do {
> > -		igt_output_t *output;
> > -		igt_plane_t *primary;
> > -		drmModeConnector *connector;
> > -		drmModeModeInfo *mode;
> > -
> > -		/*
> > -		 * let's reset state each mode so we will get the
> > -		 * HPD pulses realibably
> > -		 */
> > -		igt_modeset_disable_all_outputs(&data->display);
> > -		chamelium_reset_state(&data->display, data->chamelium,
> > -				      port, data->ports, data->port_count);
> > -
> > -		/*
> > -		 * modes may change due to mode pruining and link issues, so we
> > -		 * need to refresh the connector
> > -		 */
> > -		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > -		connector = chamelium_port_get_connector(data->chamelium, port,
> > -							 false);
> > -		primary = igt_output_get_plane_type(output,
> > -						    DRM_PLANE_TYPE_PRIMARY);
> > -		igt_assert(primary);
> > -		igt_require(igt_plane_has_format_mod(primary, fourcc,
> > -			    DRM_FORMAT_MOD_LINEAR));
> > -
> > -		/* we may skip some modes due to above but that's ok */
> > -		count_modes = connector->count_modes;
> > -		if (i >= count_modes)
> > -			break;
> > -
> > -		mode = &connector->modes[i];
> > -
> > -		if (check == CHAMELIUM_CHECK_ANALOG && bridge &&
> > -		    prune_vga_mode(data, mode))
> > -			continue;
> > -
> > -		do_test_display(data, port, output, mode, fourcc, check,
> > -				count);
> > -		drmModeFreeConnector(connector);
> > -	} while (++i < count_modes);
> > -}
> > -
> > -static const char test_display_frame_dump_desc[] =
> > -	"For each mode of the IGT base EDID, display and capture a few "
> > -	"frames, then download the captured frames and compare them "
> > -	"bit-by-bit to the sent ones";
> > -static void
> > -test_display_frame_dump(data_t *data, struct chamelium_port *port)
> > -{
> > -
> > -	int i, count_modes;
> > -
> > -	i = 0;
> > -	do {
> > -		igt_output_t *output;
> > -		igt_plane_t *primary;
> > -		struct igt_fb fb;
> > -		struct chamelium_frame_dump *frame;
> > -		drmModeModeInfo *mode;
> > -		drmModeConnector *connector;
> > -		int fb_id, j;
> > -
> > -		/*
> > -		 * let's reset state each mode so we will get the
> > -		 * HPD pulses realibably
> > -		 */
> > -		igt_modeset_disable_all_outputs(&data->display);
> > -		chamelium_reset_state(&data->display, data->chamelium,
> > -				      port, data->ports, data->port_count);
> > -
> > -		/*
> > -		 * modes may change due to mode pruining and link issues, so we
> > -		 * need to refresh the connector
> > -		 */
> > -		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > -		connector = chamelium_port_get_connector(data->chamelium, port,
> > -							 false);
> > -		primary = igt_output_get_plane_type(output,
> > -						    DRM_PLANE_TYPE_PRIMARY);
> > -		igt_assert(primary);
> > -
> > -		/* we may skip some modes due to above but that's ok */
> > -		count_modes = connector->count_modes;
> > -		if (i >= count_modes)
> > -			break;
> > -
> > -		mode = &connector->modes[i];
> > -
> > -		fb_id = igt_create_color_pattern_fb(data->drm_fd,
> > -						    mode->hdisplay, mode->vdisplay,
> > -						    DRM_FORMAT_XRGB8888,
> > -						    DRM_FORMAT_MOD_LINEAR,
> > -						    0, 0, 0, &fb);
> > -		igt_assert(fb_id > 0);
> > -
> > -		enable_output(data, port, output, mode, &fb);
> > -
> > -		igt_debug("Reading frame dumps from Chamelium...\n");
> > -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
> > -		for (j = 0; j < 5; j++) {
> > -			frame = chamelium_read_captured_frame(data->chamelium,
> > -							      j);
> > -			chamelium_assert_frame_eq(data->chamelium, frame, &fb);
> > -			chamelium_destroy_frame_dump(frame);
> > -		}
> > -
> > -		igt_remove_fb(data->drm_fd, &fb);
> > -		drmModeFreeConnector(connector);
> > -	} while (++i < count_modes);
> > -}
> > -
> > -#define MODE_CLOCK_ACCURACY 0.05 /* 5% */
> > -
> > -static void check_mode(struct chamelium *chamelium, struct chamelium_port *port,
> > -		       drmModeModeInfo *mode)
> > -{
> > -	struct chamelium_video_params video_params = {0};
> > -	double mode_clock;
> > -	int mode_hsync_offset, mode_vsync_offset;
> > -	int mode_hsync_width, mode_vsync_width;
> > -	int mode_hsync_polarity, mode_vsync_polarity;
> > -
> > -	chamelium_port_get_video_params(chamelium, port, &video_params);
> > -
> > -	mode_clock = (double) mode->clock / 1000;
> > -
> > -	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) {
> > -		/* this is what chamelium understands as offsets for DP */
> > -		mode_hsync_offset = mode->htotal - mode->hsync_start;
> > -		mode_vsync_offset = mode->vtotal - mode->vsync_start;
> > -	} else {
> > -		/* and this is what they are for other connectors */
> > -		mode_hsync_offset = mode->hsync_start - mode->hdisplay;
> > -		mode_vsync_offset = mode->vsync_start - mode->vdisplay;
> > -	}
> > -
> > -	mode_hsync_width = mode->hsync_end - mode->hsync_start;
> > -	mode_vsync_width = mode->vsync_end - mode->vsync_start;
> > -
> > -	mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
> > -	mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
> > -
> > -	igt_debug("Checking video mode:\n");
> > -	igt_debug("clock: got %f, expected %f ± %f%%\n",
> > -		  video_params.clock, mode_clock, MODE_CLOCK_ACCURACY * 100);
> > -	igt_debug("hactive: got %d, expected %d\n",
> > -		  video_params.hactive, mode->hdisplay);
> > -	igt_debug("vactive: got %d, expected %d\n",
> > -		  video_params.vactive, mode->vdisplay);
> > -	igt_debug("hsync_offset: got %d, expected %d\n",
> > -		  video_params.hsync_offset, mode_hsync_offset);
> > -	igt_debug("vsync_offset: got %d, expected %d\n",
> > -		  video_params.vsync_offset, mode_vsync_offset);
> > -	igt_debug("htotal: got %d, expected %d\n",
> > -		  video_params.htotal, mode->htotal);
> > -	igt_debug("vtotal: got %d, expected %d\n",
> > -		  video_params.vtotal, mode->vtotal);
> > -	igt_debug("hsync_width: got %d, expected %d\n",
> > -		  video_params.hsync_width, mode_hsync_width);
> > -	igt_debug("vsync_width: got %d, expected %d\n",
> > -		  video_params.vsync_width, mode_vsync_width);
> > -	igt_debug("hsync_polarity: got %d, expected %d\n",
> > -		  video_params.hsync_polarity, mode_hsync_polarity);
> > -	igt_debug("vsync_polarity: got %d, expected %d\n",
> > -		  video_params.vsync_polarity, mode_vsync_polarity);
> > -
> > -	if (!isnan(video_params.clock)) {
> > -		igt_assert(video_params.clock >
> > -			   mode_clock * (1 - MODE_CLOCK_ACCURACY));
> > -		igt_assert(video_params.clock <
> > -			   mode_clock * (1 + MODE_CLOCK_ACCURACY));
> > -	}
> > -	igt_assert(video_params.hactive == mode->hdisplay);
> > -	igt_assert(video_params.vactive == mode->vdisplay);
> > -	igt_assert(video_params.hsync_offset == mode_hsync_offset);
> > -	igt_assert(video_params.vsync_offset == mode_vsync_offset);
> > -	igt_assert(video_params.htotal == mode->htotal);
> > -	igt_assert(video_params.vtotal == mode->vtotal);
> > -	igt_assert(video_params.hsync_width == mode_hsync_width);
> > -	igt_assert(video_params.vsync_width == mode_vsync_width);
> > -	igt_assert(video_params.hsync_polarity == mode_hsync_polarity);
> > -	igt_assert(video_params.vsync_polarity == mode_vsync_polarity);
> > -}
> > -
> > -static const char test_mode_timings_desc[] =
> > -	"For each mode of the IGT base EDID, perform a modeset and check the "
> > -	"mode detected by the Chamelium receiver matches the mode we set";
> > -static void test_mode_timings(data_t *data, struct chamelium_port *port)
> > -{
> > -	int i, count_modes;
> > -
> > -	i = 0;
> > -	igt_require(chamelium_supports_get_video_params(data->chamelium));
> > -	do {
> > -		igt_output_t *output;
> > -		igt_plane_t *primary;
> > -		drmModeConnector *connector;
> > -		drmModeModeInfo *mode;
> > -		int fb_id;
> > -		struct igt_fb fb;
> > -
> > -		/*
> > -		 * let's reset state each mode so we will get the
> > -		 * HPD pulses realibably
> > -		 */
> > -		igt_modeset_disable_all_outputs(&data->display);
> > -		chamelium_reset_state(&data->display, data->chamelium,
> > -				      port, data->ports, data->port_count);
> > -
> > -		/*
> > -		 * modes may change due to mode pruining and link issues, so we
> > -		 * need to refresh the connector
> > -		 */
> > -		output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > -		connector = chamelium_port_get_connector(data->chamelium, port, false);
> > -		primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -		igt_assert(primary);
> > -
> > -		/* we may skip some modes due to above but that's ok */
> > -		count_modes = connector->count_modes;
> > -		if (i >= count_modes)
> > -			break;
> > -
> > -		mode = &connector->modes[i];
> > -
> > -		fb_id = igt_create_color_pattern_fb(data->drm_fd,
> > -						    mode->hdisplay, mode->vdisplay,
> > -						    DRM_FORMAT_XRGB8888,
> > -						    DRM_FORMAT_MOD_LINEAR,
> > -						    0, 0, 0, &fb);
> > -		igt_assert(fb_id > 0);
> > -
> > -		enable_output(data, port, output, mode, &fb);
> > -
> > -		/* Trigger the FSM */
> > -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0);
> > -
> > -		check_mode(data->chamelium, port, mode);
> > -
> > -		igt_remove_fb(data->drm_fd, &fb);
> > -		drmModeFreeConnector(connector);
> > -	} while (++i < count_modes);
> > -}
> > -
> > -struct vic_mode {
> > -	int hactive, vactive;
> > -	int vrefresh; /* Hz */
> > -	uint32_t picture_ar;
> > -};
> > -
> > -/* Maps Video Identification Codes to a mode */
> > -static const struct vic_mode vic_modes[] = {
> > -	[16] = {
> > -		.hactive = 1920,
> > -		.vactive = 1080,
> > -		.vrefresh = 60,
> > -		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
> > -	},
> > -};
> > -
> > -/* Maps aspect ratios to their mode flag */
> > -static const uint32_t mode_ar_flags[] = {
> > -	[DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9,
> > -};
> > -
> > -static enum infoframe_avi_picture_aspect_ratio
> > -get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
> > -{
> > -	/* The AVI picture aspect ratio field only supports 4:3 and 16:9 */
> > -	switch (aspect_ratio) {
> > -	case DRM_MODE_PICTURE_ASPECT_4_3:
> > -		return INFOFRAME_AVI_PIC_AR_4_3;
> > -	case DRM_MODE_PICTURE_ASPECT_16_9:
> > -		return INFOFRAME_AVI_PIC_AR_16_9;
> > -	default:
> > -		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
> > -	}
> > -}
> > -
> > -static bool vic_mode_matches_drm(const struct vic_mode *vic_mode,
> > -				 drmModeModeInfo *drm_mode)
> > -{
> > -	uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar];
> > -
> > -	return vic_mode->hactive == drm_mode->hdisplay &&
> > -	       vic_mode->vactive == drm_mode->vdisplay &&
> > -	       vic_mode->vrefresh == drm_mode->vrefresh &&
> > -	       ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
> > -}
> > -
> > -static const char test_display_aspect_ratio_desc[] =
> > -	"Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and "
> > -	"check they include the relevant fields";
> > -static void test_display_aspect_ratio(data_t *data, struct chamelium_port *port)
> > -{
> > -	igt_output_t *output;
> > -	igt_plane_t *primary;
> > -	drmModeConnector *connector;
> > -	drmModeModeInfo *mode;
> > -	int fb_id, i;
> > -	struct igt_fb fb;
> > -	bool found, ok;
> > -	struct chamelium_infoframe *infoframe;
> > -	struct infoframe_avi infoframe_avi;
> > -	uint8_t vic = 16; /* TODO: test more VICs */
> > -	const struct vic_mode *vic_mode;
> > -	uint32_t aspect_ratio;
> > -	enum infoframe_avi_picture_aspect_ratio frame_ar;
> > -
> > -	igt_require(chamelium_supports_get_last_infoframe(data->chamelium));
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	output = prepare_output(data, port, IGT_CUSTOM_EDID_ASPECT_RATIO);
> > -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -	igt_assert(primary);
> > -
> > -	vic_mode = &vic_modes[vic];
> > -	aspect_ratio = vic_mode->picture_ar;
> > -
> > -	found = false;
> > -	igt_assert(connector->count_modes > 0);
> > -	for (i = 0; i < connector->count_modes; i++) {
> > -		mode = &connector->modes[i];
> > -
> > -		if (vic_mode_matches_drm(vic_mode, mode)) {
> > -			found = true;
> > -			break;
> > -		}
> > -	}
> > -	igt_assert_f(found,
> > -		     "Failed to find mode with the correct aspect ratio\n");
> > -
> > -	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> > -					    mode->hdisplay, mode->vdisplay,
> > -					    DRM_FORMAT_XRGB8888,
> > -					    DRM_FORMAT_MOD_LINEAR,
> > -					    0, 0, 0, &fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	enable_output(data, port, output, mode, &fb);
> > -
> > -	infoframe = chamelium_get_last_infoframe(data->chamelium, port,
> > -						 CHAMELIUM_INFOFRAME_AVI);
> > -	igt_assert_f(infoframe, "AVI InfoFrame not received\n");
> > -
> > -	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
> > -				 infoframe->payload, infoframe->payload_size);
> > -	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
> > -
> > -	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
> > -
> > -	igt_debug("Checking AVI InfoFrame\n");
> > -	igt_debug("Picture aspect ratio: got %d, expected %d\n",
> > -		  infoframe_avi.picture_aspect_ratio, frame_ar);
> > -	igt_debug("Video Identification Code (VIC): got %d, expected %d\n",
> > -		  infoframe_avi.vic, vic);
> > -
> > -	igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar);
> > -	igt_assert(infoframe_avi.vic == vic);
> > -
> > -	chamelium_infoframe_destroy(infoframe);
> > -	igt_remove_fb(data->drm_fd, &fb);
> > -	drmModeFreeConnector(connector);
> > -}
> > -
> > -
> > -/* Playback parameters control the audio signal we synthesize and send */
> > -#define PLAYBACK_CHANNELS 2
> > -#define PLAYBACK_SAMPLES 1024
> > -
> > -/* Capture paremeters control the audio signal we receive */
> > -#define CAPTURE_SAMPLES 2048
> > -
> > -#define AUDIO_TIMEOUT 2000 /* ms */
> > -/* A streak of 3 gives confidence that the signal is good. */
> > -#define MIN_STREAK 3
> > -
> > -#define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */
> > -#define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */
> > -#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */
> > -
> > -/* TODO: enable >48KHz rates, these are not reliable */
> > -static int test_sampling_rates[] = {
> > -	32000,
> > -	44100,
> > -	48000,
> > -	/* 88200, */
> > -	/* 96000, */
> > -	/* 176400, */
> > -	/* 192000, */
> > -};
> > -
> > -static int test_sampling_rates_count = sizeof(test_sampling_rates) / sizeof(int);
> > -
> > -/* Test frequencies (Hz): a sine signal will be generated for each.
> > - *
> > - * Depending on the sampling rate chosen, it might not be possible to properly
> > - * detect the generated sine (see Nyquist–Shannon sampling theorem).
> > - * Frequencies that can't be reliably detected will be automatically pruned in
> > - * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be
> > - * tested with a 192KHz sampling rate.
> > - */
> > -static int test_frequencies[] = {
> > -	300,
> > -	600,
> > -	1200,
> > -	10000,
> > -	80000,
> > -};
> > -
> > -static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> > -
> > -static const snd_pcm_format_t test_formats[] = {
> > -	SND_PCM_FORMAT_S16_LE,
> > -	SND_PCM_FORMAT_S24_LE,
> > -	SND_PCM_FORMAT_S32_LE,
> > -};
> > -
> > -static const size_t test_formats_count = sizeof(test_formats) / sizeof(test_formats[0]);
> > -
> > -struct audio_state {
> > -	struct alsa *alsa;
> > -	struct chamelium *chamelium;
> > -	struct chamelium_port *port;
> > -	struct chamelium_stream *stream;
> > -
> > -	/* The capture format is only available after capture has started. */
> > -	struct {
> > -		snd_pcm_format_t format;
> > -		int channels;
> > -		int rate;
> > -	} playback, capture;
> > -
> > -	char *name;
> > -	struct audio_signal *signal; /* for frequencies test only */
> > -	int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS];
> > -
> > -	size_t recv_pages;
> > -	int msec;
> > -
> > -	int dump_fd;
> > -	char *dump_path;
> > -
> > -	pthread_t thread;
> > -	atomic_bool run;
> > -	atomic_bool positive; /* for pulse test only */
> > -};
> > -
> > -static void audio_state_init(struct audio_state *state, data_t *data,
> > -			     struct alsa *alsa, struct chamelium_port *port,
> > -			     snd_pcm_format_t format, int channels, int rate)
> > -{
> > -	memset(state, 0, sizeof(*state));
> > -	state->dump_fd = -1;
> > -
> > -	state->alsa = alsa;
> > -	state->chamelium = data->chamelium;
> > -	state->port = port;
> > -
> > -	state->playback.format = format;
> > -	state->playback.channels = channels;
> > -	state->playback.rate = rate;
> > -
> > -	alsa_configure_output(alsa, format, channels, rate);
> > -
> > -	state->stream = chamelium_stream_init();
> > -	igt_assert_f(state->stream,
> > -		     "Failed to initialize Chamelium stream client\n");
> > -}
> > -
> > -static void audio_state_fini(struct audio_state *state)
> > -{
> > -	chamelium_stream_deinit(state->stream);
> > -	free(state->name);
> > -}
> > -
> > -static void *run_audio_thread(void *data)
> > -{
> > -	struct alsa *alsa = data;
> > -
> > -	alsa_run(alsa, -1);
> > -	return NULL;
> > -}
> > -
> > -static void audio_state_start(struct audio_state *state, const char *name)
> > -{
> > -	int ret;
> > -	bool ok;
> > -	size_t i, j;
> > -	enum chamelium_stream_realtime_mode stream_mode;
> > -	char dump_suffix[64];
> > -
> > -	free(state->name);
> > -	state->name = strdup(name);
> > -	state->recv_pages = 0;
> > -	state->msec = 0;
> > -
> > -	igt_debug("Starting %s test with playback format %s, "
> > -		  "sampling rate %d Hz and %d channels\n",
> > -		  name, snd_pcm_format_name(state->playback.format),
> > -		  state->playback.rate, state->playback.channels);
> > -
> > -	chamelium_start_capturing_audio(state->chamelium, state->port, false);
> > -
> > -	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
> > -	ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode);
> > -	igt_assert_f(ok, "Failed to start streaming audio capture\n");
> > -
> > -	/* Start playing audio */
> > -	state->run = true;
> > -	ret = pthread_create(&state->thread, NULL,
> > -			     run_audio_thread, state->alsa);
> > -	igt_assert_f(ret == 0, "Failed to start audio playback thread\n");
> > -
> > -	/* The Chamelium device only supports this PCM format. */
> > -	state->capture.format = SND_PCM_FORMAT_S32_LE;
> > -
> > -	/* Only after we've started playing audio, we can retrieve the capture
> > -	 * format used by the Chamelium device. */
> > -	chamelium_get_audio_format(state->chamelium, state->port,
> > -				   &state->capture.rate,
> > -				   &state->capture.channels);
> > -	if (state->capture.rate == 0) {
> > -		igt_debug("Audio receiver doesn't indicate the capture "
> > -			 "sampling rate, assuming it's %d Hz\n",
> > -			 state->playback.rate);
> > -		state->capture.rate = state->playback.rate;
> > -	}
> > -
> > -	chamelium_get_audio_channel_mapping(state->chamelium, state->port,
> > -					    state->channel_mapping);
> > -	/* Make sure we can capture all channels we send. */
> > -	for (i = 0; i < state->playback.channels; i++) {
> > -		ok = false;
> > -		for (j = 0; j < state->capture.channels; j++) {
> > -			if (state->channel_mapping[j] == i) {
> > -				ok = true;
> > -				break;
> > -			}
> > -		}
> > -		igt_assert_f(ok, "Cannot capture all channels\n");
> > -	}
> > -
> > -	if (igt_frame_dump_is_enabled()) {
> > -		snprintf(dump_suffix, sizeof(dump_suffix),
> > -			 "capture-%s-%s-%dch-%dHz",
> > -			 name, snd_pcm_format_name(state->playback.format),
> > -			 state->playback.channels, state->playback.rate);
> > -
> > -		state->dump_fd = audio_create_wav_file_s32_le(dump_suffix,
> > -							      state->capture.rate,
> > -							      state->capture.channels,
> > -							      &state->dump_path);
> > -		igt_assert_f(state->dump_fd >= 0,
> > -			     "Failed to create audio dump file\n");
> > -	}
> > -}
> > -
> > -static void audio_state_receive(struct audio_state *state,
> > -				int32_t **recv, size_t *recv_len)
> > -{
> > -	bool ok;
> > -	size_t page_count;
> > -	size_t recv_size;
> > -
> > -	ok = chamelium_stream_receive_realtime_audio(state->stream,
> > -						     &page_count,
> > -						     recv, recv_len);
> > -	igt_assert_f(ok, "Failed to receive audio from stream server\n");
> > -
> > -	state->msec = state->recv_pages * *recv_len
> > -		      / (double) state->capture.channels
> > -		      / (double) state->capture.rate * 1000;
> > -	state->recv_pages++;
> > -
> > -	if (state->dump_fd >= 0) {
> > -		recv_size = *recv_len * sizeof(int32_t);
> > -		igt_assert_f(write(state->dump_fd, *recv, recv_size) == recv_size,
> > -			     "Failed to write to audio dump file\n");
> > -	}
> > -}
> > -
> > -static void audio_state_stop(struct audio_state *state, bool success)
> > -{
> > -	bool ok;
> > -	int ret;
> > -	struct chamelium_audio_file *audio_file;
> > -	enum igt_log_level log_level;
> > -
> > -	igt_debug("Stopping audio playback\n");
> > -	state->run = false;
> > -	ret = pthread_join(state->thread, NULL);
> > -	igt_assert_f(ret == 0, "Failed to join audio playback thread\n");
> > -
> > -	ok = chamelium_stream_stop_realtime_audio(state->stream);
> > -	igt_assert_f(ok, "Failed to stop streaming audio capture\n");
> > -
> > -	audio_file = chamelium_stop_capturing_audio(state->chamelium,
> > -						    state->port);
> > -	if (audio_file) {
> > -		igt_debug("Audio file saved on the Chamelium in %s\n",
> > -			  audio_file->path);
> > -		chamelium_destroy_audio_file(audio_file);
> > -	}
> > -
> > -	if (state->dump_fd >= 0) {
> > -		close(state->dump_fd);
> > -		state->dump_fd = -1;
> > -
> > -		if (success) {
> > -			/* Test succeeded, no need to keep the captured data */
> > -			unlink(state->dump_path);
> > -		} else
> > -			igt_debug("Saved captured audio data to %s\n",
> > -				  state->dump_path);
> > -		free(state->dump_path);
> > -		state->dump_path = NULL;
> > -	}
> > -
> > -	if (success)
> > -		log_level = IGT_LOG_DEBUG;
> > -	else
> > -		log_level = IGT_LOG_CRITICAL;
> > -
> > -	igt_log(IGT_LOG_DOMAIN, log_level, "Audio %s test result for format %s, "
> > -		"sampling rate %d Hz and %d channels: %s\n",
> > -		state->name, snd_pcm_format_name(state->playback.format),
> > -		state->playback.rate, state->playback.channels,
> > -		success ? "ALL GREEN" : "FAILED");
> > -
> > -}
> > -
> > -static void check_audio_infoframe(struct audio_state *state)
> > -{
> > -	struct chamelium_infoframe *infoframe;
> > -	struct infoframe_audio infoframe_audio;
> > -	struct infoframe_audio expected = {0};
> > -	bool ok;
> > -
> > -	if (!chamelium_supports_get_last_infoframe(state->chamelium)) {
> > -		igt_debug("Skipping audio InfoFrame check: "
> > -			  "Chamelium board doesn't support GetLastInfoFrame\n");
> > -		return;
> > -	}
> > -
> > -	expected.coding_type = INFOFRAME_AUDIO_CT_PCM;
> > -	expected.channel_count = state->playback.channels;
> > -	expected.sampling_freq = state->playback.rate;
> > -	expected.sample_size = snd_pcm_format_width(state->playback.format);
> > -
> > -	infoframe = chamelium_get_last_infoframe(state->chamelium, state->port,
> > -						 CHAMELIUM_INFOFRAME_AUDIO);
> > -	if (infoframe == NULL && state->playback.channels <= 2) {
> > -		/* Audio InfoFrames are optional for mono and stereo audio */
> > -		igt_debug("Skipping audio InfoFrame check: "
> > -			  "no InfoFrame received\n");
> > -		return;
> > -	}
> > -	igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n");
> > -
> > -	ok = infoframe_audio_parse(&infoframe_audio, infoframe->version,
> > -				   infoframe->payload, infoframe->payload_size);
> > -	chamelium_infoframe_destroy(infoframe);
> > -	igt_assert_f(ok, "failed to parse audio InfoFrame\n");
> > -
> > -	igt_debug("Checking audio InfoFrame:\n");
> > -	igt_debug("coding_type: got %d, expected %d\n",
> > -		  infoframe_audio.coding_type, expected.coding_type);
> > -	igt_debug("channel_count: got %d, expected %d\n",
> > -		  infoframe_audio.channel_count, expected.channel_count);
> > -	igt_debug("sampling_freq: got %d, expected %d\n",
> > -		  infoframe_audio.sampling_freq, expected.sampling_freq);
> > -	igt_debug("sample_size: got %d, expected %d\n",
> > -		  infoframe_audio.sample_size, expected.sample_size);
> > -
> > -	if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED)
> > -		igt_assert(infoframe_audio.coding_type == expected.coding_type);
> > -	if (infoframe_audio.channel_count >= 0)
> > -		igt_assert(infoframe_audio.channel_count == expected.channel_count);
> > -	if (infoframe_audio.sampling_freq >= 0)
> > -		igt_assert(infoframe_audio.sampling_freq == expected.sampling_freq);
> > -	if (infoframe_audio.sample_size >= 0)
> > -		igt_assert(infoframe_audio.sample_size == expected.sample_size);
> > -}
> > -
> > -static int
> > -audio_output_frequencies_callback(void *data, void *buffer, int samples)
> > -{
> > -	struct audio_state *state = data;
> > -	double *tmp;
> > -	size_t len;
> > -
> > -	len = samples * state->playback.channels;
> > -	tmp = malloc(len * sizeof(double));
> > -	audio_signal_fill(state->signal, tmp, samples);
> > -	audio_convert_to(buffer, tmp, len, state->playback.format);
> > -	free(tmp);
> > -
> > -	return state->run ? 0 : -1;
> > -}
> > -
> > -static bool test_audio_frequencies(struct audio_state *state)
> > -{
> > -	int freq, step;
> > -	int32_t *recv, *buf;
> > -	double *channel;
> > -	size_t i, j, streak;
> > -	size_t recv_len, buf_len, buf_cap, channel_len;
> > -	bool success;
> > -	int capture_chan;
> > -
> > -	state->signal = audio_signal_init(state->playback.channels,
> > -					  state->playback.rate);
> > -	igt_assert_f(state->signal, "Failed to initialize audio signal\n");
> > -
> > -	/* We'll choose different frequencies per channel to make sure they are
> > -	 * independent from each other. To do so, we'll add a different offset
> > -	 * to the base frequencies for each channel. We need to choose a big
> > -	 * enough offset so that we're sure to detect mixed up channels. We
> > -	 * choose an offset of two 2 bins in the final FFT to enforce a clear
> > -	 * difference.
> > -	 *
> > -	 * Note that we assume capture_rate == playback_rate. We'll assert this
> > -	 * later on. We cannot retrieve the capture rate before starting
> > -	 * playing audio, so we don't really have the choice.
> > -	 */
> > -	step = 2 * state->playback.rate / CAPTURE_SAMPLES;
> > -	for (i = 0; i < test_frequencies_count; i++) {
> > -		for (j = 0; j < state->playback.channels; j++) {
> > -			freq = test_frequencies[i] + j * step;
> > -			audio_signal_add_frequency(state->signal, freq, j);
> > -		}
> > -	}
> > -	audio_signal_synthesize(state->signal);
> > -
> > -	alsa_register_output_callback(state->alsa,
> > -				      audio_output_frequencies_callback, state,
> > -				      PLAYBACK_SAMPLES);
> > -
> > -	audio_state_start(state, "frequencies");
> > -
> > -	igt_assert_f(state->capture.rate == state->playback.rate,
> > -		     "Capture rate (%dHz) doesn't match playback rate (%dHz)\n",
> > -		     state->capture.rate, state->playback.rate);
> > -
> > -	/* Needs to be a multiple of 128, because that's the number of samples
> > -	 * we get per channel each time we receive an audio page from the
> > -	 * Chamelium device.
> > -	 *
> > -	 * Additionally, this value needs to be high enough to guarantee we
> > -	 * capture a full period of each sine we generate. If we capture 2048
> > -	 * samples at a 192KHz sampling rate, we get a full period for a >94Hz
> > -	 * sines. For lower sampling rates, the capture duration will be
> > -	 * longer.
> > -	 */
> > -	channel_len = CAPTURE_SAMPLES;
> > -	channel = malloc(sizeof(double) * channel_len);
> > -
> > -	buf_cap = state->capture.channels * channel_len;
> > -	buf = malloc(sizeof(int32_t) * buf_cap);
> > -	buf_len = 0;
> > -
> > -	recv = NULL;
> > -	recv_len = 0;
> > -
> > -	success = false;
> > -	streak = 0;
> > -	while (!success && state->msec < AUDIO_TIMEOUT) {
> > -		audio_state_receive(state, &recv, &recv_len);
> > -
> > -		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
> > -		buf_len += recv_len;
> > -
> > -		if (buf_len < buf_cap)
> > -			continue;
> > -		igt_assert(buf_len == buf_cap);
> > -
> > -		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> > -
> > -		for (j = 0; j < state->playback.channels; j++) {
> > -			capture_chan = state->channel_mapping[j];
> > -			igt_assert(capture_chan >= 0);
> > -			igt_debug("Processing channel %zu (captured as "
> > -				  "channel %d)\n", j, capture_chan);
> > -
> > -			audio_extract_channel_s32_le(channel, channel_len,
> > -						     buf, buf_len,
> > -						     state->capture.channels,
> > -						     capture_chan);
> > -
> > -			if (audio_signal_detect(state->signal,
> > -						state->capture.rate, j,
> > -						channel, channel_len))
> > -				streak++;
> > -			else
> > -				streak = 0;
> > -		}
> > -
> > -		buf_len = 0;
> > -
> > -		success = streak == MIN_STREAK * state->playback.channels;
> > -	}
> > -
> > -	audio_state_stop(state, success);
> > -
> > -	free(recv);
> > -	free(buf);
> > -	free(channel);
> > -	audio_signal_fini(state->signal);
> > -
> > -	check_audio_infoframe(state);
> > -
> > -	return success;
> > -}
> > -
> > -static int audio_output_flatline_callback(void *data, void *buffer,
> > -					     int samples)
> > -{
> > -	struct audio_state *state = data;
> > -	double *tmp;
> > -	size_t len, i;
> > -
> > -	len = samples * state->playback.channels;
> > -	tmp = malloc(len * sizeof(double));
> > -	for (i = 0; i < len; i++)
> > -		tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE;
> > -	audio_convert_to(buffer, tmp, len, state->playback.format);
> > -	free(tmp);
> > -
> > -	return state->run ? 0 : -1;
> > -}
> > -
> > -static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos)
> > -{
> > -	double expected, min, max;
> > -	size_t i;
> > -	bool ok;
> > -
> > -	min = max = NAN;
> > -	for (i = 0; i < buf_len; i++) {
> > -		if (isnan(min) || buf[i] < min)
> > -			min = buf[i];
> > -		if (isnan(max) || buf[i] > max)
> > -			max = buf[i];
> > -	}
> > -
> > -	expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE;
> > -	ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY &&
> > -	      max <= expected + FLATLINE_AMPLITUDE_ACCURACY);
> > -	if (ok)
> > -		igt_debug("Flatline wave amplitude detected\n");
> > -	else
> > -		igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n",
> > -			  min, max);
> > -	return ok;
> > -}
> > -
> > -static ssize_t detect_falling_edge(double *buf, size_t buf_len)
> > -{
> > -	size_t i;
> > -
> > -	for (i = 0; i < buf_len; i++) {
> > -		if (buf[i] < 0)
> > -			return i;
> > -	}
> > -
> > -	return -1;
> > -}
> > -
> > -/** test_audio_flatline:
> > - *
> > - * Send a constant value (one positive, then a negative one) and check that:
> > - *
> > - * - The amplitude of the flatline is correct
> > - * - All channels switch from a positive signal to a negative one at the same
> > - *   time (ie. all channels are aligned)
> > - */
> > -static bool test_audio_flatline(struct audio_state *state)
> > -{
> > -	bool success, amp_success, align_success;
> > -	int32_t *recv;
> > -	size_t recv_len, i, channel_len;
> > -	ssize_t j;
> > -	int streak, capture_chan;
> > -	double *channel;
> > -	int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS];
> > -
> > -	alsa_register_output_callback(state->alsa,
> > -				      audio_output_flatline_callback, state,
> > -				      PLAYBACK_SAMPLES);
> > -
> > -	/* Start by sending a positive signal */
> > -	state->positive = true;
> > -
> > -	audio_state_start(state, "flatline");
> > -
> > -	for (i = 0; i < state->playback.channels; i++)
> > -		falling_edges[i] = -1;
> > -
> > -	recv = NULL;
> > -	recv_len = 0;
> > -	amp_success = false;
> > -	streak = 0;
> > -	while (!amp_success && state->msec < AUDIO_TIMEOUT) {
> > -		audio_state_receive(state, &recv, &recv_len);
> > -
> > -		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> > -
> > -		for (i = 0; i < state->playback.channels; i++) {
> > -			capture_chan = state->channel_mapping[i];
> > -			igt_assert(capture_chan >= 0);
> > -			igt_debug("Processing channel %zu (captured as "
> > -				  "channel %d)\n", i, capture_chan);
> > -
> > -			channel_len = audio_extract_channel_s32_le(NULL, 0,
> > -								   recv, recv_len,
> > -								   state->capture.channels,
> > -								   capture_chan);
> > -			channel = malloc(channel_len * sizeof(double));
> > -			audio_extract_channel_s32_le(channel, channel_len,
> > -						     recv, recv_len,
> > -						     state->capture.channels,
> > -						     capture_chan);
> > -
> > -			/* Check whether the amplitude is fine */
> > -			if (detect_flatline_amplitude(channel, channel_len,
> > -						      state->positive))
> > -				streak++;
> > -			else
> > -				streak = 0;
> > -
> > -			/* If we're now sending a negative signal, detect the
> > -			 * falling edge */
> > -			j = detect_falling_edge(channel, channel_len);
> > -			if (!state->positive && j >= 0) {
> > -				falling_edges[i] = recv_len * state->recv_pages
> > -						   + j;
> > -			}
> > -
> > -			free(channel);
> > -		}
> > -
> > -		amp_success = streak == MIN_STREAK * state->playback.channels;
> > -
> > -		if (amp_success && state->positive) {
> > -			/* Switch to a negative signal after we've detected the
> > -			 * positive one. */
> > -			state->positive = false;
> > -			amp_success = false;
> > -			streak = 0;
> > -			igt_debug("Switching to negative square wave\n");
> > -		}
> > -	}
> > -
> > -	/* Check alignment between all channels by comparing the index of the
> > -	 * falling edge. */
> > -	align_success = true;
> > -	for (i = 0; i < state->playback.channels; i++) {
> > -		if (falling_edges[i] < 0) {
> > -			igt_critical("Falling edge not detected for channel %zu\n",
> > -				     i);
> > -			align_success = false;
> > -			continue;
> > -		}
> > -
> > -		if (abs(falling_edges[0] - falling_edges[i]) >
> > -		    FLATLINE_ALIGN_ACCURACY) {
> > -			igt_critical("Channel alignment mismatch: "
> > -				     "channel 0 has a falling edge at index %d "
> > -				     "while channel %zu has index %d\n",
> > -				     falling_edges[0], i, falling_edges[i]);
> > -			align_success = false;
> > -		}
> > -	}
> > -
> > -	success = amp_success && align_success;
> > -	audio_state_stop(state, success);
> > -
> > -	free(recv);
> > -
> > -	return success;
> > -}
> > -
> > -static bool check_audio_configuration(struct alsa *alsa, snd_pcm_format_t format,
> > -				      int channels, int sampling_rate)
> > -{
> > -	if (!alsa_test_output_configuration(alsa, format, channels,
> > -					    sampling_rate)) {
> > -		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> > -			  "and %d channels because at least one of the "
> > -			  "selected output devices doesn't support this "
> > -			  "configuration\n",
> > -			  snd_pcm_format_name(format),
> > -			  sampling_rate, channels);
> > -		return false;
> > -	}
> > -	/* TODO: the Chamelium device sends a malformed signal for some audio
> > -	 * configurations. See crbug.com/950917 */
> > -	if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) ||
> > -			channels > 2) {
> > -		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> > -			  "and %d channels because the Chamelium device "
> > -			  "doesn't support this configuration\n",
> > -			  snd_pcm_format_name(format),
> > -			  sampling_rate, channels);
> > -		return false;
> > -	}
> > -	return true;
> > -}
> > -
> > -static const char test_display_audio_desc[] =
> > -	"Playback various audio signals with various audio formats/rates, "
> > -	"capture them and check they are correct";
> > -static void
> > -test_display_audio(data_t *data, struct chamelium_port *port,
> > -		   const char *audio_device, enum igt_custom_edid_type edid)
> > -{
> > -	bool run, success;
> > -	struct alsa *alsa;
> > -	int ret;
> > -	igt_output_t *output;
> > -	igt_plane_t *primary;
> > -	struct igt_fb fb;
> > -	drmModeModeInfo *mode;
> > -	drmModeConnector *connector;
> > -	int fb_id, i, j;
> > -	int channels, sampling_rate;
> > -	snd_pcm_format_t format;
> > -	struct audio_state state;
> > -
> > -	igt_require(alsa_has_exclusive_access());
> > -
> > -	/* Old Chamelium devices need an update for DisplayPort audio and
> > -	 * chamelium_get_audio_format support. */
> > -	igt_require(chamelium_has_audio_support(data->chamelium, port));
> > -
> > -	alsa = alsa_init();
> > -	igt_assert(alsa);
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	output = prepare_output(data, port, edid);
> > -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -	igt_assert(primary);
> > -
> > -	/* Enable the output because the receiver won't try to receive audio if
> > -	 * it doesn't receive video. */
> > -	igt_assert(connector->count_modes > 0);
> > -	mode = &connector->modes[0];
> > -
> > -	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> > -					    mode->hdisplay, mode->vdisplay,
> > -					    DRM_FORMAT_XRGB8888,
> > -					    DRM_FORMAT_MOD_LINEAR,
> > -					    0, 0, 0, &fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	enable_output(data, port, output, mode, &fb);
> > -
> > -	run = false;
> > -	success = true;
> > -	for (i = 0; i < test_sampling_rates_count; i++) {
> > -		for (j = 0; j < test_formats_count; j++) {
> > -			ret = alsa_open_output(alsa, audio_device);
> > -			igt_assert_f(ret >= 0, "Failed to open ALSA output\n");
> > -
> > -			/* TODO: playback on all 8 available channels (this
> > -			 * isn't supported by Chamelium devices yet, see
> > -			 * https://crbug.com/950917) */
> > -			format = test_formats[j];
> > -			channels = PLAYBACK_CHANNELS;
> > -			sampling_rate = test_sampling_rates[i];
> > -
> > -			if (!check_audio_configuration(alsa, format, channels,
> > -						       sampling_rate))
> > -				continue;
> > -
> > -			run = true;
> > -
> > -			audio_state_init(&state, data, alsa, port,
> > -					 format, channels, sampling_rate);
> > -			success &= test_audio_frequencies(&state);
> > -			success &= test_audio_flatline(&state);
> > -			audio_state_fini(&state);
> > -
> > -			alsa_close_output(alsa);
> > -		}
> > -	}
> > -
> > -	/* Make sure we tested at least one frequency and format. */
> > -	igt_assert(run);
> > -	/* Make sure all runs were successful. */
> > -	igt_assert(success);
> > -
> > -	igt_remove_fb(data->drm_fd, &fb);
> > -
> > -	drmModeFreeConnector(connector);
> > -
> > -	free(alsa);
> > -}
> > -
> > -static const char test_display_audio_edid_desc[] =
> > -	"Plug a connector with an EDID suitable for audio, check ALSA's "
> > -	"EDID-Like Data reports the correct audio parameters";
> > -static void
> > -test_display_audio_edid(data_t *data, struct chamelium_port *port,
> > -			enum igt_custom_edid_type edid)
> > -{
> > -	igt_output_t *output;
> > -	igt_plane_t *primary;
> > -	struct igt_fb fb;
> > -	drmModeModeInfo *mode;
> > -	drmModeConnector *connector;
> > -	int fb_id;
> > -	struct eld_entry eld;
> > -	struct eld_sad *sad;
> > -
> > -	igt_require(eld_is_supported());
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	output = prepare_output(data, port, edid);
> > -	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > -	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -	igt_assert(primary);
> > -
> > -	/* Enable the output because audio cannot be played on inactive
> > -	 * connectors. */
> > -	igt_assert(connector->count_modes > 0);
> > -	mode = &connector->modes[0];
> > -
> > -	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> > -					    mode->hdisplay, mode->vdisplay,
> > -					    DRM_FORMAT_XRGB8888,
> > -					    DRM_FORMAT_MOD_LINEAR,
> > -					    0, 0, 0, &fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	enable_output(data, port, output, mode, &fb);
> > -
> > -	igt_assert(eld_get_igt(&eld));
> > -	igt_assert(eld.sads_len == 1);
> > -
> > -	sad = &eld.sads[0];
> > -	igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM);
> > -	igt_assert(sad->channels == 2);
> > -	igt_assert(sad->rates == (CEA_SAD_SAMPLING_RATE_32KHZ |
> > -		   CEA_SAD_SAMPLING_RATE_44KHZ | CEA_SAD_SAMPLING_RATE_48KHZ));
> > -	igt_assert(sad->bits == (CEA_SAD_SAMPLE_SIZE_16 |
> > -		   CEA_SAD_SAMPLE_SIZE_20 | CEA_SAD_SAMPLE_SIZE_24));
> > -
> > -	igt_remove_fb(data->drm_fd, &fb);
> > -
> > -	drmModeFreeConnector(connector);
> > -}
> > -
> > -static void randomize_plane_stride(data_t *data,
> > -				   uint32_t width, uint32_t height,
> > -				   uint32_t format, uint64_t modifier,
> > -				   size_t *stride)
> > -{
> > -	size_t stride_min;
> > -	uint32_t max_tile_w = 4, tile_w, tile_h;
> > -	int i;
> > -	struct igt_fb dummy;
> > -
> > -	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
> > -
> > -	/* Randomize the stride to less than twice the minimum. */
> > -	*stride = (rand() % stride_min) + stride_min;
> > -
> > -	/*
> > -	 * Create a dummy FB to determine bpp for each plane, and calculate
> > -	 * the maximum tile width from that.
> > -	 */
> > -	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
> > -	for (i = 0; i < dummy.num_planes; i++) {
> > -		igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i], &tile_w, &tile_h);
> > -
> > -		if (tile_w > max_tile_w)
> > -			max_tile_w = tile_w;
> > -	}
> > -	igt_remove_fb(data->drm_fd, &dummy);
> > -
> > -	/*
> > -	 * Pixman requires the stride to be aligned to 32-bits, which is
> > -	 * reflected in the initial value of max_tile_w and the hw
> > -	 * may require a multiple of tile width, choose biggest of the 2.
> > -	 */
> > -	*stride = ALIGN(*stride, max_tile_w);
> > -}
> > -
> > -static void update_tiled_modifier(igt_plane_t *plane, uint32_t width,
> > -				  uint32_t height, uint32_t format,
> > -				  uint64_t *modifier)
> > -{
> > -	if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) {
> > -		/* Randomize the column height to less than twice the minimum. */
> > -		size_t column_height = (rand() % height) + height;
> > -
> > -		igt_debug("Selecting VC4 SAND256 tiling with column height %ld\n",
> > -			  column_height);
> > -
> > -		*modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(column_height);
> > -	}
> > -}
> > -
> > -static void randomize_plane_setup(data_t *data, igt_plane_t *plane,
> > -				  drmModeModeInfo *mode,
> > -				  uint32_t *width, uint32_t *height,
> > -				  uint32_t *format, uint64_t *modifier,
> > -				  bool allow_yuv)
> > -{
> > -	int min_dim;
> > -	uint32_t idx[plane->format_mod_count];
> > -	unsigned int count = 0;
> > -	unsigned int i;
> > -
> > -	/* First pass to count the supported formats. */
> > -	for (i = 0; i < plane->format_mod_count; i++)
> > -		if (igt_fb_supported_format(plane->formats[i]) &&
> > -		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
> > -			idx[count++] = i;
> > -
> > -	igt_assert(count > 0);
> > -
> > -	i = idx[rand() % count];
> > -	*format = plane->formats[i];
> > -	*modifier = plane->modifiers[i];
> > -
> > -	update_tiled_modifier(plane, *width, *height, *format, modifier);
> > -
> > -	/*
> > -	 * Randomize width and height in the mode dimensions range.
> > -	 *
> > -	 * Restrict to a min of 2 * min_dim, this way src_w/h are always at
> > -	 * least min_dim, because src_w = width - (rand % w / 2).
> > -	 *
> > -	 * Use a minimum dimension of 16 for YUV, because planar YUV
> > -	 * subsamples the UV plane.
> > -	 */
> > -	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
> > -
> > -	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
> > -	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
> > -}
> > -
> > -static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h,
> > -			    uint32_t src_x, uint32_t src_y, uint32_t crtc_w,
> > -			    uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y,
> > -			    struct igt_fb *fb)
> > -{
> > -	igt_plane_set_fb(plane, fb);
> > -
> > -	igt_plane_set_position(plane, crtc_x, crtc_y);
> > -	igt_plane_set_size(plane, crtc_w, crtc_h);
> > -
> > -	igt_fb_set_position(fb, plane, src_x, src_y);
> > -	igt_fb_set_size(fb, plane, src_w, src_h);
> > -}
> > -
> > -static void randomize_plane_coordinates(data_t *data, igt_plane_t *plane,
> > -					drmModeModeInfo *mode,
> > -					struct igt_fb *fb,
> > -					uint32_t *src_w, uint32_t *src_h,
> > -					uint32_t *src_x, uint32_t *src_y,
> > -					uint32_t *crtc_w, uint32_t *crtc_h,
> > -					int32_t *crtc_x, int32_t *crtc_y,
> > -					bool allow_scaling)
> > -{
> > -	bool is_yuv = igt_format_is_yuv(fb->drm_format);
> > -	uint32_t width = fb->width, height = fb->height;
> > -	double ratio;
> > -	int ret;
> > -
> > -	/* Randomize source offset in the first half of the original size. */
> > -	*src_x = rand() % (width / 2);
> > -	*src_y = rand() % (height / 2);
> > -
> > -	/* The source size only includes the active source area. */
> > -	*src_w = width - *src_x;
> > -	*src_h = height - *src_y;
> > -
> > -	if (allow_scaling) {
> > -		*crtc_w = (rand() % mode->hdisplay) + 1;
> > -		*crtc_h = (rand() % mode->vdisplay) + 1;
> > -
> > -		/*
> > -		 * Don't bother with scaling if dimensions are quite close in
> > -		 * order to get non-scaling cases more frequently. Also limit
> > -		 * scaling to 3x to avoid agressive filtering that makes
> > -		 * comparison less reliable, and don't go above 2x downsampling
> > -		 * to avoid possible hw limitations.
> > -		 */
> > -
> > -		ratio = ((double) *crtc_w / *src_w);
> > -		if (ratio < 0.5)
> > -			*src_w = *crtc_w * 2;
> > -		else if (ratio > 0.8 && ratio < 1.2)
> > -			*crtc_w = *src_w;
> > -		else if (ratio > 3.0)
> > -			*crtc_w = *src_w * 3;
> > -
> > -		ratio = ((double) *crtc_h / *src_h);
> > -		if (ratio < 0.5)
> > -			*src_h = *crtc_h * 2;
> > -		else if (ratio > 0.8 && ratio < 1.2)
> > -			*crtc_h = *src_h;
> > -		else if (ratio > 3.0)
> > -			*crtc_h = *src_h * 3;
> > -	} else {
> > -		*crtc_w = *src_w;
> > -		*crtc_h = *src_h;
> > -	}
> > -
> > -	if (*crtc_w != *src_w || *crtc_h != *src_h) {
> > -		/*
> > -		 * When scaling is involved, make sure to not go off-bounds or
> > -		 * scaled clipping may result in decimal dimensions, that most
> > -		 * drivers don't support.
> > -		 */
> > -		if (*crtc_w < mode->hdisplay)
> > -			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
> > -		else
> > -			*crtc_x = 0;
> > -
> > -		if (*crtc_h < mode->vdisplay)
> > -			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
> > -		else
> > -			*crtc_y = 0;
> > -	} else {
> > -		/*
> > -		 * Randomize the on-crtc position and allow the plane to go
> > -		 * off-display by less than half of its on-crtc dimensions.
> > -		 */
> > -		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
> > -		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
> > -	}
> > -
> > -	configure_plane(plane, *src_w, *src_h, *src_x, *src_y,
> > -			*crtc_w, *crtc_h, *crtc_x, *crtc_y, fb);
> > -	ret = igt_display_try_commit_atomic(&data->display,
> > -					    DRM_MODE_ATOMIC_TEST_ONLY |
> > -					    DRM_MODE_ATOMIC_ALLOW_MODESET,
> > -					    NULL);
> > -	if (!ret)
> > -		return;
> > -
> > -	/* Coordinates are logged in the dumped debug log, so only report w/h on failure here. */
> > -	igt_assert_f(ret != -ENOSPC,"Failure in testcase, invalid coordinates on a %ux%u fb\n", width, height);
> > -
> > -	/* Make YUV coordinates a multiple of 2 and retry the math. */
> > -	if (is_yuv) {
> > -		*src_x &= ~1;
> > -		*src_y &= ~1;
> > -		*src_w &= ~1;
> > -		*src_h &= ~1;
> > -		/* To handle 1:1 scaling, clear crtc_w/h too. */
> > -		*crtc_w &= ~1;
> > -		*crtc_h &= ~1;
> > -
> > -		if (*crtc_x < 0 && (*crtc_x & 1))
> > -			(*crtc_x)++;
> > -		else
> > -			*crtc_x &= ~1;
> > -
> > -		/* If negative, round up to 0 instead of down */
> > -		if (*crtc_y < 0 && (*crtc_y & 1))
> > -			(*crtc_y)++;
> > -		else
> > -			*crtc_y &= ~1;
> > -
> > -		configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
> > -				*crtc_h, *crtc_x, *crtc_y, fb);
> > -		ret = igt_display_try_commit_atomic(&data->display,
> > -						DRM_MODE_ATOMIC_TEST_ONLY |
> > -						DRM_MODE_ATOMIC_ALLOW_MODESET,
> > -						NULL);
> > -		if (!ret)
> > -			return;
> > -	}
> > -
> > -	igt_assert(!ret || allow_scaling);
> > -	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
> > -		  ((double) *crtc_w / *src_w), ((double) *crtc_h / *src_h));
> > -
> > -	*crtc_w = *src_w;
> > -	*crtc_h = *src_h;
> > -
> > -	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
> > -			*crtc_h, *crtc_x, *crtc_y, fb);
> > -	igt_display_commit_atomic(&data->display,
> > -				  DRM_MODE_ATOMIC_TEST_ONLY |
> > -				  DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
> > -}
> > -
> > -static void blit_plane_cairo(data_t *data, cairo_surface_t *result,
> > -			     uint32_t src_w, uint32_t src_h,
> > -			     uint32_t src_x, uint32_t src_y,
> > -			     uint32_t crtc_w, uint32_t crtc_h,
> > -			     int32_t crtc_x, int32_t crtc_y,
> > -			     struct igt_fb *fb)
> > -{
> > -	cairo_surface_t *surface;
> > -	cairo_surface_t *clipped_surface;
> > -	cairo_t *cr;
> > -
> > -	surface = igt_get_cairo_surface(data->drm_fd, fb);
> > -
> > -	if (src_x || src_y) {
> > -		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
> > -							     src_w, src_h);
> > -
> > -		cr = cairo_create(clipped_surface);
> > -
> > -		cairo_translate(cr, -1. * src_x, -1. * src_y);
> > -
> > -		cairo_set_source_surface(cr, surface, 0, 0);
> > -
> > -		cairo_paint(cr);
> > -		cairo_surface_flush(clipped_surface);
> > -
> > -		cairo_destroy(cr);
> > -	} else {
> > -		clipped_surface = surface;
> > -	}
> > -
> > -	cr = cairo_create(result);
> > -
> > -	cairo_translate(cr, crtc_x, crtc_y);
> > -
> > -	if (src_w != crtc_w || src_h != crtc_h) {
> > -		cairo_scale(cr, (double) crtc_w / src_w,
> > -			    (double) crtc_h / src_h);
> > -	}
> > -
> > -	cairo_set_source_surface(cr, clipped_surface, 0, 0);
> > -	cairo_surface_destroy(clipped_surface);
> > -
> > -	if (src_w != crtc_w || src_h != crtc_h) {
> > -		cairo_pattern_set_filter(cairo_get_source(cr),
> > -					 CAIRO_FILTER_BILINEAR);
> > -		cairo_pattern_set_extend(cairo_get_source(cr),
> > -					 CAIRO_EXTEND_NONE);
> > -	}
> > -
> > -	cairo_paint(cr);
> > -	cairo_surface_flush(result);
> > -
> > -	cairo_destroy(cr);
> > -}
> > -
> > -static void prepare_randomized_plane(data_t *data,
> > -				     drmModeModeInfo *mode,
> > -				     igt_plane_t *plane,
> > -				     struct igt_fb *overlay_fb,
> > -				     unsigned int index,
> > -				     cairo_surface_t *result_surface,
> > -				     bool allow_scaling, bool allow_yuv)
> > -{
> > -	struct igt_fb pattern_fb;
> > -	uint32_t overlay_fb_w, overlay_fb_h;
> > -	uint32_t overlay_src_w, overlay_src_h;
> > -	uint32_t overlay_src_x, overlay_src_y;
> > -	int32_t overlay_crtc_x, overlay_crtc_y;
> > -	uint32_t overlay_crtc_w, overlay_crtc_h;
> > -	uint32_t format;
> > -	uint64_t modifier;
> > -	size_t stride;
> > -	bool tiled;
> > -	int fb_id;
> > -
> > -	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
> > -			      &format, &modifier, allow_yuv);
> > -
> > -	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
> > -	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n",
> > -		  index, overlay_fb_w, overlay_fb_h,
> > -		  igt_format_str(format), tiled ? "tiled" : "linear");
> > -
> > -	/* Get a pattern framebuffer for the overlay plane. */
> > -	fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h,
> > -					 DRM_FORMAT_XRGB8888, 32, &pattern_fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h,
> > -			       format, modifier, &stride);
> > -
> > -	igt_debug("Plane %d: stride %ld\n", index, stride);
> > -
> > -	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
> > -					   modifier, stride);
> > -	igt_assert(fb_id > 0);
> > -
> > -	randomize_plane_coordinates(data, plane, mode, overlay_fb,
> > -				    &overlay_src_w, &overlay_src_h,
> > -				    &overlay_src_x, &overlay_src_y,
> > -				    &overlay_crtc_w, &overlay_crtc_h,
> > -				    &overlay_crtc_x, &overlay_crtc_y,
> > -				    allow_scaling);
> > -
> > -	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index,
> > -		  overlay_src_w, overlay_src_h);
> > -	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
> > -		  overlay_src_x, overlay_src_y);
> > -	igt_debug("Plane %d: on-crtc size %dx%d\n", index,
> > -		  overlay_crtc_w, overlay_crtc_h);
> > -	igt_debug("Plane %d: on-crtc position %dx%d\n", index,
> > -		  overlay_crtc_x, overlay_crtc_y);
> > -
> > -	blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h,
> > -			 overlay_src_x, overlay_src_y,
> > -			 overlay_crtc_w, overlay_crtc_h,
> > -			 overlay_crtc_x, overlay_crtc_y, &pattern_fb);
> > -
> > -	/* Remove the original pattern framebuffer. */
> > -	igt_remove_fb(data->drm_fd, &pattern_fb);
> > -}
> > -
> > -static const char test_display_planes_random_desc[] =
> > -	"Setup a few overlay planes with random parameters, capture the frame "
> > -	"and check it matches the expected output";
> > -static void test_display_planes_random(data_t *data,
> > -				       struct chamelium_port *port,
> > -				       enum chamelium_check check)
> > -{
> > -	igt_output_t *output;
> > -	drmModeModeInfo *mode;
> > -	igt_plane_t *primary_plane;
> > -	struct igt_fb primary_fb;
> > -	struct igt_fb result_fb;
> > -	struct igt_fb *overlay_fbs;
> > -	igt_crc_t *crc;
> > -	igt_crc_t *expected_crc;
> > -	struct chamelium_fb_crc_async_data *fb_crc;
> > -	unsigned int overlay_planes_max = 0;
> > -	unsigned int overlay_planes_count;
> > -	cairo_surface_t *result_surface;
> > -	int captured_frame_count;
> > -	bool allow_scaling;
> > -	bool allow_yuv;
> > -	unsigned int i;
> > -	unsigned int fb_id;
> > -
> > -	switch (check) {
> > -	case CHAMELIUM_CHECK_CRC:
> > -		allow_scaling = false;
> > -		allow_yuv = false;
> > -		break;
> > -	case CHAMELIUM_CHECK_CHECKERBOARD:
> > -		allow_scaling = true;
> > -		allow_yuv = true;
> > -		break;
> > -	default:
> > -		igt_assert(false);
> > -	}
> > -
> > -	srand(time(NULL));
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	/* Find the connector and pipe. */
> > -	output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > -
> > -	mode = igt_output_get_mode(output);
> > -
> > -	/* Get a framebuffer for the primary plane. */
> > -	primary_plane = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -	igt_assert(primary_plane);
> > -
> > -	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> > -					 DRM_FORMAT_XRGB8888, 64, &primary_fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	/* Get a framebuffer for the cairo composition result. */
> > -	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay,
> > -			      mode->vdisplay, DRM_FORMAT_XRGB8888,
> > -			      DRM_FORMAT_MOD_LINEAR, &result_fb);
> > -	igt_assert(fb_id > 0);
> > -
> > -	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
> > -
> > -	/* Paint the primary framebuffer on the result surface. */
> > -	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
> > -			 &primary_fb);
> > -
> > -	/* Configure the primary plane. */
> > -	igt_plane_set_fb(primary_plane, &primary_fb);
> > -
> > -	overlay_planes_max =
> > -		igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY);
> > -
> > -	/* Limit the number of planes to a reasonable scene. */
> > -	overlay_planes_max = min(overlay_planes_max, 4u);
> > -
> > -	overlay_planes_count = (rand() % overlay_planes_max) + 1;
> > -	igt_debug("Using %d overlay planes\n", overlay_planes_count);
> > -
> > -	overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count);
> > -
> > -	for (i = 0; i < overlay_planes_count; i++) {
> > -		struct igt_fb *overlay_fb = &overlay_fbs[i];
> > -		igt_plane_t *plane =
> > -			igt_output_get_plane_type_index(output,
> > -							DRM_PLANE_TYPE_OVERLAY,
> > -							i);
> > -		igt_assert(plane);
> > -
> > -		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
> > -					 result_surface, allow_scaling,
> > -					 allow_yuv);
> > -	}
> > -
> > -	cairo_surface_destroy(result_surface);
> > -
> > -	if (check == CHAMELIUM_CHECK_CRC)
> > -		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> > -								&result_fb);
> > -
> > -	igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > -
> > -	if (check == CHAMELIUM_CHECK_CRC) {
> > -		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1);
> > -		crc = chamelium_read_captured_crcs(data->chamelium,
> > -						   &captured_frame_count);
> > -
> > -		igt_assert(captured_frame_count == 1);
> > -
> > -		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> > -
> > -		chamelium_assert_crc_eq_or_dump(data->chamelium,
> > -						expected_crc, crc,
> > -						&result_fb, 0);
> > -
> > -		free(expected_crc);
> > -		free(crc);
> > -	} else if (check == CHAMELIUM_CHECK_CHECKERBOARD) {
> > -		struct chamelium_frame_dump *dump;
> > -
> > -		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> > -						  0, 0);
> > -		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> > -						     dump, &result_fb, check);
> > -		chamelium_destroy_frame_dump(dump);
> > -	}
> > -
> > -	for (i = 0; i < overlay_planes_count; i++)
> > -		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
> > -
> > -	free(overlay_fbs);
> > -
> > -	igt_remove_fb(data->drm_fd, &primary_fb);
> > -	igt_remove_fb(data->drm_fd, &result_fb);
> > -}
> > -
> > -static const char test_hpd_without_ddc_desc[] =
> > -	"Disable DDC on a VGA connector, check we still get a uevent on hotplug";
> > -static void
> > -test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> > -{
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -	igt_flush_uevents(mon);
> > -
> > -	/* Disable the DDC on the connector and make sure we still get a
> > -	 * hotplug
> > -	 */
> > -	chamelium_port_set_ddc_state(data->chamelium, port, false);
> > -	chamelium_plug(data->chamelium, port);
> > -
> > -	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > -	igt_assert_eq(chamelium_reprobe_connector(&data->display,
> > -						  data->chamelium, port),
> > -						  DRM_MODE_CONNECTED);
> > -
> > -	igt_cleanup_uevents(mon);
> > -}
> > -
> > -static const char test_hpd_storm_detect_desc[] =
> > -	"Trigger a series of hotplugs in a very small timeframe to simulate a"
> > -	"bad cable, check the kernel falls back to polling to avoid a hotplug "
> > -	"storm";
> > -static void
> > -test_hpd_storm_detect(data_t *data, struct chamelium_port *port, int width)
> > -{
> > -	struct udev_monitor *mon;
> > -	int count = 0;
> > -
> > -	igt_require_hpd_storm_ctl(data->drm_fd);
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	igt_hpd_storm_set_threshold(data->drm_fd, 1);
> > -	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> > -	igt_assert(igt_hpd_storm_detected(data->drm_fd));
> > -
> > -	mon = igt_watch_uevents();
> > -	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> > -
> > -	/*
> > -	 * Polling should have been enabled by the HPD storm at this point,
> > -	 * so we should only get at most 1 hotplug event
> > -	 */
> > -	igt_until_timeout(5)
> > -		count += igt_hotplug_detected(mon, 1);
> > -	igt_assert_lt(count, 2);
> > -
> > -	igt_cleanup_uevents(mon);
> > -	igt_hpd_storm_reset(data->drm_fd);
> > -}
> > -
> > -static const char test_hpd_storm_disable_desc[] =
> > -	"Disable HPD storm detection, trigger a storm and check the kernel "
> > -	"doesn't detect one";
> > -static void
> > -test_hpd_storm_disable(data_t *data, struct chamelium_port *port, int width)
> > -{
> > -	igt_require_hpd_storm_ctl(data->drm_fd);
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	chamelium_reset_state(&data->display, data->chamelium,
> > -			      port, data->ports, data->port_count);
> > -
> > -	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> > -	chamelium_fire_hpd_pulses(data->chamelium, port,
> > -				  width, 10);
> > -	igt_assert(!igt_hpd_storm_detected(data->drm_fd));
> > -
> > -	igt_hpd_storm_reset(data->drm_fd);
> > -}
> > -
> > -static const char igt_edid_stress_resolution_desc[] =
> > -	"Stress test the DUT by testing multiple EDIDs, one right after the other,"
> > -	"and ensure their validity by check the real screen resolution vs the"
> > -	"advertised mode resultion.";
> > -static void edid_stress_resolution(data_t *data, struct chamelium_port *port,
> > -				   monitor_edid edids_list[],
> > -				   size_t edids_list_len)
> > -{
> > -	int i;
> > -	struct chamelium *chamelium = data->chamelium;
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -
> > -	for (i = 0; i < edids_list_len; ++i) {
> > -		struct chamelium_edid *chamelium_edid;
> > -		drmModeModeInfo mode;
> > -		struct igt_fb fb = { 0 };
> > -		igt_output_t *output;
> > -		enum pipe pipe;
> > -		bool is_video_stable;
> > -		int screen_res_w, screen_res_h;
> > -
> > -		monitor_edid *edid = &edids_list[i];
> > -		igt_info("Testing out the EDID for %s\n",
> > -			 monitor_edid_get_name(edid));
> > -
> > -		/* Getting and Setting the EDID on Chamelium. */
> > -		chamelium_edid =
> > -			get_chameleon_edid_from_monitor_edid(chamelium, edid);
> > -		chamelium_port_set_edid(data->chamelium, port, chamelium_edid);
> > -		free_chamelium_edid_from_monitor_edid(chamelium_edid);
> > -
> > -		igt_flush_uevents(mon);
> > -		chamelium_plug(chamelium, port);
> > -		wait_for_connector_after_hotplug(data, mon, port,
> > -						 DRM_MODE_CONNECTED);
> > -		igt_flush_uevents(mon);
> > -
> > -		/* Setting an output on the screen to turn it on. */
> > -		mode = get_mode_for_port(chamelium, port);
> > -		create_fb_for_mode(data, &fb, &mode);
> > -		output = get_output_for_port(data, port);
> > -		pipe = get_pipe_for_output(&data->display, output);
> > -		igt_output_set_pipe(output, pipe);
> > -		enable_output(data, port, output, &mode, &fb);
> > -
> > -		/* Capture the screen resolution and verify. */
> > -		is_video_stable = chamelium_port_wait_video_input_stable(
> > -			chamelium, port, 5);
> > -		igt_assert(is_video_stable);
> > -
> > -		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> > -					      &screen_res_h);
> > -		igt_assert(screen_res_w == fb.width);
> > -		igt_assert(screen_res_h == fb.height);
> > -
> > -		// Clean up
> > -		igt_remove_fb(data->drm_fd, &fb);
> > -		igt_modeset_disable_all_outputs(&data->display);
> > -		chamelium_unplug(chamelium, port);
> > -	}
> > -
> > -	chamelium_reset_state(&data->display, data->chamelium, port,
> > -			      data->ports, data->port_count);
> > -}
> > -
> > -static const char igt_edid_resolution_list_desc[] =
> > -	"Get an EDID with many modes of different configurations, set them on the screen and check the"
> > -	" screen resolution matches the mode resolution.";
> > -
> > -static void edid_resolution_list(data_t *data, struct chamelium_port *port)
> > -{
> > -	struct chamelium *chamelium = data->chamelium;
> > -	struct udev_monitor *mon = igt_watch_uevents();
> > -	drmModeConnector *connector;
> > -	drmModeModeInfoPtr modes;
> > -	int count_modes;
> > -	int i;
> > -	igt_output_t *output;
> > -	enum pipe pipe;
> > -
> > -	chamelium_unplug(chamelium, port);
> > -	set_edid(data, port, IGT_CUSTOM_EDID_FULL);
> > -
> > -	igt_flush_uevents(mon);
> > -	chamelium_plug(chamelium, port);
> > -	wait_for_connector_after_hotplug(data, mon, port, DRM_MODE_CONNECTED);
> > -	igt_flush_uevents(mon);
> > -
> > -	connector = chamelium_port_get_connector(chamelium, port, true);
> > -	modes = connector->modes;
> > -	count_modes = connector->count_modes;
> > -
> > -	output = get_output_for_port(data, port);
> > -	pipe = get_pipe_for_output(&data->display, output);
> > -	igt_output_set_pipe(output, pipe);
> > -
> > -	for (i = 0; i < count_modes; ++i)
> > -		igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh);
> > -
> > -	for (i = 0; i < count_modes; ++i) {
> > -		struct igt_fb fb = { 0 };
> > -		bool is_video_stable;
> > -		int screen_res_w, screen_res_h;
> > -
> > -		igt_info("Testing #%d %s %uHz\n", i, modes[i].name,
> > -			 modes[i].vrefresh);
> > -
> > -		/* Set the screen mode with the one we chose. */
> > -		create_fb_for_mode(data, &fb, &modes[i]);
> > -		enable_output(data, port, output, &modes[i], &fb);
> > -		is_video_stable = chamelium_port_wait_video_input_stable(
> > -			chamelium, port, 10);
> > -		igt_assert(is_video_stable);
> > -
> > -		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> > -					      &screen_res_h);
> > -		igt_assert_eq(screen_res_w, modes[i].hdisplay);
> > -		igt_assert_eq(screen_res_h, modes[i].vdisplay);
> > -
> > -		igt_remove_fb(data->drm_fd, &fb);
> > -	}
> > -
> > -	igt_modeset_disable_all_outputs(&data->display);
> > -	drmModeFreeConnector(connector);
> > -}
> > -
> > -#define for_each_port(p, port)            \
> > -	for (p = 0, port = data.ports[p]; \
> > -	     p < data.port_count;         \
> > -	     p++, port = data.ports[p])
> > -
> > -#define connector_subtest(name__, type__)                    \
> > -	igt_subtest(name__)                                  \
> > -		for_each_port(p, port)                       \
> > -			if (chamelium_port_get_type(port) == \
> > -			    DRM_MODE_CONNECTOR_ ## type__)
> > -
> > -#define connector_dynamic_subtest(name__, type__)            \
> > -	igt_subtest_with_dynamic(name__)                     \
> > -		for_each_port(p, port)                       \
> > -			if (chamelium_port_get_type(port) == \
> > -			    DRM_MODE_CONNECTOR_ ## type__)
> > -
> > -
> > -static data_t data;
> > -
> > -IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board");
> > -igt_main
> > -{
> > -	struct chamelium_port *port;
> > -	int p;
> > -	size_t i;
> > -
> > -	igt_fixture {
> > -		/* So fbcon doesn't try to reprobe things itself */
> > -		kmstest_set_vt_graphics_mode();
> > -
> > -		data.drm_fd = drm_open_driver_master(DRIVER_ANY);
> > -		igt_display_require(&data.display, data.drm_fd);
> > -		igt_require(data.display.is_atomic);
> > -
> > -		/*
> > -		 * XXX: disabling modeset, can be removed when
> > -		 * igt_display_require will start doing this for us
> > -		 */
> > -		igt_display_commit2(&data.display, COMMIT_ATOMIC);
> > -
> > -		/* we need to initalize chamelium after igt_display_require */
> > -		data.chamelium = chamelium_init(data.drm_fd, &data.display);
> > -		igt_require(data.chamelium);
> > -
> > -		data.ports = chamelium_get_ports(data.chamelium,
> > -						 &data.port_count);
> > -
> > -		for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) {
> > -			data.edids[i] = chamelium_new_edid(data.chamelium,
> > -							   igt_kms_get_custom_edid(i));
> > -		}
> > -	}
> > -
> > -	igt_describe("DisplayPort tests");
> > -	igt_subtest_group {
> > -		igt_fixture {
> > -			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> > -							    data.port_count, 1);
> > -		}
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("dp-hpd", DisplayPort)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_DP_HDMI,
> > -				     TEST_MODESET_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("dp-hpd-fast", DisplayPort)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("dp-hpd-enable-disable-mode", DisplayPort)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_ON_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("dp-hpd-with-enabled-mode", DisplayPort)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_ON);
> > -
> > -		igt_describe(igt_custom_edid_type_read_desc);
> > -		connector_subtest("dp-edid-read", DisplayPort) {
> > -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
> > -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
> > -		}
> > -
> > -		igt_describe(igt_edid_stress_resolution_desc);
> > -		connector_subtest("dp-edid-stress-resolution-4k", DisplayPort)
> > -			edid_stress_resolution(&data, port, DP_EDIDS_4K,
> > -					       ARRAY_SIZE(DP_EDIDS_4K));
> > -
> > -		igt_describe(igt_edid_stress_resolution_desc);
> > -		connector_subtest("dp-edid-stress-resolution-non-4k",
> > -				  DisplayPort)
> > -			edid_stress_resolution(&data, port, DP_EDIDS_NON_4K,
> > -					       ARRAY_SIZE(DP_EDIDS_NON_4K));
> > -
> > -		igt_describe(igt_edid_resolution_list_desc);
> > -		connector_subtest("dp-edid-resolution-list", DisplayPort)
> > -			edid_resolution_list(&data, port);
> > -
> > -		igt_describe(test_suspend_resume_hpd_desc);
> > -		connector_subtest("dp-hpd-after-suspend", DisplayPort)
> > -			test_suspend_resume_hpd(&data, port,
> > -						SUSPEND_STATE_MEM,
> > -						SUSPEND_TEST_NONE);
> > -
> > -		igt_describe(test_suspend_resume_hpd_desc);
> > -		connector_subtest("dp-hpd-after-hibernate", DisplayPort)
> > -			test_suspend_resume_hpd(&data, port,
> > -						SUSPEND_STATE_DISK,
> > -						SUSPEND_TEST_DEVICES);
> > -
> > -		igt_describe(test_hpd_storm_detect_desc);
> > -		connector_subtest("dp-hpd-storm", DisplayPort)
> > -			test_hpd_storm_detect(&data, port,
> > -					      HPD_STORM_PULSE_INTERVAL_DP);
> > -
> > -		igt_describe(test_hpd_storm_disable_desc);
> > -		connector_subtest("dp-hpd-storm-disable", DisplayPort)
> > -			test_hpd_storm_disable(&data, port,
> > -					       HPD_STORM_PULSE_INTERVAL_DP);
> > -
> > -		igt_describe(test_suspend_resume_edid_change_desc);
> > -		connector_subtest("dp-edid-change-during-suspend", DisplayPort)
> > -			test_suspend_resume_edid_change(&data, port,
> > -							SUSPEND_STATE_MEM,
> > -							SUSPEND_TEST_NONE,
> > -							IGT_CUSTOM_EDID_BASE,
> > -							IGT_CUSTOM_EDID_ALT);
> > -
> > -		igt_describe(test_suspend_resume_edid_change_desc);
> > -		connector_subtest("dp-edid-change-during-hibernate", DisplayPort)
> > -			test_suspend_resume_edid_change(&data, port,
> > -							SUSPEND_STATE_DISK,
> > -							SUSPEND_TEST_DEVICES,
> > -							IGT_CUSTOM_EDID_BASE,
> > -							IGT_CUSTOM_EDID_ALT);
> > -
> > -		igt_describe(test_display_all_modes_desc);
> > -		connector_subtest("dp-crc-single", DisplayPort)
> > -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > -					       CHAMELIUM_CHECK_CRC, 1);
> > -
> > -		igt_describe(test_display_one_mode_desc);
> > -		connector_subtest("dp-crc-fast", DisplayPort)
> > -			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> > -					      CHAMELIUM_CHECK_CRC, 1);
> > -
> > -		igt_describe(test_display_all_modes_desc);
> > -		connector_subtest("dp-crc-multiple", DisplayPort)
> > -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > -					       CHAMELIUM_CHECK_CRC, 3);
> > -
> > -		igt_describe(test_display_frame_dump_desc);
> > -		connector_subtest("dp-frame-dump", DisplayPort)
> > -			test_display_frame_dump(&data, port);
> > -
> > -		igt_describe(test_mode_timings_desc);
> > -		connector_subtest("dp-mode-timings", DisplayPort)
> > -			test_mode_timings(&data, port);
> > -
> > -		igt_describe(test_display_audio_desc);
> > -		connector_subtest("dp-audio", DisplayPort)
> > -			test_display_audio(&data, port, "HDMI",
> > -					   IGT_CUSTOM_EDID_DP_AUDIO);
> > -
> > -		igt_describe(test_display_audio_edid_desc);
> > -		connector_subtest("dp-audio-edid", DisplayPort)
> > -			test_display_audio_edid(&data, port,
> > -						IGT_CUSTOM_EDID_DP_AUDIO);
> > -
> > -		igt_describe(test_hotplug_for_each_pipe_desc);
> > -		connector_subtest("dp-hpd-for-each-pipe", DisplayPort)
> > -			test_hotplug_for_each_pipe(&data, port);
> > -	}
> > -
> > -	igt_describe("HDMI tests");
> > -	igt_subtest_group {
> > -		igt_fixture {
> > -			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_HDMIA,
> > -							    data.port_count, 1);
> > -		}
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("hdmi-hpd", HDMIA)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_DP_HDMI,
> > -				     TEST_MODESET_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("hdmi-hpd-fast", HDMIA)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_ON_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_ON);
> > -
> > -		igt_describe(igt_custom_edid_type_read_desc);
> > -		connector_subtest("hdmi-edid-read", HDMIA) {
> > -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
> > -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
> > -		}
> > -
> > -		igt_describe(igt_edid_stress_resolution_desc);
> > -		connector_subtest("hdmi-edid-stress-resolution-4k", HDMIA)
> > -			edid_stress_resolution(&data, port, HDMI_EDIDS_4K,
> > -					       ARRAY_SIZE(HDMI_EDIDS_4K));
> > -
> > -		igt_describe(igt_edid_stress_resolution_desc);
> > -		connector_subtest("hdmi-edid-stress-resolution-non-4k", HDMIA)
> > -			edid_stress_resolution(&data, port, HDMI_EDIDS_NON_4K,
> > -					       ARRAY_SIZE(HDMI_EDIDS_NON_4K));
> > -
> > -		igt_describe(test_suspend_resume_hpd_desc);
> > -		connector_subtest("hdmi-hpd-after-suspend", HDMIA)
> > -			test_suspend_resume_hpd(&data, port,
> > -						SUSPEND_STATE_MEM,
> > -						SUSPEND_TEST_NONE);
> > -
> > -		igt_describe(test_suspend_resume_hpd_desc);
> > -		connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
> > -			test_suspend_resume_hpd(&data, port,
> > -						SUSPEND_STATE_DISK,
> > -						SUSPEND_TEST_DEVICES);
> > -
> > -		igt_describe(test_hpd_storm_detect_desc);
> > -		connector_subtest("hdmi-hpd-storm", HDMIA)
> > -			test_hpd_storm_detect(&data, port,
> > -					      HPD_STORM_PULSE_INTERVAL_HDMI);
> > -
> > -		igt_describe(test_hpd_storm_disable_desc);
> > -		connector_subtest("hdmi-hpd-storm-disable", HDMIA)
> > -			test_hpd_storm_disable(&data, port,
> > -					       HPD_STORM_PULSE_INTERVAL_HDMI);
> > -
> > -		igt_describe(test_suspend_resume_edid_change_desc);
> > -		connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
> > -			test_suspend_resume_edid_change(&data, port,
> > -							SUSPEND_STATE_MEM,
> > -							SUSPEND_TEST_NONE,
> > -							IGT_CUSTOM_EDID_BASE,
> > -							IGT_CUSTOM_EDID_ALT);
> > -
> > -		igt_describe(test_suspend_resume_edid_change_desc);
> > -		connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
> > -			test_suspend_resume_edid_change(&data, port,
> > -							SUSPEND_STATE_DISK,
> > -							SUSPEND_TEST_DEVICES,
> > -							IGT_CUSTOM_EDID_BASE,
> > -							IGT_CUSTOM_EDID_ALT);
> > -
> > -		igt_describe(test_display_all_modes_desc);
> > -		connector_subtest("hdmi-crc-single", HDMIA)
> > -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > -					       CHAMELIUM_CHECK_CRC, 1);
> > -
> > -		igt_describe(test_display_one_mode_desc);
> > -		connector_subtest("hdmi-crc-fast", HDMIA)
> > -			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> > -					      CHAMELIUM_CHECK_CRC, 1);
> > -
> > -		igt_describe(test_display_all_modes_desc);
> > -		connector_subtest("hdmi-crc-multiple", HDMIA)
> > -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > -					       CHAMELIUM_CHECK_CRC, 3);
> > -
> > -		igt_describe(test_display_one_mode_desc);
> > -		connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA) {
> > -			int k;
> > -			igt_output_t *output;
> > -			igt_plane_t *primary;
> > -
> > -			output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE);
> > -			primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -			igt_assert(primary);
> > -
> > -			for (k = 0; k < primary->format_mod_count; k++) {
> > -				if (!igt_fb_supported_format(primary->formats[k]))
> > -					continue;
> > -
> > -				if (igt_format_is_yuv(primary->formats[k]))
> > -					continue;
> > -
> > -				if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
> > -					continue;
> > -
> > -				igt_dynamic_f("%s", igt_format_str(primary->formats[k]))
> > -					test_display_one_mode(&data, port, primary->formats[k],
> > -							      CHAMELIUM_CHECK_CRC, 1);
> > -			}
> > -		}
> > -
> > -		igt_describe(test_display_planes_random_desc);
> > -		connector_subtest("hdmi-crc-planes-random", HDMIA)
> > -			test_display_planes_random(&data, port,
> > -						   CHAMELIUM_CHECK_CRC);
> > -
> > -		igt_describe(test_display_one_mode_desc);
> > -		connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA) {
> > -			int k;
> > -			igt_output_t *output;
> > -			igt_plane_t *primary;
> > -
> > -			output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE);
> > -			primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > -			igt_assert(primary);
> > -
> > -			for (k = 0; k < primary->format_mod_count; k++) {
> > -				if (!igt_fb_supported_format(primary->formats[k]))
> > -					continue;
> > -
> > -				if (!igt_format_is_yuv(primary->formats[k]))
> > -					continue;
> > -
> > -				if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
> > -					continue;
> > -
> > -				igt_dynamic_f("%s", igt_format_str(primary->formats[k]))
> > -					test_display_one_mode(&data, port, primary->formats[k],
> > -							      CHAMELIUM_CHECK_CHECKERBOARD, 1);
> > -			}
> > -		}
> > -
> > -		igt_describe(test_display_planes_random_desc);
> > -		connector_subtest("hdmi-cmp-planes-random", HDMIA)
> > -			test_display_planes_random(&data, port,
> > -						   CHAMELIUM_CHECK_CHECKERBOARD);
> > -
> > -		igt_describe(test_display_frame_dump_desc);
> > -		connector_subtest("hdmi-frame-dump", HDMIA)
> > -			test_display_frame_dump(&data, port);
> > -
> > -		igt_describe(test_mode_timings_desc);
> > -		connector_subtest("hdmi-mode-timings", HDMIA)
> > -			test_mode_timings(&data, port);
> > -
> > -		igt_describe(test_display_audio_desc);
> > -		connector_subtest("hdmi-audio", HDMIA)
> > -			test_display_audio(&data, port, "HDMI",
> > -					   IGT_CUSTOM_EDID_HDMI_AUDIO);
> > -
> > -		igt_describe(test_display_audio_edid_desc);
> > -		connector_subtest("hdmi-audio-edid", HDMIA)
> > -			test_display_audio_edid(&data, port,
> > -						IGT_CUSTOM_EDID_HDMI_AUDIO);
> > -
> > -		igt_describe(test_display_aspect_ratio_desc);
> > -		connector_subtest("hdmi-aspect-ratio", HDMIA)
> > -			test_display_aspect_ratio(&data, port);
> > -
> > -		igt_describe(test_hotplug_for_each_pipe_desc);
> > -		connector_subtest("hdmi-hpd-for-each-pipe", HDMIA)
> > -			test_hotplug_for_each_pipe(&data, port);
> > -	}
> > -
> > -	igt_describe("VGA tests");
> > -	igt_subtest_group {
> > -		igt_fixture {
> > -			chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_VGA,
> > -							    data.port_count, 1);
> > -		}
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("vga-hpd", VGA)
> > -			test_hotplug(&data, port, HPD_TOGGLE_COUNT_VGA,
> > -				     TEST_MODESET_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("vga-hpd-fast", VGA)
> > -			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("vga-hpd-enable-disable-mode", VGA)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_ON_OFF);
> > -
> > -		igt_describe(test_basic_hotplug_desc);
> > -		connector_subtest("vga-hpd-with-enabled-mode", VGA)
> > -			test_hotplug(&data, port,
> > -				     HPD_TOGGLE_COUNT_FAST,
> > -				     TEST_MODESET_ON);
> > -
> > -		igt_describe(igt_custom_edid_type_read_desc);
> > -		connector_subtest("vga-edid-read", VGA) {
> > -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE);
> > -			igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT);
> > -		}
> > -
> > -		igt_describe(test_suspend_resume_hpd_desc);
> > -		connector_subtest("vga-hpd-after-suspend", VGA)
> > -			test_suspend_resume_hpd(&data, port,
> > -						SUSPEND_STATE_MEM,
> > -						SUSPEND_TEST_NONE);
> > -
> > -		igt_describe(test_suspend_resume_hpd_desc);
> > -		connector_subtest("vga-hpd-after-hibernate", VGA)
> > -			test_suspend_resume_hpd(&data, port,
> > -						SUSPEND_STATE_DISK,
> > -						SUSPEND_TEST_DEVICES);
> > -
> > -		igt_describe(test_hpd_without_ddc_desc);
> > -		connector_subtest("vga-hpd-without-ddc", VGA)
> > -			test_hpd_without_ddc(&data, port);
> > -
> > -		igt_describe(test_display_all_modes_desc);
> > -		connector_subtest("vga-frame-dump", VGA)
> > -			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > -					       CHAMELIUM_CHECK_ANALOG, 1);
> > -	}
> > -
> > -	igt_describe("Tests that operate on all connectors");
> > -	igt_subtest_group {
> > -
> > -		igt_fixture {
> > -			igt_require(data.port_count);
> > -		}
> > -
> > -		igt_describe(test_suspend_resume_hpd_common_desc);
> > -		igt_subtest("common-hpd-after-suspend")
> > -			test_suspend_resume_hpd_common(&data,
> > -						       SUSPEND_STATE_MEM,
> > -						       SUSPEND_TEST_NONE);
> > -
> > -		igt_describe(test_suspend_resume_hpd_common_desc);
> > -		igt_subtest("common-hpd-after-hibernate")
> > -			test_suspend_resume_hpd_common(&data,
> > -						       SUSPEND_STATE_DISK,
> > -						       SUSPEND_TEST_DEVICES);
> > -	}
> > -
> > -	igt_describe(test_hotplug_for_each_pipe_desc);
> > -	connector_subtest("vga-hpd-for-each-pipe", VGA)
> > -		test_hotplug_for_each_pipe(&data, port);
> > -
> > -	igt_fixture {
> > -		igt_display_fini(&data.display);
> > -		close(data.drm_fd);
> > -	}
> > -}
> > diff --git a/tests/chamelium/kms_chamelium_audio.c b/tests/chamelium/kms_chamelium_audio.c
> > new file mode 100644
> > index 00000000..4d13744c
> > --- /dev/null
> > +++ b/tests/chamelium/kms_chamelium_audio.c
> > @@ -0,0 +1,858 @@
> > +// SPDX-License-Identifier: MIT
> > +/*
> > + * A Chamelium test for testing the Audio functionality.
> > + *
> > + * Copyright 2022 Google LLC.
> > + *
> > + * Authors: Mark Yacoub <markyacoub@chromium.org>
> > + */
> > +
> > +#include "igt_eld.h"
> > +#include "igt_infoframe.h"
> > +#include "kms_chamelium_helper.h"
> > +
> > +/* Playback parameters control the audio signal we synthesize and send */
> > +#define PLAYBACK_CHANNELS 2
> > +#define PLAYBACK_SAMPLES 1024
> > +
> > +/* Capture paremeters control the audio signal we receive */
> > +#define CAPTURE_SAMPLES 2048
> > +
> > +#define AUDIO_TIMEOUT 2000 /* ms */
> > +/* A streak of 3 gives confidence that the signal is good. */
> > +#define MIN_STREAK 3
> > +
> > +#define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */
> > +#define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */
> > +#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */
> > +
> > +struct audio_state {
> > +	struct alsa *alsa;
> > +	struct chamelium *chamelium;
> > +	struct chamelium_port *port;
> > +	struct chamelium_stream *stream;
> > +
> > +	/* The capture format is only available after capture has started. */
> > +	struct {
> > +		snd_pcm_format_t format;
> > +		int channels;
> > +		int rate;
> > +	} playback, capture;
> > +
> > +	char *name;
> > +	struct audio_signal *signal; /* for frequencies test only */
> > +	int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS];
> > +
> > +	size_t recv_pages;
> > +	int msec;
> > +
> > +	int dump_fd;
> > +	char *dump_path;
> > +
> > +	pthread_t thread;
> > +	atomic_bool run;
> > +	atomic_bool positive; /* for pulse test only */
> > +};
> > +
> > +/* TODO: enable >48KHz rates, these are not reliable */
> > +static int test_sampling_rates[] = {
> > +	32000, 44100, 48000,
> > +	/* 88200, */
> > +	/* 96000, */
> > +	/* 176400, */
> > +	/* 192000, */
> > +};
> > +
> > +static int test_sampling_rates_count =
> > +	sizeof(test_sampling_rates) / sizeof(int);
> > +
> > +/* Test frequencies (Hz): a sine signal will be generated for each.
> > + *
> > + * Depending on the sampling rate chosen, it might not be possible to properly
> > + * detect the generated sine (see Nyquist–Shannon sampling theorem).
> > + * Frequencies that can't be reliably detected will be automatically pruned in
> > + * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be
> > + * tested with a 192KHz sampling rate.
> > + */
> > +static int test_frequencies[] = {
> > +	300, 600, 1200, 10000, 80000,
> > +};
> > +
> > +static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> > +
> > +static const snd_pcm_format_t test_formats[] = {
> > +	SND_PCM_FORMAT_S16_LE,
> > +	SND_PCM_FORMAT_S24_LE,
> > +	SND_PCM_FORMAT_S32_LE,
> > +};
> > +
> > +static const size_t test_formats_count =
> > +	sizeof(test_formats) / sizeof(test_formats[0]);
> > +
> > +static void audio_state_init(struct audio_state *state, chamelium_data_t *data,
> > +			     struct alsa *alsa, struct chamelium_port *port,
> > +			     snd_pcm_format_t format, int channels, int rate)
> > +{
> > +	memset(state, 0, sizeof(*state));
> > +	state->dump_fd = -1;
> > +
> > +	state->alsa = alsa;
> > +	state->chamelium = data->chamelium;
> > +	state->port = port;
> > +
> > +	state->playback.format = format;
> > +	state->playback.channels = channels;
> > +	state->playback.rate = rate;
> > +
> > +	alsa_configure_output(alsa, format, channels, rate);
> > +
> > +	state->stream = chamelium_stream_init();
> > +	igt_assert_f(state->stream,
> > +		     "Failed to initialize Chamelium stream client\n");
> > +}
> > +
> > +static void audio_state_fini(struct audio_state *state)
> > +{
> > +	chamelium_stream_deinit(state->stream);
> > +	free(state->name);
> > +}
> > +
> > +static void *run_audio_thread(void *data)
> > +{
> > +	struct alsa *alsa = data;
> > +
> > +	alsa_run(alsa, -1);
> > +	return NULL;
> > +}
> > +
> > +static void audio_state_start(struct audio_state *state, const char *name)
> > +{
> > +	int ret;
> > +	bool ok;
> > +	size_t i, j;
> > +	enum chamelium_stream_realtime_mode stream_mode;
> > +	char dump_suffix[64];
> > +
> > +	free(state->name);
> > +	state->name = strdup(name);
> > +	state->recv_pages = 0;
> > +	state->msec = 0;
> > +
> > +	igt_debug("Starting %s test with playback format %s, "
> > +		  "sampling rate %d Hz and %d channels\n",
> > +		  name, snd_pcm_format_name(state->playback.format),
> > +		  state->playback.rate, state->playback.channels);
> > +
> > +	chamelium_start_capturing_audio(state->chamelium, state->port, false);
> > +
> > +	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
> > +	ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode);
> > +	igt_assert_f(ok, "Failed to start streaming audio capture\n");
> > +
> > +	/* Start playing audio */
> > +	state->run = true;
> > +	ret = pthread_create(&state->thread, NULL, run_audio_thread,
> > +			     state->alsa);
> > +	igt_assert_f(ret == 0, "Failed to start audio playback thread\n");
> > +
> > +	/* The Chamelium device only supports this PCM format. */
> > +	state->capture.format = SND_PCM_FORMAT_S32_LE;
> > +
> > +	/* Only after we've started playing audio, we can retrieve the capture
> > +	 * format used by the Chamelium device. */
> > +	chamelium_get_audio_format(state->chamelium, state->port,
> > +				   &state->capture.rate,
> > +				   &state->capture.channels);
> > +	if (state->capture.rate == 0) {
> > +		igt_debug("Audio receiver doesn't indicate the capture "
> > +			  "sampling rate, assuming it's %d Hz\n",
> > +			  state->playback.rate);
> > +		state->capture.rate = state->playback.rate;
> > +	}
> > +
> > +	chamelium_get_audio_channel_mapping(state->chamelium, state->port,
> > +					    state->channel_mapping);
> > +	/* Make sure we can capture all channels we send. */
> > +	for (i = 0; i < state->playback.channels; i++) {
> > +		ok = false;
> > +		for (j = 0; j < state->capture.channels; j++) {
> > +			if (state->channel_mapping[j] == i) {
> > +				ok = true;
> > +				break;
> > +			}
> > +		}
> > +		igt_assert_f(ok, "Cannot capture all channels\n");
> > +	}
> > +
> > +	if (igt_frame_dump_is_enabled()) {
> > +		snprintf(dump_suffix, sizeof(dump_suffix),
> > +			 "capture-%s-%s-%dch-%dHz", name,
> > +			 snd_pcm_format_name(state->playback.format),
> > +			 state->playback.channels, state->playback.rate);
> > +
> > +		state->dump_fd = audio_create_wav_file_s32_le(
> > +			dump_suffix, state->capture.rate,
> > +			state->capture.channels, &state->dump_path);
> > +		igt_assert_f(state->dump_fd >= 0,
> > +			     "Failed to create audio dump file\n");
> > +	}
> > +}
> > +
> > +static void audio_state_receive(struct audio_state *state, int32_t **recv,
> > +				size_t *recv_len)
> > +{
> > +	bool ok;
> > +	size_t page_count;
> > +	size_t recv_size;
> > +
> > +	ok = chamelium_stream_receive_realtime_audio(state->stream, &page_count,
> > +						     recv, recv_len);
> > +	igt_assert_f(ok, "Failed to receive audio from stream server\n");
> > +
> > +	state->msec = state->recv_pages * *recv_len /
> > +		      (double)state->capture.channels /
> > +		      (double)state->capture.rate * 1000;
> > +	state->recv_pages++;
> > +
> > +	if (state->dump_fd >= 0) {
> > +		recv_size = *recv_len * sizeof(int32_t);
> > +		igt_assert_f(write(state->dump_fd, *recv, recv_size) ==
> > +				     recv_size,
> > +			     "Failed to write to audio dump file\n");
> > +	}
> > +}
> > +
> > +static void audio_state_stop(struct audio_state *state, bool success)
> > +{
> > +	bool ok;
> > +	int ret;
> > +	struct chamelium_audio_file *audio_file;
> > +	enum igt_log_level log_level;
> > +
> > +	igt_debug("Stopping audio playback\n");
> > +	state->run = false;
> > +	ret = pthread_join(state->thread, NULL);
> > +	igt_assert_f(ret == 0, "Failed to join audio playback thread\n");
> > +
> > +	ok = chamelium_stream_stop_realtime_audio(state->stream);
> > +	igt_assert_f(ok, "Failed to stop streaming audio capture\n");
> > +
> > +	audio_file =
> > +		chamelium_stop_capturing_audio(state->chamelium, state->port);
> > +	if (audio_file) {
> > +		igt_debug("Audio file saved on the Chamelium in %s\n",
> > +			  audio_file->path);
> > +		chamelium_destroy_audio_file(audio_file);
> > +	}
> > +
> > +	if (state->dump_fd >= 0) {
> > +		close(state->dump_fd);
> > +		state->dump_fd = -1;
> > +
> > +		if (success) {
> > +			/* Test succeeded, no need to keep the captured data */
> > +			unlink(state->dump_path);
> > +		} else
> > +			igt_debug("Saved captured audio data to %s\n",
> > +				  state->dump_path);
> > +		free(state->dump_path);
> > +		state->dump_path = NULL;
> > +	}
> > +
> > +	if (success)
> > +		log_level = IGT_LOG_DEBUG;
> > +	else
> > +		log_level = IGT_LOG_CRITICAL;
> > +
> > +	igt_log(IGT_LOG_DOMAIN, log_level,
> > +		"Audio %s test result for format %s, "
> > +		"sampling rate %d Hz and %d channels: %s\n",
> > +		state->name, snd_pcm_format_name(state->playback.format),
> > +		state->playback.rate, state->playback.channels,
> > +		success ? "ALL GREEN" : "FAILED");
> > +}
> > +
> > +static void check_audio_infoframe(struct audio_state *state)
> > +{
> > +	struct chamelium_infoframe *infoframe;
> > +	struct infoframe_audio infoframe_audio;
> > +	struct infoframe_audio expected = { 0 };
> > +	bool ok;
> > +
> > +	if (!chamelium_supports_get_last_infoframe(state->chamelium)) {
> > +		igt_debug("Skipping audio InfoFrame check: "
> > +			  "Chamelium board doesn't support GetLastInfoFrame\n");
> > +		return;
> > +	}
> > +
> > +	expected.coding_type = INFOFRAME_AUDIO_CT_PCM;
> > +	expected.channel_count = state->playback.channels;
> > +	expected.sampling_freq = state->playback.rate;
> > +	expected.sample_size = snd_pcm_format_width(state->playback.format);
> > +
> > +	infoframe = chamelium_get_last_infoframe(state->chamelium, state->port,
> > +						 CHAMELIUM_INFOFRAME_AUDIO);
> > +	if (infoframe == NULL && state->playback.channels <= 2) {
> > +		/* Audio InfoFrames are optional for mono and stereo audio */
> > +		igt_debug("Skipping audio InfoFrame check: "
> > +			  "no InfoFrame received\n");
> > +		return;
> > +	}
> > +	igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n");
> > +
> > +	ok = infoframe_audio_parse(&infoframe_audio, infoframe->version,
> > +				   infoframe->payload, infoframe->payload_size);
> > +	chamelium_infoframe_destroy(infoframe);
> > +	igt_assert_f(ok, "failed to parse audio InfoFrame\n");
> > +
> > +	igt_debug("Checking audio InfoFrame:\n");
> > +	igt_debug("coding_type: got %d, expected %d\n",
> > +		  infoframe_audio.coding_type, expected.coding_type);
> > +	igt_debug("channel_count: got %d, expected %d\n",
> > +		  infoframe_audio.channel_count, expected.channel_count);
> > +	igt_debug("sampling_freq: got %d, expected %d\n",
> > +		  infoframe_audio.sampling_freq, expected.sampling_freq);
> > +	igt_debug("sample_size: got %d, expected %d\n",
> > +		  infoframe_audio.sample_size, expected.sample_size);
> > +
> > +	if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED)
> > +		igt_assert(infoframe_audio.coding_type == expected.coding_type);
> > +	if (infoframe_audio.channel_count >= 0)
> > +		igt_assert(infoframe_audio.channel_count ==
> > +			   expected.channel_count);
> > +	if (infoframe_audio.sampling_freq >= 0)
> > +		igt_assert(infoframe_audio.sampling_freq ==
> > +			   expected.sampling_freq);
> > +	if (infoframe_audio.sample_size >= 0)
> > +		igt_assert(infoframe_audio.sample_size == expected.sample_size);
> > +}
> > +
> > +static int audio_output_frequencies_callback(void *data, void *buffer,
> > +					     int samples)
> > +{
> > +	struct audio_state *state = data;
> > +	double *tmp;
> > +	size_t len;
> > +
> > +	len = samples * state->playback.channels;
> > +	tmp = malloc(len * sizeof(double));
> > +	audio_signal_fill(state->signal, tmp, samples);
> > +	audio_convert_to(buffer, tmp, len, state->playback.format);
> > +	free(tmp);
> > +
> > +	return state->run ? 0 : -1;
> > +}
> > +
> > +static bool test_audio_frequencies(struct audio_state *state)
> > +{
> > +	int freq, step;
> > +	int32_t *recv, *buf;
> > +	double *channel;
> > +	size_t i, j, streak;
> > +	size_t recv_len, buf_len, buf_cap, channel_len;
> > +	bool success;
> > +	int capture_chan;
> > +
> > +	state->signal = audio_signal_init(state->playback.channels,
> > +					  state->playback.rate);
> > +	igt_assert_f(state->signal, "Failed to initialize audio signal\n");
> > +
> > +	/* We'll choose different frequencies per channel to make sure they are
> > +	 * independent from each other. To do so, we'll add a different offset
> > +	 * to the base frequencies for each channel. We need to choose a big
> > +	 * enough offset so that we're sure to detect mixed up channels. We
> > +	 * choose an offset of two 2 bins in the final FFT to enforce a clear
> > +	 * difference.
> > +	 *
> > +	 * Note that we assume capture_rate == playback_rate. We'll assert this
> > +	 * later on. We cannot retrieve the capture rate before starting
> > +	 * playing audio, so we don't really have the choice.
> > +	 */
> > +	step = 2 * state->playback.rate / CAPTURE_SAMPLES;
> > +	for (i = 0; i < test_frequencies_count; i++) {
> > +		for (j = 0; j < state->playback.channels; j++) {
> > +			freq = test_frequencies[i] + j * step;
> > +			audio_signal_add_frequency(state->signal, freq, j);
> > +		}
> > +	}
> > +	audio_signal_synthesize(state->signal);
> > +
> > +	alsa_register_output_callback(state->alsa,
> > +				      audio_output_frequencies_callback, state,
> > +				      PLAYBACK_SAMPLES);
> > +
> > +	audio_state_start(state, "frequencies");
> > +
> > +	igt_assert_f(state->capture.rate == state->playback.rate,
> > +		     "Capture rate (%dHz) doesn't match playback rate (%dHz)\n",
> > +		     state->capture.rate, state->playback.rate);
> > +
> > +	/* Needs to be a multiple of 128, because that's the number of samples
> > +	 * we get per channel each time we receive an audio page from the
> > +	 * Chamelium device.
> > +	 *
> > +	 * Additionally, this value needs to be high enough to guarantee we
> > +	 * capture a full period of each sine we generate. If we capture 2048
> > +	 * samples at a 192KHz sampling rate, we get a full period for a >94Hz
> > +	 * sines. For lower sampling rates, the capture duration will be
> > +	 * longer.
> > +	 */
> > +	channel_len = CAPTURE_SAMPLES;
> > +	channel = malloc(sizeof(double) * channel_len);
> > +
> > +	buf_cap = state->capture.channels * channel_len;
> > +	buf = malloc(sizeof(int32_t) * buf_cap);
> > +	buf_len = 0;
> > +
> > +	recv = NULL;
> > +	recv_len = 0;
> > +
> > +	success = false;
> > +	streak = 0;
> > +	while (!success && state->msec < AUDIO_TIMEOUT) {
> > +		audio_state_receive(state, &recv, &recv_len);
> > +
> > +		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
> > +		buf_len += recv_len;
> > +
> > +		if (buf_len < buf_cap)
> > +			continue;
> > +		igt_assert(buf_len == buf_cap);
> > +
> > +		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> > +
> > +		for (j = 0; j < state->playback.channels; j++) {
> > +			capture_chan = state->channel_mapping[j];
> > +			igt_assert(capture_chan >= 0);
> > +			igt_debug("Processing channel %zu (captured as "
> > +				  "channel %d)\n",
> > +				  j, capture_chan);
> > +
> > +			audio_extract_channel_s32_le(channel, channel_len, buf,
> > +						     buf_len,
> > +						     state->capture.channels,
> > +						     capture_chan);
> > +
> > +			if (audio_signal_detect(state->signal,
> > +						state->capture.rate, j, channel,
> > +						channel_len))
> > +				streak++;
> > +			else
> > +				streak = 0;
> > +		}
> > +
> > +		buf_len = 0;
> > +
> > +		success = streak == MIN_STREAK * state->playback.channels;
> > +	}
> > +
> > +	audio_state_stop(state, success);
> > +
> > +	free(recv);
> > +	free(buf);
> > +	free(channel);
> > +	audio_signal_fini(state->signal);
> > +
> > +	check_audio_infoframe(state);
> > +
> > +	return success;
> > +}
> > +
> > +static int audio_output_flatline_callback(void *data, void *buffer, int samples)
> > +{
> > +	struct audio_state *state = data;
> > +	double *tmp;
> > +	size_t len, i;
> > +
> > +	len = samples * state->playback.channels;
> > +	tmp = malloc(len * sizeof(double));
> > +	for (i = 0; i < len; i++)
> > +		tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE;
> > +	audio_convert_to(buffer, tmp, len, state->playback.format);
> > +	free(tmp);
> > +
> > +	return state->run ? 0 : -1;
> > +}
> > +
> > +static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos)
> > +{
> > +	double expected, min, max;
> > +	size_t i;
> > +	bool ok;
> > +
> > +	min = max = NAN;
> > +	for (i = 0; i < buf_len; i++) {
> > +		if (isnan(min) || buf[i] < min)
> > +			min = buf[i];
> > +		if (isnan(max) || buf[i] > max)
> > +			max = buf[i];
> > +	}
> > +
> > +	expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE;
> > +	ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY &&
> > +	      max <= expected + FLATLINE_AMPLITUDE_ACCURACY);
> > +	if (ok)
> > +		igt_debug("Flatline wave amplitude detected\n");
> > +	else
> > +		igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n",
> > +			  min, max);
> > +	return ok;
> > +}
> > +
> > +static ssize_t detect_falling_edge(double *buf, size_t buf_len)
> > +{
> > +	size_t i;
> > +
> > +	for (i = 0; i < buf_len; i++) {
> > +		if (buf[i] < 0)
> > +			return i;
> > +	}
> > +
> > +	return -1;
> > +}
> > +
> > +/** test_audio_flatline:
> > + *
> > + * Send a constant value (one positive, then a negative one) and check that:
> > + *
> > + * - The amplitude of the flatline is correct
> > + * - All channels switch from a positive signal to a negative one at the same
> > + *   time (ie. all channels are aligned)
> > + */
> > +static bool test_audio_flatline(struct audio_state *state)
> > +{
> > +	bool success, amp_success, align_success;
> > +	int32_t *recv;
> > +	size_t recv_len, i, channel_len;
> > +	ssize_t j;
> > +	int streak, capture_chan;
> > +	double *channel;
> > +	int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS];
> > +
> > +	alsa_register_output_callback(state->alsa,
> > +				      audio_output_flatline_callback, state,
> > +				      PLAYBACK_SAMPLES);
> > +
> > +	/* Start by sending a positive signal */
> > +	state->positive = true;
> > +
> > +	audio_state_start(state, "flatline");
> > +
> > +	for (i = 0; i < state->playback.channels; i++)
> > +		falling_edges[i] = -1;
> > +
> > +	recv = NULL;
> > +	recv_len = 0;
> > +	amp_success = false;
> > +	streak = 0;
> > +	while (!amp_success && state->msec < AUDIO_TIMEOUT) {
> > +		audio_state_receive(state, &recv, &recv_len);
> > +
> > +		igt_debug("Detecting audio signal, t=%d msec\n", state->msec);
> > +
> > +		for (i = 0; i < state->playback.channels; i++) {
> > +			capture_chan = state->channel_mapping[i];
> > +			igt_assert(capture_chan >= 0);
> > +			igt_debug("Processing channel %zu (captured as "
> > +				  "channel %d)\n",
> > +				  i, capture_chan);
> > +
> > +			channel_len = audio_extract_channel_s32_le(
> > +				NULL, 0, recv, recv_len,
> > +				state->capture.channels, capture_chan);
> > +			channel = malloc(channel_len * sizeof(double));
> > +			audio_extract_channel_s32_le(channel, channel_len, recv,
> > +						     recv_len,
> > +						     state->capture.channels,
> > +						     capture_chan);
> > +
> > +			/* Check whether the amplitude is fine */
> > +			if (detect_flatline_amplitude(channel, channel_len,
> > +						      state->positive))
> > +				streak++;
> > +			else
> > +				streak = 0;
> > +
> > +			/* If we're now sending a negative signal, detect the
> > +			 * falling edge */
> > +			j = detect_falling_edge(channel, channel_len);
> > +			if (!state->positive && j >= 0) {
> > +				falling_edges[i] =
> > +					recv_len * state->recv_pages + j;
> > +			}
> > +
> > +			free(channel);
> > +		}
> > +
> > +		amp_success = streak == MIN_STREAK * state->playback.channels;
> > +
> > +		if (amp_success && state->positive) {
> > +			/* Switch to a negative signal after we've detected the
> > +			 * positive one. */
> > +			state->positive = false;
> > +			amp_success = false;
> > +			streak = 0;
> > +			igt_debug("Switching to negative square wave\n");
> > +		}
> > +	}
> > +
> > +	/* Check alignment between all channels by comparing the index of the
> > +	 * falling edge. */
> > +	align_success = true;
> > +	for (i = 0; i < state->playback.channels; i++) {
> > +		if (falling_edges[i] < 0) {
> > +			igt_critical(
> > +				"Falling edge not detected for channel %zu\n",
> > +				i);
> > +			align_success = false;
> > +			continue;
> > +		}
> > +
> > +		if (abs(falling_edges[0] - falling_edges[i]) >
> > +		    FLATLINE_ALIGN_ACCURACY) {
> > +			igt_critical("Channel alignment mismatch: "
> > +				     "channel 0 has a falling edge at index %d "
> > +				     "while channel %zu has index %d\n",
> > +				     falling_edges[0], i, falling_edges[i]);
> > +			align_success = false;
> > +		}
> > +	}
> > +
> > +	success = amp_success && align_success;
> > +	audio_state_stop(state, success);
> > +
> > +	free(recv);
> > +
> > +	return success;
> > +}
> > +
> > +static bool check_audio_configuration(struct alsa *alsa,
> > +				      snd_pcm_format_t format, int channels,
> > +				      int sampling_rate)
> > +{
> > +	if (!alsa_test_output_configuration(alsa, format, channels,
> > +					    sampling_rate)) {
> > +		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> > +			  "and %d channels because at least one of the "
> > +			  "selected output devices doesn't support this "
> > +			  "configuration\n",
> > +			  snd_pcm_format_name(format), sampling_rate, channels);
> > +		return false;
> > +	}
> > +	/* TODO: the Chamelium device sends a malformed signal for some audio
> > +	 * configurations. See crbug.com/950917 */
> > +	if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) ||
> > +	    channels > 2) {
> > +		igt_debug("Skipping test with format %s, sampling rate %d Hz "
> > +			  "and %d channels because the Chamelium device "
> > +			  "doesn't support this configuration\n",
> > +			  snd_pcm_format_name(format), sampling_rate, channels);
> > +		return false;
> > +	}
> > +	return true;
> > +}
> > +
> > +static const char test_display_audio_desc[] =
> > +	"Playback various audio signals with various audio formats/rates, "
> > +	"capture them and check they are correct";
> > +static void test_display_audio(chamelium_data_t *data,
> > +			       struct chamelium_port *port,
> > +			       const char *audio_device,
> > +			       enum igt_custom_edid_type edid)
> > +{
> > +	bool run, success;
> > +	struct alsa *alsa;
> > +	int ret;
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +	struct igt_fb fb;
> > +	drmModeModeInfo *mode;
> > +	drmModeConnector *connector;
> > +	int fb_id, i, j;
> > +	int channels, sampling_rate;
> > +	snd_pcm_format_t format;
> > +	struct audio_state state;
> > +
> > +	igt_require(alsa_has_exclusive_access());
> > +
> > +	/* Old Chamelium devices need an update for DisplayPort audio and
> > +	 * chamelium_get_audio_format support. */
> > +	igt_require(chamelium_has_audio_support(data->chamelium, port));
> > +
> > +	alsa = alsa_init();
> > +	igt_assert(alsa);
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	output = chamelium_prepare_output(data, port, edid);
> > +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	/* Enable the output because the receiver won't try to receive audio if
> > +	 * it doesn't receive video. */
> > +	igt_assert(connector->count_modes > 0);
> > +	mode = &connector->modes[0];
> > +
> > +	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
> > +					    mode->vdisplay, DRM_FORMAT_XRGB8888,
> > +					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> > +					    &fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	chamelium_enable_output(data, port, output, mode, &fb);
> > +
> > +	run = false;
> > +	success = true;
> > +	for (i = 0; i < test_sampling_rates_count; i++) {
> > +		for (j = 0; j < test_formats_count; j++) {
> > +			ret = alsa_open_output(alsa, audio_device);
> > +			igt_assert_f(ret >= 0, "Failed to open ALSA output\n");
> > +
> > +			/* TODO: playback on all 8 available channels (this
> > +			 * isn't supported by Chamelium devices yet, see
> > +			 * https://crbug.com/950917) */
> > +			format = test_formats[j];
> > +			channels = PLAYBACK_CHANNELS;
> > +			sampling_rate = test_sampling_rates[i];
> > +
> > +			if (!check_audio_configuration(alsa, format, channels,
> > +						       sampling_rate))
> > +				continue;
> > +
> > +			run = true;
> > +
> > +			audio_state_init(&state, data, alsa, port, format,
> > +					 channels, sampling_rate);
> > +			success &= test_audio_frequencies(&state);
> > +			success &= test_audio_flatline(&state);
> > +			audio_state_fini(&state);
> > +
> > +			alsa_close_output(alsa);
> > +		}
> > +	}
> > +
> > +	/* Make sure we tested at least one frequency and format. */
> > +	igt_assert(run);
> > +	/* Make sure all runs were successful. */
> > +	igt_assert(success);
> > +
> > +	igt_remove_fb(data->drm_fd, &fb);
> > +
> > +	drmModeFreeConnector(connector);
> > +
> > +	free(alsa);
> > +}
> > +
> > +static const char test_display_audio_edid_desc[] =
> > +	"Plug a connector with an EDID suitable for audio, check ALSA's "
> > +	"EDID-Like Data reports the correct audio parameters";
> > +static void test_display_audio_edid(chamelium_data_t *data,
> > +				    struct chamelium_port *port,
> > +				    enum igt_custom_edid_type edid)
> > +{
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +	struct igt_fb fb;
> > +	drmModeModeInfo *mode;
> > +	drmModeConnector *connector;
> > +	int fb_id;
> > +	struct eld_entry eld;
> > +	struct eld_sad *sad;
> > +
> > +	igt_require(eld_is_supported());
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	output = chamelium_prepare_output(data, port, edid);
> > +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	/* Enable the output because audio cannot be played on inactive
> > +	 * connectors. */
> > +	igt_assert(connector->count_modes > 0);
> > +	mode = &connector->modes[0];
> > +
> > +	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
> > +					    mode->vdisplay, DRM_FORMAT_XRGB8888,
> > +					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> > +					    &fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	chamelium_enable_output(data, port, output, mode, &fb);
> > +
> > +	igt_assert(eld_get_igt(&eld));
> > +	igt_assert(eld.sads_len == 1);
> > +
> > +	sad = &eld.sads[0];
> > +	igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM);
> > +	igt_assert(sad->channels == 2);
> > +	igt_assert(sad->rates ==
> > +		   (CEA_SAD_SAMPLING_RATE_32KHZ | CEA_SAD_SAMPLING_RATE_44KHZ |
> > +		    CEA_SAD_SAMPLING_RATE_48KHZ));
> > +	igt_assert(sad->bits ==
> > +		   (CEA_SAD_SAMPLE_SIZE_16 | CEA_SAD_SAMPLE_SIZE_20 |
> > +		    CEA_SAD_SAMPLE_SIZE_24));
> > +
> > +	igt_remove_fb(data->drm_fd, &fb);
> > +
> > +	drmModeFreeConnector(connector);
> > +}
> > +
> > +IGT_TEST_DESCRIPTION("Testing Audio with a Chamelium board");
> > +igt_main
> > +{
> > +	chamelium_data_t data;
> > +	struct chamelium_port *port;
> > +	int p;
> > +
> > +	igt_fixture {
> > +		chamelium_init_test(&data);
> > +	}
> > +
> > +	igt_describe("DisplayPort tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_display_audio_desc);
> > +		connector_subtest("dp-audio", DisplayPort) test_display_audio(
> > +			&data, port, "HDMI", IGT_CUSTOM_EDID_DP_AUDIO);
> > +
> > +		igt_describe(test_display_audio_edid_desc);
> > +		connector_subtest("dp-audio-edid", DisplayPort)
> > +			test_display_audio_edid(&data, port,
> > +						IGT_CUSTOM_EDID_DP_AUDIO);
> > +	}
> > +
> > +	igt_describe("HDMI tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_display_audio_desc);
> > +		connector_subtest("hdmi-audio", HDMIA) test_display_audio(
> > +			&data, port, "HDMI", IGT_CUSTOM_EDID_HDMI_AUDIO);
> > +
> > +		igt_describe(test_display_audio_edid_desc);
> > +		connector_subtest("hdmi-audio-edid", HDMIA)
> > +			test_display_audio_edid(&data, port,
> > +						IGT_CUSTOM_EDID_HDMI_AUDIO);
> > +	}
> > +
> > +	igt_fixture {
> > +		igt_display_fini(&data.display);
> > +		close(data.drm_fd);
> > +	}
> > +}
> > diff --git a/tests/chamelium/kms_color_chamelium.c b/tests/chamelium/kms_chamelium_color.c
> > similarity index 100%
> > rename from tests/chamelium/kms_color_chamelium.c
> > rename to tests/chamelium/kms_chamelium_color.c
> > diff --git a/tests/chamelium/kms_chamelium_edid.c b/tests/chamelium/kms_chamelium_edid.c
> > new file mode 100644
> > index 00000000..c9e15f41
> > --- /dev/null
> > +++ b/tests/chamelium/kms_chamelium_edid.c
> > @@ -0,0 +1,543 @@
> > +// SPDX-License-Identifier: MIT
> > +/*
> > + * A Chamelium test for testing the EDID functionality.
> > + *
> > + * Copyright 2022 Google LLC.
> > + *
> > + * Authors: Mark Yacoub <markyacoub@chromium.org>
> > + */
> > +
> > +#include <fcntl.h>
> > +#include <stdint.h>
> > +#include <string.h>
> > +#include <stdatomic.h>
> > +#include <xf86drmMode.h>
> > +
> > +#include "config.h"
> > +#include "igt.h"
> > +#include "igt_chamelium.h"
> > +#include "igt_edid.h"
> > +#include "igt_eld.h"
> > +#include "igt_vc4.h"
> > +#include "igt_infoframe.h"
> > +#include "kms_chamelium_helper.h"
> > +#include "monitor_edids/dp_edids.h"
> > +#include "monitor_edids/hdmi_edids.h"
> > +#include "monitor_edids/monitor_edids_helper.h"
> > +
> > +#define MODE_CLOCK_ACCURACY 0.05 /* 5% */
> > +
> > +static void get_connectors_link_status_failed(chamelium_data_t *data,
> > +					      bool *link_status_failed)
> > +{
> > +	drmModeConnector *connector;
> > +	uint64_t link_status;
> > +	drmModePropertyPtr prop;
> > +	int p;
> > +
> > +	for (p = 0; p < data->port_count; p++) {
> > +		connector = chamelium_port_get_connector(data->chamelium,
> > +							 data->ports[p], false);
> > +
> > +		igt_assert(kmstest_get_property(
> > +			data->drm_fd, connector->connector_id,
> > +			DRM_MODE_OBJECT_CONNECTOR, "link-status", NULL,
> > +			&link_status, &prop));
> > +
> > +		link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD;
> > +
> > +		drmModeFreeProperty(prop);
> > +		drmModeFreeConnector(connector);
> > +	}
> > +}
> > +
> > +static void check_mode(struct chamelium *chamelium, struct chamelium_port *port,
> > +		       drmModeModeInfo *mode)
> > +{
> > +	struct chamelium_video_params video_params = { 0 };
> > +	double mode_clock;
> > +	int mode_hsync_offset, mode_vsync_offset;
> > +	int mode_hsync_width, mode_vsync_width;
> > +	int mode_hsync_polarity, mode_vsync_polarity;
> > +
> > +	chamelium_port_get_video_params(chamelium, port, &video_params);
> > +
> > +	mode_clock = (double)mode->clock / 1000;
> > +
> > +	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) {
> > +		/* this is what chamelium understands as offsets for DP */
> > +		mode_hsync_offset = mode->htotal - mode->hsync_start;
> > +		mode_vsync_offset = mode->vtotal - mode->vsync_start;
> > +	} else {
> > +		/* and this is what they are for other connectors */
> > +		mode_hsync_offset = mode->hsync_start - mode->hdisplay;
> > +		mode_vsync_offset = mode->vsync_start - mode->vdisplay;
> > +	}
> > +
> > +	mode_hsync_width = mode->hsync_end - mode->hsync_start;
> > +	mode_vsync_width = mode->vsync_end - mode->vsync_start;
> > +
> > +	mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
> > +	mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
> > +
> > +	igt_debug("Checking video mode:\n");
> > +	igt_debug("clock: got %f, expected %f ± %f%%\n", video_params.clock,
> > +		  mode_clock, MODE_CLOCK_ACCURACY * 100);
> > +	igt_debug("hactive: got %d, expected %d\n", video_params.hactive,
> > +		  mode->hdisplay);
> > +	igt_debug("vactive: got %d, expected %d\n", video_params.vactive,
> > +		  mode->vdisplay);
> > +	igt_debug("hsync_offset: got %d, expected %d\n",
> > +		  video_params.hsync_offset, mode_hsync_offset);
> > +	igt_debug("vsync_offset: got %d, expected %d\n",
> > +		  video_params.vsync_offset, mode_vsync_offset);
> > +	igt_debug("htotal: got %d, expected %d\n", video_params.htotal,
> > +		  mode->htotal);
> > +	igt_debug("vtotal: got %d, expected %d\n", video_params.vtotal,
> > +		  mode->vtotal);
> > +	igt_debug("hsync_width: got %d, expected %d\n",
> > +		  video_params.hsync_width, mode_hsync_width);
> > +	igt_debug("vsync_width: got %d, expected %d\n",
> > +		  video_params.vsync_width, mode_vsync_width);
> > +	igt_debug("hsync_polarity: got %d, expected %d\n",
> > +		  video_params.hsync_polarity, mode_hsync_polarity);
> > +	igt_debug("vsync_polarity: got %d, expected %d\n",
> > +		  video_params.vsync_polarity, mode_vsync_polarity);
> > +
> > +	if (!isnan(video_params.clock)) {
> > +		igt_assert(video_params.clock >
> > +			   mode_clock * (1 - MODE_CLOCK_ACCURACY));
> > +		igt_assert(video_params.clock <
> > +			   mode_clock * (1 + MODE_CLOCK_ACCURACY));
> > +	}
> > +	igt_assert(video_params.hactive == mode->hdisplay);
> > +	igt_assert(video_params.vactive == mode->vdisplay);
> > +	igt_assert(video_params.hsync_offset == mode_hsync_offset);
> > +	igt_assert(video_params.vsync_offset == mode_vsync_offset);
> > +	igt_assert(video_params.htotal == mode->htotal);
> > +	igt_assert(video_params.vtotal == mode->vtotal);
> > +	igt_assert(video_params.hsync_width == mode_hsync_width);
> > +	igt_assert(video_params.vsync_width == mode_vsync_width);
> > +	igt_assert(video_params.hsync_polarity == mode_hsync_polarity);
> > +	igt_assert(video_params.vsync_polarity == mode_vsync_polarity);
> > +}
> > +
> > +static const char igt_custom_edid_type_read_desc[] =
> > +	"Make sure the EDID exposed by KMS is the same as the screen's";
> > +static void igt_custom_edid_type_read(chamelium_data_t *data,
> > +				      struct chamelium_port *port,
> > +				      enum igt_custom_edid_type edid)
> > +{
> > +	drmModePropertyBlobPtr edid_blob = NULL;
> > +	drmModeConnector *connector;
> > +	size_t raw_edid_size;
> > +	const struct edid *raw_edid;
> > +	uint64_t edid_blob_id;
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	chamelium_set_edid(data, port, edid);
> > +	chamelium_plug(data->chamelium, port);
> > +	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> > +					      port, DRM_MODE_CONNECTED);
> > +
> > +	igt_skip_on(chamelium_check_analog_bridge(data, port));
> > +
> > +	connector = chamelium_port_get_connector(data->chamelium, port, true);
> > +	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> > +					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> > +					&edid_blob_id, NULL));
> > +	igt_assert(edid_blob_id != 0);
> > +	edid_blob = drmModeGetPropertyBlob(data->drm_fd, edid_blob_id);
> > +	igt_assert(edid_blob);
> > +
> > +	raw_edid = chamelium_edid_get_raw(data->edids[edid], port);
> > +	raw_edid_size = edid_get_size(raw_edid);
> > +	igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0);
> > +
> > +	drmModeFreePropertyBlob(edid_blob);
> > +	drmModeFreeConnector(connector);
> > +}
> > +
> > +static const char igt_edid_stress_resolution_desc[] =
> > +	"Stress test the DUT by testing multiple EDIDs, one right after the other,"
> > +	"and ensure their validity by check the real screen resolution vs the"
> > +	"advertised mode resultion.";
> > +static void edid_stress_resolution(chamelium_data_t *data,
> > +				   struct chamelium_port *port,
> > +				   monitor_edid edids_list[],
> > +				   size_t edids_list_len)
> > +{
> > +	int i;
> > +	struct chamelium *chamelium = data->chamelium;
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +
> > +	for (i = 0; i < edids_list_len; ++i) {
> > +		struct chamelium_edid *chamelium_edid;
> > +		drmModeModeInfo mode;
> > +		struct igt_fb fb = { 0 };
> > +		igt_output_t *output;
> > +		enum pipe pipe;
> > +		bool is_video_stable;
> > +		int screen_res_w, screen_res_h;
> > +
> > +		monitor_edid *edid = &edids_list[i];
> > +		igt_info("Testing out the EDID for %s\n",
> > +			 monitor_edid_get_name(edid));
> > +
> > +		/* Getting and Setting the EDID on Chamelium. */
> > +		chamelium_edid =
> > +			get_chameleon_edid_from_monitor_edid(chamelium, edid);
> > +		chamelium_port_set_edid(data->chamelium, port, chamelium_edid);
> > +		free_chamelium_edid_from_monitor_edid(chamelium_edid);
> > +
> > +		igt_flush_uevents(mon);
> > +		chamelium_plug(chamelium, port);
> > +		chamelium_wait_for_connector_after_hotplug(data, mon, port,
> > +							   DRM_MODE_CONNECTED);
> > +		igt_flush_uevents(mon);
> > +
> > +		/* Setting an output on the screen to turn it on. */
> > +		mode = chamelium_get_mode_for_port(chamelium, port);
> > +		chamelium_create_fb_for_mode(data, &fb, &mode);
> > +		output = chamelium_get_output_for_port(data, port);
> > +		pipe = chamelium_get_pipe_for_output(&data->display, output);
> > +		igt_output_set_pipe(output, pipe);
> > +		chamelium_enable_output(data, port, output, &mode, &fb);
> > +
> > +		/* Capture the screen resolution and verify. */
> > +		is_video_stable = chamelium_port_wait_video_input_stable(
> > +			chamelium, port, 5);
> > +		igt_assert(is_video_stable);
> > +
> > +		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> > +					      &screen_res_h);
> > +		igt_assert(screen_res_w == fb.width);
> > +		igt_assert(screen_res_h == fb.height);
> > +
> > +		// Clean up
> > +		igt_remove_fb(data->drm_fd, &fb);
> > +		igt_modeset_disable_all_outputs(&data->display);
> > +		chamelium_unplug(chamelium, port);
> > +	}
> > +
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +}
> > +
> > +static const char igt_edid_resolution_list_desc[] =
> > +	"Get an EDID with many modes of different configurations, set them on the screen and check the"
> > +	" screen resolution matches the mode resolution.";
> > +
> > +static void edid_resolution_list(chamelium_data_t *data,
> > +				 struct chamelium_port *port)
> > +{
> > +	struct chamelium *chamelium = data->chamelium;
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +	drmModeConnector *connector;
> > +	drmModeModeInfoPtr modes;
> > +	int count_modes;
> > +	int i;
> > +	igt_output_t *output;
> > +	enum pipe pipe;
> > +
> > +	chamelium_unplug(chamelium, port);
> > +	chamelium_set_edid(data, port, IGT_CUSTOM_EDID_FULL);
> > +
> > +	igt_flush_uevents(mon);
> > +	chamelium_plug(chamelium, port);
> > +	chamelium_wait_for_connector_after_hotplug(data, mon, port,
> > +						   DRM_MODE_CONNECTED);
> > +	igt_flush_uevents(mon);
> > +
> > +	connector = chamelium_port_get_connector(chamelium, port, true);
> > +	modes = connector->modes;
> > +	count_modes = connector->count_modes;
> > +
> > +	output = chamelium_get_output_for_port(data, port);
> > +	pipe = chamelium_get_pipe_for_output(&data->display, output);
> > +	igt_output_set_pipe(output, pipe);
> > +
> > +	for (i = 0; i < count_modes; ++i)
> > +		igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh);
> > +
> > +	for (i = 0; i < count_modes; ++i) {
> > +		struct igt_fb fb = { 0 };
> > +		bool is_video_stable;
> > +		int screen_res_w, screen_res_h;
> > +
> > +		igt_info("Testing #%d %s %uHz\n", i, modes[i].name,
> > +			 modes[i].vrefresh);
> > +
> > +		/* Set the screen mode with the one we chose. */
> > +		chamelium_create_fb_for_mode(data, &fb, &modes[i]);
> > +		chamelium_enable_output(data, port, output, &modes[i], &fb);
> > +		is_video_stable = chamelium_port_wait_video_input_stable(
> > +			chamelium, port, 10);
> > +		igt_assert(is_video_stable);
> > +
> > +		chamelium_port_get_resolution(chamelium, port, &screen_res_w,
> > +					      &screen_res_h);
> > +		igt_assert_eq(screen_res_w, modes[i].hdisplay);
> > +		igt_assert_eq(screen_res_h, modes[i].vdisplay);
> > +
> > +		igt_remove_fb(data->drm_fd, &fb);
> > +	}
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	drmModeFreeConnector(connector);
> > +}
> > +
> > +static const char test_suspend_resume_edid_change_desc[] =
> > +	"Simulate a screen being unplugged and another screen being plugged "
> > +	"during suspend, check that a uevent is sent and connector status is "
> > +	"updated";
> > +static void test_suspend_resume_edid_change(chamelium_data_t *data,
> > +					    struct chamelium_port *port,
> > +					    enum igt_suspend_state state,
> > +					    enum igt_suspend_test test,
> > +					    enum igt_custom_edid_type edid,
> > +					    enum igt_custom_edid_type alt_edid)
> > +{
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +	bool link_status_failed[2][data->port_count];
> > +	int p;
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	/* Catch the event and flush all remaining ones. */
> > +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > +	igt_flush_uevents(mon);
> > +
> > +	/* First plug in the port */
> > +	chamelium_set_edid(data, port, edid);
> > +	chamelium_plug(data->chamelium, port);
> > +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > +
> > +	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> > +					      port, DRM_MODE_CONNECTED);
> > +
> > +	/*
> > +	 * Change the edid before we suspend. On resume, the machine should
> > +	 * notice the EDID change and fire a hotplug event.
> > +	 */
> > +	chamelium_set_edid(data, port, alt_edid);
> > +
> > +	get_connectors_link_status_failed(data, link_status_failed[0]);
> > +
> > +	igt_flush_uevents(mon);
> > +
> > +	igt_system_suspend_autoresume(state, test);
> > +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > +	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> > +
> > +	get_connectors_link_status_failed(data, link_status_failed[1]);
> > +
> > +	for (p = 0; p < data->port_count; p++)
> > +		igt_skip_on(!link_status_failed[0][p] &&
> > +			    link_status_failed[1][p]);
> > +}
> > +
> > +static const char test_mode_timings_desc[] =
> > +	"For each mode of the IGT base EDID, perform a modeset and check the "
> > +	"mode detected by the Chamelium receiver matches the mode we set";
> > +static void test_mode_timings(chamelium_data_t *data,
> > +			      struct chamelium_port *port)
> > +{
> > +	int i, count_modes;
> > +
> > +	i = 0;
> > +	igt_require(chamelium_supports_get_video_params(data->chamelium));
> > +	do {
> > +		igt_output_t *output;
> > +		igt_plane_t *primary;
> > +		drmModeConnector *connector;
> > +		drmModeModeInfo *mode;
> > +		int fb_id;
> > +		struct igt_fb fb;
> > +
> > +		/*
> > +		 * let's reset state each mode so we will get the
> > +		 * HPD pulses realibably
> > +		 */
> > +		igt_modeset_disable_all_outputs(&data->display);
> > +		chamelium_reset_state(&data->display, data->chamelium, port,
> > +				      data->ports, data->port_count);
> > +
> > +		/*
> > +		 * modes may change due to mode pruining and link issues, so we
> > +		 * need to refresh the connector
> > +		 */
> > +		output = chamelium_prepare_output(data, port,
> > +						  IGT_CUSTOM_EDID_BASE);
> > +		connector = chamelium_port_get_connector(data->chamelium, port,
> > +							 false);
> > +		primary = igt_output_get_plane_type(output,
> > +						    DRM_PLANE_TYPE_PRIMARY);
> > +		igt_assert(primary);
> > +
> > +		/* we may skip some modes due to above but that's ok */
> > +		count_modes = connector->count_modes;
> > +		if (i >= count_modes)
> > +			break;
> > +
> > +		mode = &connector->modes[i];
> > +
> > +		fb_id = igt_create_color_pattern_fb(
> > +			data->drm_fd, mode->hdisplay, mode->vdisplay,
> > +			DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> > +			&fb);
> > +		igt_assert(fb_id > 0);
> > +
> > +		chamelium_enable_output(data, port, output, mode, &fb);
> > +
> > +		/* Trigger the FSM */
> > +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0);
> > +
> > +		check_mode(data->chamelium, port, mode);
> > +
> > +		igt_remove_fb(data->drm_fd, &fb);
> > +		drmModeFreeConnector(connector);
> > +	} while (++i < count_modes);
> > +}
> > +
> > +IGT_TEST_DESCRIPTION("Testing EDID with a Chamelium board");
> > +igt_main
> > +{
> > +	chamelium_data_t data;
> > +	struct chamelium_port *port;
> > +	int p;
> > +
> > +	igt_fixture {
> > +		chamelium_init_test(&data);
> > +	}
> > +
> > +	igt_describe("DisplayPort tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(igt_custom_edid_type_read_desc);
> > +		connector_subtest("dp-edid-read", DisplayPort)
> > +		{
> > +			igt_custom_edid_type_read(&data, port,
> > +						  IGT_CUSTOM_EDID_BASE);
> > +			igt_custom_edid_type_read(&data, port,
> > +						  IGT_CUSTOM_EDID_ALT);
> > +		}
> > +
> > +		igt_describe(igt_edid_stress_resolution_desc);
> > +		connector_subtest("dp-edid-stress-resolution-4k", DisplayPort)
> > +			edid_stress_resolution(&data, port, DP_EDIDS_4K,
> > +					       ARRAY_SIZE(DP_EDIDS_4K));
> > +
> > +		igt_describe(igt_edid_stress_resolution_desc);
> > +		connector_subtest("dp-edid-stress-resolution-non-4k",
> > +				  DisplayPort)
> > +			edid_stress_resolution(&data, port, DP_EDIDS_NON_4K,
> > +					       ARRAY_SIZE(DP_EDIDS_NON_4K));
> > +
> > +		igt_describe(igt_edid_resolution_list_desc);
> > +		connector_subtest("dp-edid-resolution-list", DisplayPort)
> > +			edid_resolution_list(&data, port);
> > +
> > +		igt_describe(test_suspend_resume_edid_change_desc);
> > +		connector_subtest("dp-edid-change-during-suspend", DisplayPort)
> > +			test_suspend_resume_edid_change(&data, port,
> > +							SUSPEND_STATE_MEM,
> > +							SUSPEND_TEST_NONE,
> > +							IGT_CUSTOM_EDID_BASE,
> > +							IGT_CUSTOM_EDID_ALT);
> > +
> > +		igt_describe(test_suspend_resume_edid_change_desc);
> > +		connector_subtest("dp-edid-change-during-hibernate",
> > +				  DisplayPort)
> > +			test_suspend_resume_edid_change(&data, port,
> > +							SUSPEND_STATE_DISK,
> > +							SUSPEND_TEST_DEVICES,
> > +							IGT_CUSTOM_EDID_BASE,
> > +							IGT_CUSTOM_EDID_ALT);
> > +
> > +		igt_describe(test_mode_timings_desc);
> > +		connector_subtest("dp-mode-timings", DisplayPort)
> > +			test_mode_timings(&data, port);
> > +	}
> > +
> > +	igt_describe("HDMI tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(igt_custom_edid_type_read_desc);
> > +		connector_subtest("hdmi-edid-read", HDMIA)
> > +		{
> > +			igt_custom_edid_type_read(&data, port,
> > +						  IGT_CUSTOM_EDID_BASE);
> > +			igt_custom_edid_type_read(&data, port,
> > +						  IGT_CUSTOM_EDID_ALT);
> > +		}
> > +
> > +		igt_describe(igt_edid_stress_resolution_desc);
> > +		connector_subtest("hdmi-edid-stress-resolution-4k", HDMIA)
> > +			edid_stress_resolution(&data, port, HDMI_EDIDS_4K,
> > +					       ARRAY_SIZE(HDMI_EDIDS_4K));
> > +
> > +		igt_describe(igt_edid_stress_resolution_desc);
> > +		connector_subtest("hdmi-edid-stress-resolution-non-4k", HDMIA)
> > +			edid_stress_resolution(&data, port, HDMI_EDIDS_NON_4K,
> > +					       ARRAY_SIZE(HDMI_EDIDS_NON_4K));
> > +
> > +		igt_describe(test_suspend_resume_edid_change_desc);
> > +		connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
> > +			test_suspend_resume_edid_change(&data, port,
> > +							SUSPEND_STATE_MEM,
> > +							SUSPEND_TEST_NONE,
> > +							IGT_CUSTOM_EDID_BASE,
> > +							IGT_CUSTOM_EDID_ALT);
> > +
> > +		igt_describe(test_suspend_resume_edid_change_desc);
> > +		connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
> > +			test_suspend_resume_edid_change(&data, port,
> > +							SUSPEND_STATE_DISK,
> > +							SUSPEND_TEST_DEVICES,
> > +							IGT_CUSTOM_EDID_BASE,
> > +							IGT_CUSTOM_EDID_ALT);
> > +
> > +		igt_describe(test_mode_timings_desc);
> > +		connector_subtest("hdmi-mode-timings", HDMIA)
> > +			test_mode_timings(&data, port);
> > +	}
> > +
> > +	igt_describe("VGA tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_VGA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(igt_custom_edid_type_read_desc);
> > +		connector_subtest("vga-edid-read", VGA)
> > +		{
> > +			igt_custom_edid_type_read(&data, port,
> > +						  IGT_CUSTOM_EDID_BASE);
> > +			igt_custom_edid_type_read(&data, port,
> > +						  IGT_CUSTOM_EDID_ALT);
> > +		}
> > +	}
> > +
> > +	igt_fixture {
> > +		igt_display_fini(&data.display);
> > +		close(data.drm_fd);
> > +	}
> > +}
> > diff --git a/tests/chamelium/kms_chamelium_frames.c b/tests/chamelium/kms_chamelium_frames.c
> > new file mode 100644
> > index 00000000..008bc34b
> > --- /dev/null
> > +++ b/tests/chamelium/kms_chamelium_frames.c
> > @@ -0,0 +1,1085 @@
> > +/*
> > + * Copyright © 2016 Red Hat 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 (including the next
> > + * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
> > + *
> > + * Authors:
> > + *    Lyude Paul <lyude@redhat.com>
> > + */
> > +
> > +#include "igt_eld.h"
> > +#include "igt_infoframe.h"
> > +#include "kms_chamelium_helper.h"
> > +
> > +#define connector_dynamic_subtest(name__, type__)                   \
> > +	igt_subtest_with_dynamic(name__)                            \
> > +	for_each_port(p, port) if (chamelium_port_get_type(port) == \
> > +				   DRM_MODE_CONNECTOR_##type__)
> > +
> > +struct vic_mode {
> > +	int hactive, vactive;
> > +	int vrefresh; /* Hz */
> > +	uint32_t picture_ar;
> > +};
> > +
> > +static int chamelium_vga_modes[][2] = {
> > +	{ 1600, 1200 }, { 1920, 1200 }, { 1920, 1080 }, { 1680, 1050 },
> > +	{ 1280, 1024 }, { 1280, 960 },	{ 1440, 900 },	{ 1280, 800 },
> > +	{ 1024, 768 },	{ 1360, 768 },	{ 1280, 720 },	{ 800, 600 },
> > +	{ 640, 480 },	{ -1, -1 },
> > +};
> > +
> > +/* Maps Video Identification Codes to a mode */
> > +static const struct vic_mode vic_modes[] = {
> > +	[16] = {
> > +		.hactive = 1920,
> > +		.vactive = 1080,
> > +		.vrefresh = 60,
> > +		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
> > +	},
> > +};
> > +
> > +/* Maps aspect ratios to their mode flag */
> > +static const uint32_t mode_ar_flags[] = {
> > +	[DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9,
> > +};
> > +
> > +static bool prune_vga_mode(chamelium_data_t *data, drmModeModeInfo *mode)
> > +{
> > +	int i = 0;
> > +
> > +	while (chamelium_vga_modes[i][0] != -1) {
> > +		if (mode->hdisplay == chamelium_vga_modes[i][0] &&
> > +		    mode->vdisplay == chamelium_vga_modes[i][1])
> > +			return false;
> > +
> > +		i++;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static void do_test_display(chamelium_data_t *data, struct chamelium_port *port,
> > +			    igt_output_t *output, drmModeModeInfo *mode,
> > +			    uint32_t fourcc, enum chamelium_check check,
> > +			    int count)
> > +{
> > +	struct chamelium_fb_crc_async_data *fb_crc;
> > +	struct igt_fb frame_fb, fb;
> > +	int i, fb_id, captured_frame_count;
> > +	int frame_id;
> > +
> > +	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> > +					 DRM_FORMAT_XRGB8888, 64, &fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	frame_id =
> > +		igt_fb_convert(&frame_fb, &fb, fourcc, DRM_FORMAT_MOD_LINEAR);
> > +	igt_assert(frame_id > 0);
> > +
> > +	if (check == CHAMELIUM_CHECK_CRC)
> > +		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> > +								&fb);
> > +
> > +	chamelium_enable_output(data, port, output, mode, &frame_fb);
> > +
> > +	if (check == CHAMELIUM_CHECK_CRC) {
> > +		igt_crc_t *expected_crc;
> > +		igt_crc_t *crc;
> > +
> > +		/* We want to keep the display running for a little bit, since
> > +		 * there's always the potential the driver isn't able to keep
> > +		 * the display running properly for very long
> > +		 */
> > +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count);
> > +		crc = chamelium_read_captured_crcs(data->chamelium,
> > +						   &captured_frame_count);
> > +
> > +		igt_assert(captured_frame_count == count);
> > +
> > +		igt_debug("Captured %d frames\n", captured_frame_count);
> > +
> > +		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> > +
> > +		for (i = 0; i < captured_frame_count; i++)
> > +			chamelium_assert_crc_eq_or_dump(
> > +				data->chamelium, expected_crc, &crc[i], &fb, i);
> > +
> > +		free(expected_crc);
> > +		free(crc);
> > +	} else if (check == CHAMELIUM_CHECK_ANALOG ||
> > +		   check == CHAMELIUM_CHECK_CHECKERBOARD) {
> > +		struct chamelium_frame_dump *dump;
> > +
> > +		igt_assert(count == 1);
> > +
> > +		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> > +						  0, 0);
> > +
> > +		if (check == CHAMELIUM_CHECK_ANALOG)
> > +			chamelium_crop_analog_frame(dump, mode->hdisplay,
> > +						    mode->vdisplay);
> > +
> > +		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> > +						     dump, &fb, check);
> > +		chamelium_destroy_frame_dump(dump);
> > +	}
> > +
> > +	igt_remove_fb(data->drm_fd, &frame_fb);
> > +	igt_remove_fb(data->drm_fd, &fb);
> > +}
> > +
> > +static enum infoframe_avi_picture_aspect_ratio
> > +get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
> > +{
> > +	/* The AVI picture aspect ratio field only supports 4:3 and 16:9 */
> > +	switch (aspect_ratio) {
> > +	case DRM_MODE_PICTURE_ASPECT_4_3:
> > +		return INFOFRAME_AVI_PIC_AR_4_3;
> > +	case DRM_MODE_PICTURE_ASPECT_16_9:
> > +		return INFOFRAME_AVI_PIC_AR_16_9;
> > +	default:
> > +		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
> > +	}
> > +}
> > +
> > +static bool vic_mode_matches_drm(const struct vic_mode *vic_mode,
> > +				 drmModeModeInfo *drm_mode)
> > +{
> > +	uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar];
> > +
> > +	return vic_mode->hactive == drm_mode->hdisplay &&
> > +	       vic_mode->vactive == drm_mode->vdisplay &&
> > +	       vic_mode->vrefresh == drm_mode->vrefresh &&
> > +	       ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
> > +}
> > +
> > +static void randomize_plane_stride(chamelium_data_t *data, uint32_t width,
> > +				   uint32_t height, uint32_t format,
> > +				   uint64_t modifier, size_t *stride)
> > +{
> > +	size_t stride_min;
> > +	uint32_t max_tile_w = 4, tile_w, tile_h;
> > +	int i;
> > +	struct igt_fb dummy;
> > +
> > +	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
> > +
> > +	/* Randomize the stride to less than twice the minimum. */
> > +	*stride = (rand() % stride_min) + stride_min;
> > +
> > +	/*
> > +	 * Create a dummy FB to determine bpp for each plane, and calculate
> > +	 * the maximum tile width from that.
> > +	 */
> > +	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
> > +	for (i = 0; i < dummy.num_planes; i++) {
> > +		igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i],
> > +				     &tile_w, &tile_h);
> > +
> > +		if (tile_w > max_tile_w)
> > +			max_tile_w = tile_w;
> > +	}
> > +	igt_remove_fb(data->drm_fd, &dummy);
> > +
> > +	/*
> > +	 * Pixman requires the stride to be aligned to 32-bits, which is
> > +	 * reflected in the initial value of max_tile_w and the hw
> > +	 * may require a multiple of tile width, choose biggest of the 2.
> > +	 */
> > +	*stride = ALIGN(*stride, max_tile_w);
> > +}
> > +
> > +static void update_tiled_modifier(igt_plane_t *plane, uint32_t width,
> > +				  uint32_t height, uint32_t format,
> > +				  uint64_t *modifier)
> > +{
> > +	if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) {
> > +		/* Randomize the column height to less than twice the minimum.
> > +		 */
> > +		size_t column_height = (rand() % height) + height;
> > +
> > +		igt_debug(
> > +			"Selecting VC4 SAND256 tiling with column height %ld\n",
> > +			column_height);
> > +
> > +		*modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(
> > +			column_height);
> > +	}
> > +}
> > +
> > +static void randomize_plane_setup(chamelium_data_t *data, igt_plane_t *plane,
> > +				  drmModeModeInfo *mode, uint32_t *width,
> > +				  uint32_t *height, uint32_t *format,
> > +				  uint64_t *modifier, bool allow_yuv)
> > +{
> > +	int min_dim;
> > +	uint32_t idx[plane->format_mod_count];
> > +	unsigned int count = 0;
> > +	unsigned int i;
> > +
> > +	/* First pass to count the supported formats. */
> > +	for (i = 0; i < plane->format_mod_count; i++)
> > +		if (igt_fb_supported_format(plane->formats[i]) &&
> > +		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
> > +			idx[count++] = i;
> > +
> > +	igt_assert(count > 0);
> > +
> > +	i = idx[rand() % count];
> > +	*format = plane->formats[i];
> > +	*modifier = plane->modifiers[i];
> > +
> > +	update_tiled_modifier(plane, *width, *height, *format, modifier);
> > +
> > +	/*
> > +	 * Randomize width and height in the mode dimensions range.
> > +	 *
> > +	 * Restrict to a min of 2 * min_dim, this way src_w/h are always at
> > +	 * least min_dim, because src_w = width - (rand % w / 2).
> > +	 *
> > +	 * Use a minimum dimension of 16 for YUV, because planar YUV
> > +	 * subsamples the UV plane.
> > +	 */
> > +	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
> > +
> > +	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
> > +	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
> > +}
> > +
> > +static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h,
> > +			    uint32_t src_x, uint32_t src_y, uint32_t crtc_w,
> > +			    uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y,
> > +			    struct igt_fb *fb)
> > +{
> > +	igt_plane_set_fb(plane, fb);
> > +
> > +	igt_plane_set_position(plane, crtc_x, crtc_y);
> > +	igt_plane_set_size(plane, crtc_w, crtc_h);
> > +
> > +	igt_fb_set_position(fb, plane, src_x, src_y);
> > +	igt_fb_set_size(fb, plane, src_w, src_h);
> > +}
> > +
> > +static void randomize_plane_coordinates(
> > +	chamelium_data_t *data, igt_plane_t *plane, drmModeModeInfo *mode,
> > +	struct igt_fb *fb, uint32_t *src_w, uint32_t *src_h, uint32_t *src_x,
> > +	uint32_t *src_y, uint32_t *crtc_w, uint32_t *crtc_h, int32_t *crtc_x,
> > +	int32_t *crtc_y, bool allow_scaling)
> > +{
> > +	bool is_yuv = igt_format_is_yuv(fb->drm_format);
> > +	uint32_t width = fb->width, height = fb->height;
> > +	double ratio;
> > +	int ret;
> > +
> > +	/* Randomize source offset in the first half of the original size. */
> > +	*src_x = rand() % (width / 2);
> > +	*src_y = rand() % (height / 2);
> > +
> > +	/* The source size only includes the active source area. */
> > +	*src_w = width - *src_x;
> > +	*src_h = height - *src_y;
> > +
> > +	if (allow_scaling) {
> > +		*crtc_w = (rand() % mode->hdisplay) + 1;
> > +		*crtc_h = (rand() % mode->vdisplay) + 1;
> > +
> > +		/*
> > +		 * Don't bother with scaling if dimensions are quite close in
> > +		 * order to get non-scaling cases more frequently. Also limit
> > +		 * scaling to 3x to avoid aggressive filtering that makes
> > +		 * comparison less reliable, and don't go above 2x downsampling
> > +		 * to avoid possible hw limitations.
> > +		 */
> > +
> > +		ratio = ((double)*crtc_w / *src_w);
> > +		if (ratio < 0.5)
> > +			*src_w = *crtc_w * 2;
> > +		else if (ratio > 0.8 && ratio < 1.2)
> > +			*crtc_w = *src_w;
> > +		else if (ratio > 3.0)
> > +			*crtc_w = *src_w * 3;
> > +
> > +		ratio = ((double)*crtc_h / *src_h);
> > +		if (ratio < 0.5)
> > +			*src_h = *crtc_h * 2;
> > +		else if (ratio > 0.8 && ratio < 1.2)
> > +			*crtc_h = *src_h;
> > +		else if (ratio > 3.0)
> > +			*crtc_h = *src_h * 3;
> > +	} else {
> > +		*crtc_w = *src_w;
> > +		*crtc_h = *src_h;
> > +	}
> > +
> > +	if (*crtc_w != *src_w || *crtc_h != *src_h) {
> > +		/*
> > +		 * When scaling is involved, make sure to not go off-bounds or
> > +		 * scaled clipping may result in decimal dimensions, that most
> > +		 * drivers don't support.
> > +		 */
> > +		if (*crtc_w < mode->hdisplay)
> > +			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
> > +		else
> > +			*crtc_x = 0;
> > +
> > +		if (*crtc_h < mode->vdisplay)
> > +			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
> > +		else
> > +			*crtc_y = 0;
> > +	} else {
> > +		/*
> > +		 * Randomize the on-crtc position and allow the plane to go
> > +		 * off-display by less than half of its on-crtc dimensions.
> > +		 */
> > +		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
> > +		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
> > +	}
> > +
> > +	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, *crtc_h,
> > +			*crtc_x, *crtc_y, fb);
> > +	ret = igt_display_try_commit_atomic(
> > +		&data->display,
> > +		DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
> > +		NULL);
> > +	if (!ret)
> > +		return;
> > +
> > +	/* Coordinates are logged in the dumped debug log, so only report w/h on
> > +	 * failure here. */
> > +	igt_assert_f(ret != -ENOSPC,
> > +		     "Failure in testcase, invalid coordinates on a %ux%u fb\n",
> > +		     width, height);
> > +
> > +	/* Make YUV coordinates a multiple of 2 and retry the math. */
> > +	if (is_yuv) {
> > +		*src_x &= ~1;
> > +		*src_y &= ~1;
> > +		*src_w &= ~1;
> > +		*src_h &= ~1;
> > +		/* To handle 1:1 scaling, clear crtc_w/h too. */
> > +		*crtc_w &= ~1;
> > +		*crtc_h &= ~1;
> > +
> > +		if (*crtc_x < 0 && (*crtc_x & 1))
> > +			(*crtc_x)++;
> > +		else
> > +			*crtc_x &= ~1;
> > +
> > +		/* If negative, round up to 0 instead of down */
> > +		if (*crtc_y < 0 && (*crtc_y & 1))
> > +			(*crtc_y)++;
> > +		else
> > +			*crtc_y &= ~1;
> > +
> > +		configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w,
> > +				*crtc_h, *crtc_x, *crtc_y, fb);
> > +		ret = igt_display_try_commit_atomic(
> > +			&data->display,
> > +			DRM_MODE_ATOMIC_TEST_ONLY |
> > +				DRM_MODE_ATOMIC_ALLOW_MODESET,
> > +			NULL);
> > +		if (!ret)
> > +			return;
> > +	}
> > +
> > +	igt_assert(!ret || allow_scaling);
> > +	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
> > +		 ((double)*crtc_w / *src_w), ((double)*crtc_h / *src_h));
> > +
> > +	*crtc_w = *src_w;
> > +	*crtc_h = *src_h;
> > +
> > +	configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, *crtc_h,
> > +			*crtc_x, *crtc_y, fb);
> > +	igt_display_commit_atomic(&data->display,
> > +				  DRM_MODE_ATOMIC_TEST_ONLY |
> > +					  DRM_MODE_ATOMIC_ALLOW_MODESET,
> > +				  NULL);
> > +}
> > +
> > +static void blit_plane_cairo(chamelium_data_t *data, cairo_surface_t *result,
> > +			     uint32_t src_w, uint32_t src_h, uint32_t src_x,
> > +			     uint32_t src_y, uint32_t crtc_w, uint32_t crtc_h,
> > +			     int32_t crtc_x, int32_t crtc_y, struct igt_fb *fb)
> > +{
> > +	cairo_surface_t *surface;
> > +	cairo_surface_t *clipped_surface;
> > +	cairo_t *cr;
> > +
> > +	surface = igt_get_cairo_surface(data->drm_fd, fb);
> > +
> > +	if (src_x || src_y) {
> > +		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
> > +							     src_w, src_h);
> > +
> > +		cr = cairo_create(clipped_surface);
> > +
> > +		cairo_translate(cr, -1. * src_x, -1. * src_y);
> > +
> > +		cairo_set_source_surface(cr, surface, 0, 0);
> > +
> > +		cairo_paint(cr);
> > +		cairo_surface_flush(clipped_surface);
> > +
> > +		cairo_destroy(cr);
> > +	} else {
> > +		clipped_surface = surface;
> > +	}
> > +
> > +	cr = cairo_create(result);
> > +
> > +	cairo_translate(cr, crtc_x, crtc_y);
> > +
> > +	if (src_w != crtc_w || src_h != crtc_h) {
> > +		cairo_scale(cr, (double)crtc_w / src_w, (double)crtc_h / src_h);
> > +	}
> > +
> > +	cairo_set_source_surface(cr, clipped_surface, 0, 0);
> > +	cairo_surface_destroy(clipped_surface);
> > +
> > +	if (src_w != crtc_w || src_h != crtc_h) {
> > +		cairo_pattern_set_filter(cairo_get_source(cr),
> > +					 CAIRO_FILTER_BILINEAR);
> > +		cairo_pattern_set_extend(cairo_get_source(cr),
> > +					 CAIRO_EXTEND_NONE);
> > +	}
> > +
> > +	cairo_paint(cr);
> > +	cairo_surface_flush(result);
> > +
> > +	cairo_destroy(cr);
> > +}
> > +
> > +static void prepare_randomized_plane(chamelium_data_t *data,
> > +				     drmModeModeInfo *mode, igt_plane_t *plane,
> > +				     struct igt_fb *overlay_fb,
> > +				     unsigned int index,
> > +				     cairo_surface_t *result_surface,
> > +				     bool allow_scaling, bool allow_yuv)
> > +{
> > +	struct igt_fb pattern_fb;
> > +	uint32_t overlay_fb_w, overlay_fb_h;
> > +	uint32_t overlay_src_w, overlay_src_h;
> > +	uint32_t overlay_src_x, overlay_src_y;
> > +	int32_t overlay_crtc_x, overlay_crtc_y;
> > +	uint32_t overlay_crtc_w, overlay_crtc_h;
> > +	uint32_t format;
> > +	uint64_t modifier;
> > +	size_t stride;
> > +	bool tiled;
> > +	int fb_id;
> > +
> > +	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
> > +			      &format, &modifier, allow_yuv);
> > +
> > +	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
> > +	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n", index,
> > +		  overlay_fb_w, overlay_fb_h, igt_format_str(format),
> > +		  tiled ? "tiled" : "linear");
> > +
> > +	/* Get a pattern framebuffer for the overlay plane. */
> > +	fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h,
> > +					 DRM_FORMAT_XRGB8888, 32, &pattern_fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h, format,
> > +			       modifier, &stride);
> > +
> > +	igt_debug("Plane %d: stride %ld\n", index, stride);
> > +
> > +	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
> > +					   modifier, stride);
> > +	igt_assert(fb_id > 0);
> > +
> > +	randomize_plane_coordinates(data, plane, mode, overlay_fb,
> > +				    &overlay_src_w, &overlay_src_h,
> > +				    &overlay_src_x, &overlay_src_y,
> > +				    &overlay_crtc_w, &overlay_crtc_h,
> > +				    &overlay_crtc_x, &overlay_crtc_y,
> > +				    allow_scaling);
> > +
> > +	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index, overlay_src_w,
> > +		  overlay_src_h);
> > +	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
> > +		  overlay_src_x, overlay_src_y);
> > +	igt_debug("Plane %d: on-crtc size %dx%d\n", index, overlay_crtc_w,
> > +		  overlay_crtc_h);
> > +	igt_debug("Plane %d: on-crtc position %dx%d\n", index, overlay_crtc_x,
> > +		  overlay_crtc_y);
> > +
> > +	blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h,
> > +			 overlay_src_x, overlay_src_y, overlay_crtc_w,
> > +			 overlay_crtc_h, overlay_crtc_x, overlay_crtc_y,
> > +			 &pattern_fb);
> > +
> > +	/* Remove the original pattern framebuffer. */
> > +	igt_remove_fb(data->drm_fd, &pattern_fb);
> > +}
> > +
> > +static const char test_display_one_mode_desc[] =
> > +	"Pick the first mode of the IGT base EDID, display and capture a few "
> > +	"frames, then check captured frames are correct";
> > +static void test_display_one_mode(chamelium_data_t *data,
> > +				  struct chamelium_port *port, uint32_t fourcc,
> > +				  enum chamelium_check check, int count)
> > +{
> > +	drmModeConnector *connector;
> > +	drmModeModeInfo *mode;
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	output = chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	igt_require(igt_plane_has_format_mod(primary, fourcc,
> > +					     DRM_FORMAT_MOD_LINEAR));
> > +
> > +	mode = &connector->modes[0];
> > +	if (check == CHAMELIUM_CHECK_ANALOG) {
> > +		bool bridge = chamelium_check_analog_bridge(data, port);
> > +
> > +		igt_assert(!(bridge && prune_vga_mode(data, mode)));
> > +	}
> > +
> > +	do_test_display(data, port, output, mode, fourcc, check, count);
> > +
> > +	drmModeFreeConnector(connector);
> > +}
> > +
> > +static const char test_display_all_modes_desc[] =
> > +	"For each mode of the IGT base EDID, display and capture a few "
> > +	"frames, then check captured frames are correct";
> > +static void test_display_all_modes(chamelium_data_t *data,
> > +				   struct chamelium_port *port, uint32_t fourcc,
> > +				   enum chamelium_check check, int count)
> > +{
> > +	bool bridge;
> > +	int i, count_modes;
> > +
> > +	if (check == CHAMELIUM_CHECK_ANALOG)
> > +		bridge = chamelium_check_analog_bridge(data, port);
> > +
> > +	i = 0;
> > +	do {
> > +		igt_output_t *output;
> > +		igt_plane_t *primary;
> > +		drmModeConnector *connector;
> > +		drmModeModeInfo *mode;
> > +
> > +		/*
> > +		 * let's reset state each mode so we will get the
> > +		 * HPD pulses realibably
> > +		 */
> > +		igt_modeset_disable_all_outputs(&data->display);
> > +		chamelium_reset_state(&data->display, data->chamelium, port,
> > +				      data->ports, data->port_count);
> > +
> > +		/*
> > +		 * modes may change due to mode pruining and link issues, so we
> > +		 * need to refresh the connector
> > +		 */
> > +		output = chamelium_prepare_output(data, port,
> > +						  IGT_CUSTOM_EDID_BASE);
> > +		connector = chamelium_port_get_connector(data->chamelium, port,
> > +							 false);
> > +		primary = igt_output_get_plane_type(output,
> > +						    DRM_PLANE_TYPE_PRIMARY);
> > +		igt_assert(primary);
> > +		igt_require(igt_plane_has_format_mod(primary, fourcc,
> > +						     DRM_FORMAT_MOD_LINEAR));
> > +
> > +		/* we may skip some modes due to above but that's ok */
> > +		count_modes = connector->count_modes;
> > +		if (i >= count_modes)
> > +			break;
> > +
> > +		mode = &connector->modes[i];
> > +
> > +		if (check == CHAMELIUM_CHECK_ANALOG && bridge &&
> > +		    prune_vga_mode(data, mode))
> > +			continue;
> > +
> > +		do_test_display(data, port, output, mode, fourcc, check, count);
> > +		drmModeFreeConnector(connector);
> > +	} while (++i < count_modes);
> > +}
> > +
> > +static const char test_display_frame_dump_desc[] =
> > +	"For each mode of the IGT base EDID, display and capture a few "
> > +	"frames, then download the captured frames and compare them "
> > +	"bit-by-bit to the sent ones";
> > +static void test_display_frame_dump(chamelium_data_t *data,
> > +				    struct chamelium_port *port)
> > +{
> > +	int i, count_modes;
> > +
> > +	i = 0;
> > +	do {
> > +		igt_output_t *output;
> > +		igt_plane_t *primary;
> > +		struct igt_fb fb;
> > +		struct chamelium_frame_dump *frame;
> > +		drmModeModeInfo *mode;
> > +		drmModeConnector *connector;
> > +		int fb_id, j;
> > +
> > +		/*
> > +		 * let's reset state each mode so we will get the
> > +		 * HPD pulses realibably
> > +		 */
> > +		igt_modeset_disable_all_outputs(&data->display);
> > +		chamelium_reset_state(&data->display, data->chamelium, port,
> > +				      data->ports, data->port_count);
> > +
> > +		/*
> > +		 * modes may change due to mode pruining and link issues, so we
> > +		 * need to refresh the connector
> > +		 */
> > +		output = chamelium_prepare_output(data, port,
> > +						  IGT_CUSTOM_EDID_BASE);
> > +		connector = chamelium_port_get_connector(data->chamelium, port,
> > +							 false);
> > +		primary = igt_output_get_plane_type(output,
> > +						    DRM_PLANE_TYPE_PRIMARY);
> > +		igt_assert(primary);
> > +
> > +		/* we may skip some modes due to above but that's ok */
> > +		count_modes = connector->count_modes;
> > +		if (i >= count_modes)
> > +			break;
> > +
> > +		mode = &connector->modes[i];
> > +
> > +		fb_id = igt_create_color_pattern_fb(
> > +			data->drm_fd, mode->hdisplay, mode->vdisplay,
> > +			DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> > +			&fb);
> > +		igt_assert(fb_id > 0);
> > +
> > +		chamelium_enable_output(data, port, output, mode, &fb);
> > +
> > +		igt_debug("Reading frame dumps from Chamelium...\n");
> > +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
> > +		for (j = 0; j < 5; j++) {
> > +			frame = chamelium_read_captured_frame(data->chamelium,
> > +							      j);
> > +			chamelium_assert_frame_eq(data->chamelium, frame, &fb);
> > +			chamelium_destroy_frame_dump(frame);
> > +		}
> > +
> > +		igt_remove_fb(data->drm_fd, &fb);
> > +		drmModeFreeConnector(connector);
> > +	} while (++i < count_modes);
> > +}
> > +
> > +static const char test_display_aspect_ratio_desc[] =
> > +	"Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and "
> > +	"check they include the relevant fields";
> > +static void test_display_aspect_ratio(chamelium_data_t *data,
> > +				      struct chamelium_port *port)
> > +{
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +	drmModeConnector *connector;
> > +	drmModeModeInfo *mode;
> > +	int fb_id, i;
> > +	struct igt_fb fb;
> > +	bool found, ok;
> > +	struct chamelium_infoframe *infoframe;
> > +	struct infoframe_avi infoframe_avi;
> > +	uint8_t vic = 16; /* TODO: test more VICs */
> > +	const struct vic_mode *vic_mode;
> > +	uint32_t aspect_ratio;
> > +	enum infoframe_avi_picture_aspect_ratio frame_ar;
> > +
> > +	igt_require(chamelium_supports_get_last_infoframe(data->chamelium));
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	output = chamelium_prepare_output(data, port,
> > +					  IGT_CUSTOM_EDID_ASPECT_RATIO);
> > +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	vic_mode = &vic_modes[vic];
> > +	aspect_ratio = vic_mode->picture_ar;
> > +
> > +	found = false;
> > +	igt_assert(connector->count_modes > 0);
> > +	for (i = 0; i < connector->count_modes; i++) {
> > +		mode = &connector->modes[i];
> > +
> > +		if (vic_mode_matches_drm(vic_mode, mode)) {
> > +			found = true;
> > +			break;
> > +		}
> > +	}
> > +	igt_assert_f(found,
> > +		     "Failed to find mode with the correct aspect ratio\n");
> > +
> > +	fb_id = igt_create_color_pattern_fb(data->drm_fd, mode->hdisplay,
> > +					    mode->vdisplay, DRM_FORMAT_XRGB8888,
> > +					    DRM_FORMAT_MOD_LINEAR, 0, 0, 0,
> > +					    &fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	chamelium_enable_output(data, port, output, mode, &fb);
> > +
> > +	infoframe = chamelium_get_last_infoframe(data->chamelium, port,
> > +						 CHAMELIUM_INFOFRAME_AVI);
> > +	igt_assert_f(infoframe, "AVI InfoFrame not received\n");
> > +
> > +	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
> > +				 infoframe->payload, infoframe->payload_size);
> > +	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
> > +
> > +	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
> > +
> > +	igt_debug("Checking AVI InfoFrame\n");
> > +	igt_debug("Picture aspect ratio: got %d, expected %d\n",
> > +		  infoframe_avi.picture_aspect_ratio, frame_ar);
> > +	igt_debug("Video Identification Code (VIC): got %d, expected %d\n",
> > +		  infoframe_avi.vic, vic);
> > +
> > +	igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar);
> > +	igt_assert(infoframe_avi.vic == vic);
> > +
> > +	chamelium_infoframe_destroy(infoframe);
> > +	igt_remove_fb(data->drm_fd, &fb);
> > +	drmModeFreeConnector(connector);
> > +}
> > +
> > +static const char test_display_planes_random_desc[] =
> > +	"Setup a few overlay planes with random parameters, capture the frame "
> > +	"and check it matches the expected output";
> > +static void test_display_planes_random(chamelium_data_t *data,
> > +				       struct chamelium_port *port,
> > +				       enum chamelium_check check)
> > +{
> > +	igt_output_t *output;
> > +	drmModeModeInfo *mode;
> > +	igt_plane_t *primary_plane;
> > +	struct igt_fb primary_fb;
> > +	struct igt_fb result_fb;
> > +	struct igt_fb *overlay_fbs;
> > +	igt_crc_t *crc;
> > +	igt_crc_t *expected_crc;
> > +	struct chamelium_fb_crc_async_data *fb_crc;
> > +	unsigned int overlay_planes_max = 0;
> > +	unsigned int overlay_planes_count;
> > +	cairo_surface_t *result_surface;
> > +	int captured_frame_count;
> > +	bool allow_scaling;
> > +	bool allow_yuv;
> > +	unsigned int i;
> > +	unsigned int fb_id;
> > +
> > +	switch (check) {
> > +	case CHAMELIUM_CHECK_CRC:
> > +		allow_scaling = false;
> > +		allow_yuv = false;
> > +		break;
> > +	case CHAMELIUM_CHECK_CHECKERBOARD:
> > +		allow_scaling = true;
> > +		allow_yuv = true;
> > +		break;
> > +	default:
> > +		igt_assert(false);
> > +	}
> > +
> > +	srand(time(NULL));
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	/* Find the connector and pipe. */
> > +	output = chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE);
> > +
> > +	mode = igt_output_get_mode(output);
> > +
> > +	/* Get a framebuffer for the primary plane. */
> > +	primary_plane =
> > +		igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	igt_assert(primary_plane);
> > +
> > +	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> > +					 DRM_FORMAT_XRGB8888, 64, &primary_fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	/* Get a framebuffer for the cairo composition result. */
> > +	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay, mode->vdisplay,
> > +			      DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
> > +			      &result_fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
> > +
> > +	/* Paint the primary framebuffer on the result surface. */
> > +	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
> > +			 &primary_fb);
> > +
> > +	/* Configure the primary plane. */
> > +	igt_plane_set_fb(primary_plane, &primary_fb);
> > +
> > +	overlay_planes_max =
> > +		igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY);
> > +
> > +	/* Limit the number of planes to a reasonable scene. */
> > +	overlay_planes_max = min(overlay_planes_max, 4u);
> > +
> > +	overlay_planes_count = (rand() % overlay_planes_max) + 1;
> > +	igt_debug("Using %d overlay planes\n", overlay_planes_count);
> > +
> > +	overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count);
> > +
> > +	for (i = 0; i < overlay_planes_count; i++) {
> > +		struct igt_fb *overlay_fb = &overlay_fbs[i];
> > +		igt_plane_t *plane = igt_output_get_plane_type_index(
> > +			output, DRM_PLANE_TYPE_OVERLAY, i);
> > +		igt_assert(plane);
> > +
> > +		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
> > +					 result_surface, allow_scaling,
> > +					 allow_yuv);
> > +	}
> > +
> > +	cairo_surface_destroy(result_surface);
> > +
> > +	if (check == CHAMELIUM_CHECK_CRC)
> > +		fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> > +								&result_fb);
> > +
> > +	igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > +
> > +	if (check == CHAMELIUM_CHECK_CRC) {
> > +		chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1);
> > +		crc = chamelium_read_captured_crcs(data->chamelium,
> > +						   &captured_frame_count);
> > +
> > +		igt_assert(captured_frame_count == 1);
> > +
> > +		expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> > +
> > +		chamelium_assert_crc_eq_or_dump(data->chamelium, expected_crc,
> > +						crc, &result_fb, 0);
> > +
> > +		free(expected_crc);
> > +		free(crc);
> > +	} else if (check == CHAMELIUM_CHECK_CHECKERBOARD) {
> > +		struct chamelium_frame_dump *dump;
> > +
> > +		dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0,
> > +						  0, 0);
> > +		chamelium_assert_frame_match_or_dump(data->chamelium, port,
> > +						     dump, &result_fb, check);
> > +		chamelium_destroy_frame_dump(dump);
> > +	}
> > +
> > +	for (i = 0; i < overlay_planes_count; i++)
> > +		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
> > +
> > +	free(overlay_fbs);
> > +
> > +	igt_remove_fb(data->drm_fd, &primary_fb);
> > +	igt_remove_fb(data->drm_fd, &result_fb);
> > +}
> > +
> > +IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board");
> > +igt_main
> > +{
> > +	chamelium_data_t data;
> > +	struct chamelium_port *port;
> > +	int p;
> > +
> > +	igt_fixture {
> > +		chamelium_init_test(&data);
> > +	}
> > +
> > +	igt_describe("DisplayPort tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_display_all_modes_desc);
> > +		connector_subtest("dp-crc-single", DisplayPort)
> > +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > +					       CHAMELIUM_CHECK_CRC, 1);
> > +
> > +		igt_describe(test_display_one_mode_desc);
> > +		connector_subtest("dp-crc-fast", DisplayPort)
> > +			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> > +					      CHAMELIUM_CHECK_CRC, 1);
> > +
> > +		igt_describe(test_display_all_modes_desc);
> > +		connector_subtest("dp-crc-multiple", DisplayPort)
> > +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > +					       CHAMELIUM_CHECK_CRC, 3);
> > +
> > +		igt_describe(test_display_frame_dump_desc);
> > +		connector_subtest("dp-frame-dump", DisplayPort)
> > +			test_display_frame_dump(&data, port);
> > +	}
> > +
> > +	igt_describe("HDMI tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_display_all_modes_desc);
> > +		connector_subtest("hdmi-crc-single", HDMIA)
> > +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > +					       CHAMELIUM_CHECK_CRC, 1);
> > +
> > +		igt_describe(test_display_one_mode_desc);
> > +		connector_subtest("hdmi-crc-fast", HDMIA)
> > +			test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888,
> > +					      CHAMELIUM_CHECK_CRC, 1);
> > +
> > +		igt_describe(test_display_all_modes_desc);
> > +		connector_subtest("hdmi-crc-multiple", HDMIA)
> > +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > +					       CHAMELIUM_CHECK_CRC, 3);
> > +
> > +		igt_describe(test_display_one_mode_desc);
> > +		connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA)
> > +		{
> > +			int k;
> > +			igt_output_t *output;
> > +			igt_plane_t *primary;
> > +
> > +			output = chamelium_prepare_output(&data, port,
> > +							  IGT_CUSTOM_EDID_BASE);
> > +			primary = igt_output_get_plane_type(
> > +				output, DRM_PLANE_TYPE_PRIMARY);
> > +			igt_assert(primary);
> > +
> > +			for (k = 0; k < primary->format_mod_count; k++) {
> > +				if (!igt_fb_supported_format(
> > +					    primary->formats[k]))
> > +					continue;
> > +
> > +				if (igt_format_is_yuv(primary->formats[k]))
> > +					continue;
> > +
> > +				if (primary->modifiers[k] !=
> > +				    DRM_FORMAT_MOD_LINEAR)
> > +					continue;
> > +
> > +				igt_dynamic_f(
> > +					"%s",
> > +					igt_format_str(primary->formats[k]))
> > +					test_display_one_mode(
> > +						&data, port,
> > +						primary->formats[k],
> > +						CHAMELIUM_CHECK_CRC, 1);
> > +			}
> > +		}
> > +
> > +		igt_describe(test_display_planes_random_desc);
> > +		connector_subtest("hdmi-crc-planes-random", HDMIA)
> > +			test_display_planes_random(&data, port,
> > +						   CHAMELIUM_CHECK_CRC);
> > +
> > +		igt_describe(test_display_one_mode_desc);
> > +		connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA)
> > +		{
> > +			int k;
> > +			igt_output_t *output;
> > +			igt_plane_t *primary;
> > +
> > +			output = chamelium_prepare_output(&data, port,
> > +							  IGT_CUSTOM_EDID_BASE);
> > +			primary = igt_output_get_plane_type(
> > +				output, DRM_PLANE_TYPE_PRIMARY);
> > +			igt_assert(primary);
> > +
> > +			for (k = 0; k < primary->format_mod_count; k++) {
> > +				if (!igt_fb_supported_format(
> > +					    primary->formats[k]))
> > +					continue;
> > +
> > +				if (!igt_format_is_yuv(primary->formats[k]))
> > +					continue;
> > +
> > +				if (primary->modifiers[k] !=
> > +				    DRM_FORMAT_MOD_LINEAR)
> > +					continue;
> > +
> > +				igt_dynamic_f(
> > +					"%s",
> > +					igt_format_str(primary->formats[k]))
> > +					test_display_one_mode(
> > +						&data, port,
> > +						primary->formats[k],
> > +						CHAMELIUM_CHECK_CHECKERBOARD,
> > +						1);
> > +			}
> > +		}
> > +
> > +		igt_describe(test_display_planes_random_desc);
> > +		connector_subtest("hdmi-cmp-planes-random", HDMIA)
> > +			test_display_planes_random(
> > +				&data, port, CHAMELIUM_CHECK_CHECKERBOARD);
> > +
> > +		igt_describe(test_display_frame_dump_desc);
> > +		connector_subtest("hdmi-frame-dump", HDMIA)
> > +			test_display_frame_dump(&data, port);
> > +
> > +		igt_describe(test_display_aspect_ratio_desc);
> > +		connector_subtest("hdmi-aspect-ratio", HDMIA)
> > +			test_display_aspect_ratio(&data, port);
> > +	}
> > +
> > +	igt_describe("VGA tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_VGA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_display_all_modes_desc);
> > +		connector_subtest("vga-frame-dump", VGA)
> > +			test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888,
> > +					       CHAMELIUM_CHECK_ANALOG, 1);
> > +	}
> > +
> > +	igt_fixture {
> > +		igt_display_fini(&data.display);
> > +		close(data.drm_fd);
> > +	}
> > +}
> > diff --git a/tests/chamelium/kms_chamelium_helper.c b/tests/chamelium/kms_chamelium_helper.c
> > new file mode 100644
> > index 00000000..b9544288
> > --- /dev/null
> > +++ b/tests/chamelium/kms_chamelium_helper.c
> > @@ -0,0 +1,330 @@
> > +// SPDX-License-Identifier: MIT
> > +/*
> > + * A helper library for all Chamelium tests.
> > + *
> > + * Copyright 2022 Google LLC.
> > + *
> > + * Authors: Mark Yacoub <markyacoub@chromium.org>
> > + */
> > +
> > +#include "igt_edid.h"
> > +#include "kms_chamelium_helper.h"
> > +
> > +void chamelium_init_test(chamelium_data_t *data)
> > +{
> > +	int i;
> > +
> > +	/* So fbcon doesn't try to reprobe things itself */
> > +	kmstest_set_vt_graphics_mode();
> > +
> > +	data->drm_fd = drm_open_driver_master(DRIVER_ANY);
> > +	igt_display_require(&data->display, data->drm_fd);
> > +	igt_require(data->display.is_atomic);
> > +
> > +	/*
> > +	 * XXX: disabling modeset, can be removed when
> > +	 * igt_display_require will start doing this for us
> > +	 */
> > +	igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > +
> > +	/* we need to initalize chamelium after igt_display_require */
> > +	data->chamelium = chamelium_init(data->drm_fd, &data->display);
> > +	igt_require(data->chamelium);
> > +
> > +	data->ports = chamelium_get_ports(data->chamelium, &data->port_count);
> > +
> > +	for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) {
> > +		data->edids[i] = chamelium_new_edid(data->chamelium,
> > +						    igt_kms_get_custom_edid(i));
> > +	}
> > +}
> > +
> > +/* Wait for hotplug and return the remaining time left from timeout */
> > +bool chamelium_wait_for_hotplug(struct udev_monitor *mon, int *timeout)
> > +{
> > +	struct timespec start, end;
> > +	int elapsed;
> > +	bool detected;
> > +
> > +	igt_assert_eq(igt_gettime(&start), 0);
> > +	detected = igt_hotplug_detected(mon, *timeout);
> > +	igt_assert_eq(igt_gettime(&end), 0);
> > +
> > +	elapsed = igt_time_elapsed(&start, &end);
> > +	igt_assert_lte(0, elapsed);
> > +	*timeout = max(0, *timeout - elapsed);
> > +
> > +	return detected;
> > +}
> > +
> > +/**
> > + * chamelium_wait_for_connector_after_hotplug:
> > + *
> > + * Waits for the connector attached to @port to have a status of @status after
> > + * it's plugged/unplugged.
> > + *
> > + */
> > +void chamelium_wait_for_connector_after_hotplug(chamelium_data_t *data,
> > +						struct udev_monitor *mon,
> > +						struct chamelium_port *port,
> > +						drmModeConnection status)
> > +{
> > +	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> > +	int hotplug_count = 0;
> > +
> > +	igt_debug("Waiting for %s to get %s after a hotplug event...\n",
> > +		  chamelium_port_get_name(port),
> > +		  kmstest_connector_status_str(status));
> > +
> > +	while (timeout > 0) {
> > +		if (!chamelium_wait_for_hotplug(mon, &timeout))
> > +			break;
> > +
> > +		hotplug_count++;
> > +
> > +		if (chamelium_reprobe_connector(&data->display, data->chamelium,
> > +						port) == status)
> > +			return;
> > +	}
> > +
> > +	igt_assert_f(
> > +		false,
> > +		"Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n",
> > +		chamelium_port_get_name(port),
> > +		kmstest_connector_status_str(status),
> > +		kmstest_connector_status_str(chamelium_reprobe_connector(
> > +			&data->display, data->chamelium, port)),
> > +		hotplug_count);
> > +}
> > +
> > +/**
> > + * chamelium_port_get_connector:
> > + * @data: The Chamelium data instance to use
> > + * @port: The chamelium port to prepare its connector
> > + * @edid: The chamelium's default EDID has a lot of resolutions, way more then
> > + * 		  we need to test. Additionally the default EDID doesn't support
> > + *        HDMI audio.
> > + *
> > + * Makes sure the output display of the connector attached to @port is connected
> > + * and ready for use.
> > + *
> > + * Returns: a pointer to the enabled igt_output_t
> > + */
> > +igt_output_t *chamelium_prepare_output(chamelium_data_t *data,
> > +				       struct chamelium_port *port,
> > +				       enum igt_custom_edid_type edid)
> > +{
> > +	igt_display_t *display = &data->display;
> > +	igt_output_t *output;
> > +	enum pipe pipe;
> > +
> > +	/* The chamelium's default EDID has a lot of resolutions, way more then
> > +	 * we need to test. Additionally the default EDID doesn't support HDMI
> > +	 * audio.
> > +	 */
> > +	chamelium_set_edid(data, port, edid);
> > +
> > +	chamelium_plug(data->chamelium, port);
> > +	chamelium_wait_for_conn_status_change(&data->display, data->chamelium,
> > +					      port, DRM_MODE_CONNECTED);
> > +
> > +	igt_display_reset(display);
> > +
> > +	output = chamelium_get_output_for_port(data, port);
> > +
> > +	/* Refresh pipe to update connected status */
> > +	igt_output_set_pipe(output, PIPE_NONE);
> > +
> > +	pipe = chamelium_get_pipe_for_output(display, output);
> > +	igt_output_set_pipe(output, pipe);
> > +
> > +	return output;
> > +}
> > +
> > +/**
> > + * chamelium_enable_output:
> > + *
> > + * Modesets the connector attached to @port for the assigned @mode and draws the
> > + * @fb.
> > + *
> > + */
> > +void chamelium_enable_output(chamelium_data_t *data,
> > +			     struct chamelium_port *port, igt_output_t *output,
> > +			     drmModeModeInfo *mode, struct igt_fb *fb)
> > +{
> > +	igt_display_t *display = output->display;
> > +	igt_plane_t *primary =
> > +		igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	drmModeConnector *connector =
> > +		chamelium_port_get_connector(data->chamelium, port, false);
> > +
> > +	igt_assert(primary);
> > +
> > +	igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
> > +	igt_plane_set_fb(primary, fb);
> > +	igt_output_override_mode(output, mode);
> > +
> > +	/* Clear any color correction values that might be enabled */
> > +	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT))
> > +		igt_pipe_obj_replace_prop_blob(primary->pipe,
> > +					       IGT_CRTC_DEGAMMA_LUT, NULL, 0);
> > +	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT))
> > +		igt_pipe_obj_replace_prop_blob(primary->pipe,
> > +					       IGT_CRTC_GAMMA_LUT, NULL, 0);
> > +	if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM))
> > +		igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM,
> > +					       NULL, 0);
> > +
> > +	igt_display_commit2(display, COMMIT_ATOMIC);
> > +
> > +	if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA)
> > +		usleep(250000);
> > +
> > +	drmModeFreeConnector(connector);
> > +}
> > +
> > +/* Return pipe attached to @outpu.t */
> > +enum pipe chamelium_get_pipe_for_output(igt_display_t *display,
> > +					igt_output_t *output)
> > +{
> > +	enum pipe pipe;
> > +
> > +	for_each_pipe(display, pipe) {
> > +		if (igt_pipe_connector_valid(pipe, output)) {
> > +			return pipe;
> > +		}
> > +	}
> > +
> > +	igt_assert_f(false, "No pipe found for output %s\n",
> > +		     igt_output_name(output));
> > +}
> > +
> > +static void chamelium_paint_xr24_pattern(uint32_t *data, size_t width,
> > +					 size_t height, size_t stride,
> > +					 size_t block_size)
> > +{
> > +	uint32_t colors[] = { 0xff000000, 0xffff0000, 0xff00ff00, 0xff0000ff,
> > +			      0xffffffff };
> > +	unsigned i, j;
> > +
> > +	for (i = 0; i < height; i++)
> > +		for (j = 0; j < width; j++)
> > +			*(data + i * stride / 4 +
> > +			  j) = colors[((j / block_size) + (i / block_size)) % 5];
> > +}
> > +
> > +/**
> > + * chamelium_get_pattern_fb:
> > + *
> > + * Creates an @fb with an xr24 pattern and returns the fb_id.
> > + *
> > + */
> > +int chamelium_get_pattern_fb(chamelium_data_t *data, size_t width,
> > +			     size_t height, uint32_t fourcc, size_t block_size,
> > +			     struct igt_fb *fb)
> > +{
> > +	int fb_id;
> > +	void *ptr;
> > +
> > +	igt_assert(fourcc == DRM_FORMAT_XRGB8888);
> > +
> > +	fb_id = igt_create_fb(data->drm_fd, width, height, fourcc,
> > +			      DRM_FORMAT_MOD_LINEAR, fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	ptr = igt_fb_map_buffer(fb->fd, fb);
> > +	igt_assert(ptr);
> > +
> > +	chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0],
> > +				     block_size);
> > +	igt_fb_unmap_buffer(fb, ptr);
> > +
> > +	return fb_id;
> > +}
> > +
> > +/* Generate a simple @fb for the size of @mode. */
> > +void chamelium_create_fb_for_mode(chamelium_data_t *data, struct igt_fb *fb,
> > +				  drmModeModeInfo *mode)
> > +{
> > +	int fb_id;
> > +
> > +	fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay,
> > +					 DRM_FORMAT_XRGB8888, 64, fb);
> > +
> > +	igt_assert(fb_id > 0);
> > +}
> > +
> > +/* Returns the first preferred mode for the connector attached to @port. */
> > +drmModeModeInfo chamelium_get_mode_for_port(struct chamelium *chamelium,
> > +					    struct chamelium_port *port)
> > +{
> > +	drmModeConnector *connector =
> > +		chamelium_port_get_connector(chamelium, port, false);
> > +	drmModeModeInfo mode;
> > +	igt_assert(&connector->modes[0] != NULL);
> > +	memcpy(&mode, &connector->modes[0], sizeof(mode));
> > +	drmModeFreeConnector(connector);
> > +	return mode;
> > +}
> > +
> > +/* Returns the igt display output for the connector attached to @port. */
> > +igt_output_t *chamelium_get_output_for_port(chamelium_data_t *data,
> > +					    struct chamelium_port *port)
> > +{
> > +	drmModeConnector *connector =
> > +		chamelium_port_get_connector(data->chamelium, port, true);
> > +	igt_output_t *output =
> > +		igt_output_from_connector(&data->display, connector);
> > +	drmModeFreeConnector(connector);
> > +	igt_assert(output != NULL);
> > +	return output;
> > +}
> > +
> > +/* Set the EDID of index @edid to Chamelium's @port. */
> > +void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *port,
> > +			enum igt_custom_edid_type edid)
> > +{
> > +	chamelium_port_set_edid(data->chamelium, port, data->edids[edid]);
> > +}
> > +
> > +/**
> > + * chamelium_check_analog_bridge:
> > + *
> > + * Check if the connector associalted to @port is an analog bridge by checking
> > + * if it has its own EDID.
> > + *
> > + */
> > +bool chamelium_check_analog_bridge(chamelium_data_t *data,
> > +				   struct chamelium_port *port)
> > +{
> > +	drmModePropertyBlobPtr edid_blob = NULL;
> > +	drmModeConnector *connector =
> > +		chamelium_port_get_connector(data->chamelium, port, false);
> > +	uint64_t edid_blob_id;
> > +	const struct edid *edid;
> > +	char edid_vendor[3];
> > +
> > +	if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) {
> > +		drmModeFreeConnector(connector);
> > +		return false;
> > +	}
> > +
> > +	igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
> > +					DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
> > +					&edid_blob_id, NULL));
> > +	igt_assert(edid_blob =
> > +			   drmModeGetPropertyBlob(data->drm_fd, edid_blob_id));
> > +
> > +	edid = (const struct edid *)edid_blob->data;
> > +	edid_get_mfg(edid, edid_vendor);
> > +
> > +	drmModeFreePropertyBlob(edid_blob);
> > +	drmModeFreeConnector(connector);
> > +
> > +	/* Analog bridges provide their own EDID */
> > +	if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' ||
> > +	    edid_vendor[2] != 'T')
> > +		return true;
> > +
> > +	return false;
> > +}
> > \ No newline at end of file
> > diff --git a/tests/chamelium/kms_chamelium_helper.h b/tests/chamelium/kms_chamelium_helper.h
> > new file mode 100644
> > index 00000000..09fa4829
> > --- /dev/null
> > +++ b/tests/chamelium/kms_chamelium_helper.h
> > @@ -0,0 +1,74 @@
> > +/* SPDX-License-Identifier: MIT */
> > +/*
> > + * A helper library for all Chamelium tests.
> > + *
> > + * Copyright 2022 Google LLC.
> > + *
> > + * Authors: Mark Yacoub <markyacoub@chromium.org>
> > + */
> > +
> > +#ifndef TESTS_CHAMELIUM_CHAMELIUM_HELPER_H
> > +#define TESTS_CHAMELIUM_CHAMELIUM_HELPER_H
> > +
> > +#include "igt.h"
> > +
> > +#define ONLINE_TIMEOUT 20 /* seconds */
> > +
> > +#define for_each_port(p, port)                                 \
> > +	for (p = 0, port = data.ports[p]; p < data.port_count; \
> > +	     p++, port = data.ports[p])
> > +
> > +#define connector_subtest(name__, type__)                           \
> > +	igt_subtest(name__)                                         \
> > +	for_each_port(p, port) if (chamelium_port_get_type(port) == \
> > +				   DRM_MODE_CONNECTOR_##type__)
> > +
> > +/*
> > + * The chamelium data structure is used to store all the information known about
> > + * chamelium to run the tests.
> > + */
> > +typedef struct {
> > +	struct chamelium *chamelium;
> > +	struct chamelium_port **ports;
> > +	igt_display_t display;
> > +	int port_count;
> > +
> > +	int drm_fd;
> > +
> > +	struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT];
> > +} chamelium_data_t;
> > +
> > +void chamelium_init_test(chamelium_data_t *data);
> > +
> > +bool chamelium_wait_for_hotplug(struct udev_monitor *mon, int *timeout);
> > +void chamelium_wait_for_connector_after_hotplug(chamelium_data_t *data,
> > +						struct udev_monitor *mon,
> > +						struct chamelium_port *port,
> > +						drmModeConnection status);
> > +
> > +igt_output_t *chamelium_prepare_output(chamelium_data_t *data,
> > +				       struct chamelium_port *port,
> > +				       enum igt_custom_edid_type edid);
> > +void chamelium_enable_output(chamelium_data_t *data,
> > +			     struct chamelium_port *port, igt_output_t *output,
> > +			     drmModeModeInfo *mode, struct igt_fb *fb);
> > +enum pipe chamelium_get_pipe_for_output(igt_display_t *display,
> > +					igt_output_t *output);
> > +
> > +int chamelium_get_pattern_fb(chamelium_data_t *data, size_t width,
> > +			     size_t height, uint32_t fourcc, size_t block_size,
> > +			     struct igt_fb *fb);
> > +void chamelium_create_fb_for_mode(chamelium_data_t *data, struct igt_fb *fb,
> > +				  drmModeModeInfo *mode);
> > +drmModeModeInfo chamelium_get_mode_for_port(struct chamelium *chamelium,
> > +					    struct chamelium_port *port);
> > +igt_output_t *chamelium_get_output_for_port(chamelium_data_t *data,
> > +					    struct chamelium_port *port);
> > +
> > +void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *port,
> > +			enum igt_custom_edid_type edid);
> > +
> > +bool chamelium_check_analog_bridge(chamelium_data_t *data,
> > +				   struct chamelium_port *port);
> > +
> > +#endif /* TESTS_CHAMELIUM_CHAMELIUM_HELPER_H */
> > diff --git a/tests/chamelium/kms_chamelium_hpd.c b/tests/chamelium/kms_chamelium_hpd.c
> > new file mode 100644
> > index 00000000..8a4e1aba
> > --- /dev/null
> > +++ b/tests/chamelium/kms_chamelium_hpd.c
> > @@ -0,0 +1,512 @@
> > +// SPDX-License-Identifier: MIT
> > +/*
> > + * A Chamelium test for testing the HPD functionality.
> > + *
> > + * Copyright 2022 Google LLC.
> > + *
> > + * Authors: Mark Yacoub <markyacoub@chromium.org>
> > + */
> > +
> > +#include "kms_chamelium_helper.h"
> > +
> > +#define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */
> > +#define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */
> > +
> > +#define HPD_TOGGLE_COUNT_VGA 5
> > +#define HPD_TOGGLE_COUNT_DP_HDMI 15
> > +#define HPD_TOGGLE_COUNT_FAST 3
> > +
> > +enum test_modeset_mode {
> > +	TEST_MODESET_ON,
> > +	TEST_MODESET_ON_OFF,
> > +	TEST_MODESET_OFF,
> > +};
> > +
> > +static void try_suspend_resume_hpd(chamelium_data_t *data,
> > +				   struct chamelium_port *port,
> > +				   enum igt_suspend_state state,
> > +				   enum igt_suspend_test test,
> > +				   struct udev_monitor *mon, bool connected)
> > +{
> > +	drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED :
> > +						     DRM_MODE_CONNECTED;
> > +	int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> > +	int delay;
> > +	int p;
> > +
> > +	igt_flush_uevents(mon);
> > +
> > +	delay = igt_get_autoresume_delay(state) * 1000 / 2;
> > +
> > +	if (port) {
> > +		chamelium_schedule_hpd_toggle(data->chamelium, port, delay,
> > +					      !connected);
> > +	} else {
> > +		for (p = 0; p < data->port_count; p++) {
> > +			port = data->ports[p];
> > +			chamelium_schedule_hpd_toggle(data->chamelium, port,
> > +						      delay, !connected);
> > +		}
> > +
> > +		port = NULL;
> > +	}
> > +
> > +	igt_system_suspend_autoresume(state, test);
> > +	igt_assert(chamelium_wait_for_hotplug(mon, &timeout));
> > +	chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> > +
> > +	if (port) {
> > +		igt_assert_eq(chamelium_reprobe_connector(
> > +				      &data->display, data->chamelium, port),
> > +			      target_state);
> > +	} else {
> > +		for (p = 0; p < data->port_count; p++) {
> > +			drmModeConnection current_state;
> > +
> > +			port = data->ports[p];
> > +			/*
> > +			 * There could be as many hotplug events sent by
> > +			 * driver as connectors we scheduled an HPD toggle on
> > +			 * above, depending on timing. So if we're not seeing
> > +			 * the expected connector state try to wait for an HPD
> > +			 * event for each connector/port.
> > +			 */
> > +			current_state = chamelium_reprobe_connector(
> > +				&data->display, data->chamelium, port);
> > +			if (p > 0 && current_state != target_state) {
> > +				igt_assert(chamelium_wait_for_hotplug(
> > +					mon, &timeout));
> > +				current_state = chamelium_reprobe_connector(
> > +					&data->display, data->chamelium, port);
> > +			}
> > +
> > +			igt_assert_eq(current_state, target_state);
> > +		}
> > +
> > +		port = NULL;
> > +	}
> > +}
> > +
> > +static const char test_basic_hotplug_desc[] =
> > +	"Check that we get uevents and updated connector status on "
> > +	"hotplug and unplug";
> > +static void test_hotplug(chamelium_data_t *data, struct chamelium_port *port,
> > +			 int toggle_count, enum test_modeset_mode modeset_mode)
> > +{
> > +	int i;
> > +	enum pipe pipe;
> > +	struct igt_fb fb = { 0 };
> > +	drmModeModeInfo mode;
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +	igt_output_t *output = chamelium_get_output_for_port(data, port);
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, NULL,
> > +			      data->ports, data->port_count);
> > +
> > +	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> > +
> > +	for (i = 0; i < toggle_count; i++) {
> > +		igt_flush_uevents(mon);
> > +
> > +		/* Check if we get a sysfs hotplug event */
> > +		chamelium_plug(data->chamelium, port);
> > +
> > +		chamelium_wait_for_connector_after_hotplug(data, mon, port,
> > +							   DRM_MODE_CONNECTED);
> > +		igt_flush_uevents(mon);
> > +
> > +		if (modeset_mode == TEST_MODESET_ON_OFF ||
> > +		    (modeset_mode == TEST_MODESET_ON && i == 0)) {
> > +			if (i == 0) {
> > +				/* We can only get mode and pipe once we are
> > +				 * connected */
> > +				output = chamelium_get_output_for_port(data,
> > +								       port);
> > +				pipe = chamelium_get_pipe_for_output(
> > +					&data->display, output);
> > +				mode = chamelium_get_mode_for_port(
> > +					data->chamelium, port);
> > +				chamelium_create_fb_for_mode(data, &fb, &mode);
> > +			}
> > +
> > +			igt_output_set_pipe(output, pipe);
> > +			chamelium_enable_output(data, port, output, &mode, &fb);
> > +		}
> > +
> > +		/* Now check if we get a hotplug from disconnection */
> > +		chamelium_unplug(data->chamelium, port);
> > +
> > +		chamelium_wait_for_connector_after_hotplug(
> > +			data, mon, port, DRM_MODE_DISCONNECTED);
> > +
> > +		igt_flush_uevents(mon);
> > +
> > +		if (modeset_mode == TEST_MODESET_ON_OFF) {
> > +			igt_output_set_pipe(output, PIPE_NONE);
> > +			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > +		}
> > +	}
> > +
> > +	igt_cleanup_uevents(mon);
> > +	igt_hpd_storm_reset(data->drm_fd);
> > +	igt_remove_fb(data->drm_fd, &fb);
> > +}
> > +
> > +static const char test_hotplug_for_each_pipe_desc[] =
> > +	"Check that we get uevents and updated connector status on "
> > +	"hotplug and unplug for each pipe with valid output";
> > +static void test_hotplug_for_each_pipe(chamelium_data_t *data,
> > +				       struct chamelium_port *port)
> > +{
> > +	igt_output_t *output;
> > +	enum pipe pipe;
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> > +	/* Disconnect if any port got connected */
> > +	chamelium_unplug(data->chamelium, port);
> > +	chamelium_wait_for_connector_after_hotplug(data, mon, port,
> > +						   DRM_MODE_DISCONNECTED);
> > +
> > +	for_each_pipe(&data->display, pipe) {
> > +		igt_flush_uevents(mon);
> > +		/* Check if we get a sysfs hotplug event */
> > +		chamelium_plug(data->chamelium, port);
> > +		chamelium_wait_for_connector_after_hotplug(data, mon, port,
> > +							   DRM_MODE_CONNECTED);
> > +		igt_flush_uevents(mon);
> > +		output = chamelium_get_output_for_port(data, port);
> > +
> > +		/* If pipe is valid for output then set it */
> > +		if (igt_pipe_connector_valid(pipe, output)) {
> > +			igt_output_set_pipe(output, pipe);
> > +			igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > +		}
> > +
> > +		chamelium_unplug(data->chamelium, port);
> > +		chamelium_wait_for_connector_after_hotplug(
> > +			data, mon, port, DRM_MODE_DISCONNECTED);
> > +		igt_flush_uevents(mon);
> > +	}
> > +
> > +	igt_cleanup_uevents(mon);
> > +	igt_hpd_storm_reset(data->drm_fd);
> > +}
> > +
> > +static const char test_suspend_resume_hpd_desc[] =
> > +	"Toggle HPD during suspend, check that uevents are sent and connector "
> > +	"status is updated";
> > +static void test_suspend_resume_hpd(chamelium_data_t *data,
> > +				    struct chamelium_port *port,
> > +				    enum igt_suspend_state state,
> > +				    enum igt_suspend_test test)
> > +{
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	/* Make sure we notice new connectors after resuming */
> > +	try_suspend_resume_hpd(data, port, state, test, mon, false);
> > +
> > +	/* Now make sure we notice disconnected connectors after resuming */
> > +	try_suspend_resume_hpd(data, port, state, test, mon, true);
> > +
> > +	igt_cleanup_uevents(mon);
> > +}
> > +
> > +static const char test_suspend_resume_hpd_common_desc[] =
> > +	"Toggle HPD during suspend on all connectors, check that uevents are "
> > +	"sent and connector status is updated";
> > +static void test_suspend_resume_hpd_common(chamelium_data_t *data,
> > +					   enum igt_suspend_state state,
> > +					   enum igt_suspend_test test)
> > +{
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +	struct chamelium_port *port;
> > +	int p;
> > +
> > +	for (p = 0; p < data->port_count; p++) {
> > +		port = data->ports[p];
> > +		igt_debug("Testing port %s\n", chamelium_port_get_name(port));
> > +	}
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, NULL,
> > +			      data->ports, data->port_count);
> > +
> > +	/* Make sure we notice new connectors after resuming */
> > +	try_suspend_resume_hpd(data, NULL, state, test, mon, false);
> > +
> > +	/* Now make sure we notice disconnected connectors after resuming */
> > +	try_suspend_resume_hpd(data, NULL, state, test, mon, true);
> > +
> > +	igt_cleanup_uevents(mon);
> > +}
> > +
> > +static const char test_hpd_without_ddc_desc[] =
> > +	"Disable DDC on a VGA connector, check we still get a uevent on hotplug";
> > +static void test_hpd_without_ddc(chamelium_data_t *data,
> > +				 struct chamelium_port *port)
> > +{
> > +	struct udev_monitor *mon = igt_watch_uevents();
> > +
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +	igt_flush_uevents(mon);
> > +
> > +	/* Disable the DDC on the connector and make sure we still get a
> > +	 * hotplug
> > +	 */
> > +	chamelium_port_set_ddc_state(data->chamelium, port, false);
> > +	chamelium_plug(data->chamelium, port);
> > +
> > +	igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT));
> > +	igt_assert_eq(chamelium_reprobe_connector(&data->display,
> > +						  data->chamelium, port),
> > +		      DRM_MODE_CONNECTED);
> > +
> > +	igt_cleanup_uevents(mon);
> > +}
> > +
> > +static const char test_hpd_storm_detect_desc[] =
> > +	"Trigger a series of hotplugs in a very small timeframe to simulate a"
> > +	"bad cable, check the kernel falls back to polling to avoid a hotplug "
> > +	"storm";
> > +static void test_hpd_storm_detect(chamelium_data_t *data,
> > +				  struct chamelium_port *port, int width)
> > +{
> > +	struct udev_monitor *mon;
> > +	int count = 0;
> > +
> > +	igt_require_hpd_storm_ctl(data->drm_fd);
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	igt_hpd_storm_set_threshold(data->drm_fd, 1);
> > +	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> > +	igt_assert(igt_hpd_storm_detected(data->drm_fd));
> > +
> > +	mon = igt_watch_uevents();
> > +	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> > +
> > +	/*
> > +	 * Polling should have been enabled by the HPD storm at this point,
> > +	 * so we should only get at most 1 hotplug event
> > +	 */
> > +	igt_until_timeout(5)
> > +		count += igt_hotplug_detected(mon, 1);
> > +	igt_assert_lt(count, 2);
> > +
> > +	igt_cleanup_uevents(mon);
> > +	igt_hpd_storm_reset(data->drm_fd);
> > +}
> > +
> > +static const char test_hpd_storm_disable_desc[] =
> > +	"Disable HPD storm detection, trigger a storm and check the kernel "
> > +	"doesn't detect one";
> > +static void test_hpd_storm_disable(chamelium_data_t *data,
> > +				   struct chamelium_port *port, int width)
> > +{
> > +	igt_require_hpd_storm_ctl(data->drm_fd);
> > +	igt_modeset_disable_all_outputs(&data->display);
> > +	chamelium_reset_state(&data->display, data->chamelium, port,
> > +			      data->ports, data->port_count);
> > +
> > +	igt_hpd_storm_set_threshold(data->drm_fd, 0);
> > +	chamelium_fire_hpd_pulses(data->chamelium, port, width, 10);
> > +	igt_assert(!igt_hpd_storm_detected(data->drm_fd));
> > +
> > +	igt_hpd_storm_reset(data->drm_fd);
> > +}
> > +
> > +IGT_TEST_DESCRIPTION("Testing HPD with a Chamelium board");
> > +igt_main
> > +{
> > +	chamelium_data_t data;
> > +	struct chamelium_port *port;
> > +	int p;
> > +
> > +	igt_fixture {
> > +		chamelium_init_test(&data);
> > +	}
> > +
> > +	igt_describe("DisplayPort tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_DisplayPort,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("dp-hpd", DisplayPort)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_DP_HDMI,
> > +				     TEST_MODESET_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("dp-hpd-fast", DisplayPort) test_hotplug(
> > +			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("dp-hpd-enable-disable-mode", DisplayPort)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > +				     TEST_MODESET_ON_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("dp-hpd-with-enabled-mode", DisplayPort)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > +				     TEST_MODESET_ON);
> > +
> > +		igt_describe(test_hotplug_for_each_pipe_desc);
> > +		connector_subtest("dp-hpd-for-each-pipe", DisplayPort)
> > +			test_hotplug_for_each_pipe(&data, port);
> > +
> > +		igt_describe(test_suspend_resume_hpd_desc);
> > +		connector_subtest("dp-hpd-after-suspend", DisplayPort)
> > +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
> > +						SUSPEND_TEST_NONE);
> > +
> > +		igt_describe(test_suspend_resume_hpd_desc);
> > +		connector_subtest("dp-hpd-after-hibernate", DisplayPort)
> > +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
> > +						SUSPEND_TEST_DEVICES);
> > +
> > +		igt_describe(test_hpd_storm_detect_desc);
> > +		connector_subtest("dp-hpd-storm", DisplayPort)
> > +			test_hpd_storm_detect(&data, port,
> > +					      HPD_STORM_PULSE_INTERVAL_DP);
> > +
> > +		igt_describe(test_hpd_storm_disable_desc);
> > +		connector_subtest("dp-hpd-storm-disable", DisplayPort)
> > +			test_hpd_storm_disable(&data, port,
> > +					       HPD_STORM_PULSE_INTERVAL_DP);
> > +	}
> > +
> > +	igt_describe("HDMI tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_HDMIA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("hdmi-hpd", HDMIA)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_DP_HDMI,
> > +				     TEST_MODESET_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("hdmi-hpd-fast", HDMIA) test_hotplug(
> > +			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > +				     TEST_MODESET_ON_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > +				     TEST_MODESET_ON);
> > +
> > +		igt_describe(test_hotplug_for_each_pipe_desc);
> > +		connector_subtest("hdmi-hpd-for-each-pipe", HDMIA)
> > +			test_hotplug_for_each_pipe(&data, port);
> > +
> > +		igt_describe(test_suspend_resume_hpd_desc);
> > +		connector_subtest("hdmi-hpd-after-suspend", HDMIA)
> > +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
> > +						SUSPEND_TEST_NONE);
> > +
> > +		igt_describe(test_suspend_resume_hpd_desc);
> > +		connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
> > +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
> > +						SUSPEND_TEST_DEVICES);
> > +
> > +		igt_describe(test_hpd_storm_detect_desc);
> > +		connector_subtest("hdmi-hpd-storm", HDMIA)
> > +			test_hpd_storm_detect(&data, port,
> > +					      HPD_STORM_PULSE_INTERVAL_HDMI);
> > +
> > +		igt_describe(test_hpd_storm_disable_desc);
> > +		connector_subtest("hdmi-hpd-storm-disable", HDMIA)
> > +			test_hpd_storm_disable(&data, port,
> > +					       HPD_STORM_PULSE_INTERVAL_HDMI);
> > +	}
> > +
> > +	igt_describe("VGA tests");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			chamelium_require_connector_present(
> > +				data.ports, DRM_MODE_CONNECTOR_VGA,
> > +				data.port_count, 1);
> > +		}
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("vga-hpd", VGA) test_hotplug(
> > +			&data, port, HPD_TOGGLE_COUNT_VGA, TEST_MODESET_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("vga-hpd-fast", VGA) test_hotplug(
> > +			&data, port, HPD_TOGGLE_COUNT_FAST, TEST_MODESET_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("vga-hpd-enable-disable-mode", VGA)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > +				     TEST_MODESET_ON_OFF);
> > +
> > +		igt_describe(test_basic_hotplug_desc);
> > +		connector_subtest("vga-hpd-with-enabled-mode", VGA)
> > +			test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST,
> > +				     TEST_MODESET_ON);
> > +
> > +		igt_describe(test_suspend_resume_hpd_desc);
> > +		connector_subtest("vga-hpd-after-suspend", VGA)
> > +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_MEM,
> > +						SUSPEND_TEST_NONE);
> > +
> > +		igt_describe(test_suspend_resume_hpd_desc);
> > +		connector_subtest("vga-hpd-after-hibernate", VGA)
> > +			test_suspend_resume_hpd(&data, port, SUSPEND_STATE_DISK,
> > +						SUSPEND_TEST_DEVICES);
> > +
> > +		igt_describe(test_hpd_without_ddc_desc);
> > +		connector_subtest("vga-hpd-without-ddc", VGA)
> > +			test_hpd_without_ddc(&data, port);
> > +	}
> > +
> > +	igt_describe("Tests that operate on all connectors");
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			igt_require(data.port_count);
> > +		}
> > +
> > +		igt_describe(test_suspend_resume_hpd_common_desc);
> > +		igt_subtest("common-hpd-after-suspend")
> > +			test_suspend_resume_hpd_common(&data, SUSPEND_STATE_MEM,
> > +						       SUSPEND_TEST_NONE);
> > +
> > +		igt_describe(test_suspend_resume_hpd_common_desc);
> > +		igt_subtest("common-hpd-after-hibernate")
> > +			test_suspend_resume_hpd_common(&data,
> > +						       SUSPEND_STATE_DISK,
> > +						       SUSPEND_TEST_DEVICES);
> > +	}
> > +
> > +	igt_describe(test_hotplug_for_each_pipe_desc);
> > +	connector_subtest("vga-hpd-for-each-pipe", VGA)
> > +		test_hotplug_for_each_pipe(&data, port);
> > +
> > +	igt_fixture {
> > +		igt_display_fini(&data.display);
> > +		close(data.drm_fd);
> > +	}
> > +}
> > diff --git a/tests/intel-ci/blacklist.txt b/tests/intel-ci/blacklist.txt
> > index 0d307730..6e5cc436 100644
> > --- a/tests/intel-ci/blacklist.txt
> > +++ b/tests/intel-ci/blacklist.txt
> > @@ -77,7 +77,7 @@ igt@kms_frontbuffer_tracking@.*drrs.*
> >  # is too costly in comparison to the value
> >  # provided.
> >  ###############################################
> > -igt@kms_chamelium@hdmi-.*-planes-random
> > +igt@kms_chamelium_frames@hdmi-.*-planes-random
> >  ###############################################
> >  # Broadcom
> >  ###############################################
> > diff --git a/tests/intel-ci/fast-feedback.testlist b/tests/intel-ci/fast-feedback.testlist
> > index f57f8ff3..fb4c0f73 100644
> > --- a/tests/intel-ci/fast-feedback.testlist
> > +++ b/tests/intel-ci/fast-feedback.testlist
> > @@ -92,14 +92,14 @@ igt@kms_addfb_basic@unused-modifier
> >  igt@kms_addfb_basic@unused-offsets
> >  igt@kms_addfb_basic@unused-pitches
> >  igt@kms_busy@basic
> > -igt@kms_chamelium@dp-hpd-fast
> > -igt@kms_chamelium@dp-edid-read
> > -igt@kms_chamelium@dp-crc-fast
> > -igt@kms_chamelium@hdmi-hpd-fast
> > -igt@kms_chamelium@hdmi-edid-read
> > -igt@kms_chamelium@hdmi-crc-fast
> > -igt@kms_chamelium@vga-hpd-fast
> > -igt@kms_chamelium@vga-edid-read
> > +igt@kms_chamelium_hpd@dp-hpd-fast
> > +igt@kms_chamelium_edid@dp-edid-read
> > +igt@kms_chamelium_frames@dp-crc-fast
> > +igt@kms_chamelium_hpd@hdmi-hpd-fast
> > +igt@kms_chamelium_edid@hdmi-edid-read
> > +igt@kms_chamelium_frames@hdmi-crc-fast
> > +igt@kms_chamelium_hpd@vga-hpd-fast
> > +igt@kms_chamelium_edid@vga-edid-read
> >  igt@kms_prop_blob@basic
> >  igt@kms_cursor_legacy@basic-busy-flip-before-cursor
> >  igt@kms_cursor_legacy@basic-flip-after-cursor
> > @@ -174,5 +174,5 @@ igt@i915_suspend@basic-s2idle-without-i915
> >  igt@i915_suspend@basic-s3-without-i915
> >  igt@gem_exec_suspend@basic-s0
> >  igt@gem_exec_suspend@basic-s3
> > -igt@kms_chamelium@common-hpd-after-suspend
> > +igt@kms_chamelium_hpd@common-hpd-after-suspend
> >  igt@kms_pipe_crc_basic@suspend-read-crc
> > diff --git a/tests/kms_color_helper.h b/tests/kms_color_helper.h
> > index f0ae30e3..f9242232 100644
> > --- a/tests/kms_color_helper.h
> > +++ b/tests/kms_color_helper.h
> > @@ -27,7 +27,7 @@
> >  
> >  /*
> >   * This header is for code that is shared between kms_color.c and
> > - * kms_color_chamelium.c. Reusability elsewhere can be questionable.
> > + * kms_chamelium_color.c. Reusability elsewhere can be questionable.
> >   */
> >  
> >  #include <math.h>
> > diff --git a/tests/meson.build b/tests/meson.build
> > index 5c052e73..b52399d5 100644
> > --- a/tests/meson.build
> > +++ b/tests/meson.build
> > @@ -260,7 +260,10 @@ msm_progs = [
> >  ]
> >  
> >  chamelium_progs = [
> > -	'kms_chamelium',
> > +	'kms_chamelium_audio',
> > +	'kms_chamelium_edid',
> > +	'kms_chamelium_frames',
> > +	'kms_chamelium_hpd',
> >  ]
> >  
> >  test_deps = [ igt_deps ]
> > @@ -309,7 +312,8 @@ endforeach
> >  if chamelium.found()
> >  	foreach prog : chamelium_progs
> >  		test_executables += executable(prog,
> > -				 join_paths('chamelium', prog + '.c'),
> > +				 [join_paths('chamelium', prog + '.c'), 
> > +				 	join_paths('chamelium', 'kms_chamelium_helper.c')],
> >  				 dependencies : test_deps,
> >  				 install_dir : libexecdir,
> >  				 install_rpath : libexecdir_rpathdir,
> > @@ -436,13 +440,13 @@ test_executables += executable('kms_color',
> >  test_list += 'kms_color'
> >  
> >  if chamelium.found()
> > -       test_executables += executable('kms_color_chamelium',
> > -                             [ 'chamelium/kms_color_chamelium.c', 'kms_color_helper.c' ],
> > +       test_executables += executable('kms_chamelium_color',
> > +                             [ 'chamelium/kms_chamelium_color.c', 'kms_color_helper.c' ],
> >                               dependencies : test_deps + [ chamelium ],
> >                               install_dir : libexecdir,
> >                               install_rpath : libexecdir_rpathdir,
> >                               install : true)
> > -       test_list += 'kms_color_chamelium'
> > +       test_list += 'kms_chamelium_color'
> >  endif
> >  
> >  test_executables += executable('sw_sync', 'sw_sync.c',
> > diff --git a/tests/vc4_ci/vc4-chamelium-fast.testlist b/tests/vc4_ci/vc4-chamelium-fast.testlist
> > index dd45d12a..a5521021 100644
> > --- a/tests/vc4_ci/vc4-chamelium-fast.testlist
> > +++ b/tests/vc4_ci/vc4-chamelium-fast.testlist
> > @@ -1,14 +1,14 @@
> > -igt@kms_chamelium@hdmi-crc-abgr8888
> > -igt@kms_chamelium@hdmi-crc-argb1555
> > -igt@kms_chamelium@hdmi-crc-argb8888
> > -igt@kms_chamelium@hdmi-crc-bgr565
> > -igt@kms_chamelium@hdmi-crc-bgr888
> > -igt@kms_chamelium@hdmi-crc-fast
> > -igt@kms_chamelium@hdmi-crc-rgb565
> > -igt@kms_chamelium@hdmi-crc-rgb888
> > -igt@kms_chamelium@hdmi-crc-xbgr8888
> > -igt@kms_chamelium@hdmi-crc-xrgb1555
> > -igt@kms_chamelium@hdmi-crc-xrgb8888
> > -igt@kms_chamelium@hdmi-edid-read
> > -igt@kms_chamelium@hdmi-hpd
> > -igt@kms_chamelium@hdmi-hpd-fast
> > +igt@kms_chamelium_frames@hdmi-crc-abgr8888
> > +igt@kms_chamelium_frames@hdmi-crc-argb1555
> > +igt@kms_chamelium_frames@hdmi-crc-argb8888
> > +igt@kms_chamelium_frames@hdmi-crc-bgr565
> > +igt@kms_chamelium_frames@hdmi-crc-bgr888
> > +igt@kms_chamelium_frames@hdmi-crc-fast
> > +igt@kms_chamelium_frames@hdmi-crc-rgb565
> > +igt@kms_chamelium_frames@hdmi-crc-rgb888
> > +igt@kms_chamelium_frames@hdmi-crc-xbgr8888
> > +igt@kms_chamelium_frames@hdmi-crc-xrgb1555
> > +igt@kms_chamelium_frames@hdmi-crc-xrgb8888
> > +igt@kms_chamelium_edid@hdmi-edid-read
> > +igt@kms_chamelium_hpd@hdmi-hpd
> > +igt@kms_chamelium_hpd@hdmi-hpd-fast
> 
> -- 
> Jani Nikula, Intel Open Source Graphics Center

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2023-01-02 16:50 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-12-29 19:27 [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Mark Yacoub
2022-12-29 21:16 ` [igt-dev] ✓ Fi.CI.BAT: success for testing headers in CI (rev2) Patchwork
2022-12-29 23:27 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork
2022-12-30 12:51 ` [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI Jani Nikula
2023-01-02 16:50   ` Kamil Konieczny

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox