From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mga02.intel.com (mga02.intel.com [134.134.136.20]) by gabe.freedesktop.org (Postfix) with ESMTPS id 2F8B110E0B1 for ; Fri, 30 Dec 2022 12:51:19 +0000 (UTC) From: Jani Nikula To: Mark Yacoub , igt-dev@lists.freedesktop.org In-Reply-To: <20221229192714.40806-1-markyacoub@chromium.org> References: <20221229192714.40806-1-markyacoub@chromium.org> Date: Fri, 30 Dec 2022 14:51:13 +0200 Message-ID: <87zgb5yrzy.fsf@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Subject: Re: [igt-dev] [PATCH v2] [DO NOT REVIEW] testing headers in CI List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Mark Yacoub Errors-To: igt-dev-bounces@lists.freedesktop.org Sender: "igt-dev" List-ID: On Thu, 29 Dec 2022, Mark Yacoub wrote: > From: Mark Yacoub 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 =3D> 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 =3D> 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 >=20=20 > Support for the Chamelium platform in IGT is found in the following plac= es: > * 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 >=20=20 > 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" >=20=20 > #include > +#include >=20=20 > #include >=20=20 > 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" >=20=20 > #include > +#include >=20=20 > #include "igt_edid.h" >=20=20 > 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 mo= nitors > * and make them compatible with IGT and Chamelium. > diff --git a/tests/chamelium/kms_chamelium.c b/tests/chamelium/kms_chamel= ium.c > deleted file mode 100644 > index 3c4b4d75..00000000 > --- a/tests/chamelium/kms_chamelium.c > +++ /dev/null > @@ -1,3132 +0,0 @@ > -/* > - * Copyright =C2=A9 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 "Softwa= re"), > - * to deal in the Software without restriction, including without limita= tion > - * the rights to use, copy, modify, merge, publish, distribute, sublicen= se, > - * 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, EXPRE= SS OR > - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILI= TY, > - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SH= ALL > - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR = OTHER > - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISI= NG > - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER D= EALINGS > - * IN THE SOFTWARE. > - * > - * Authors: > - * Lyude Paul > - */ > - > -#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 > -#include > -#include > -#include > -// #include > - > -// 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 =3D 0; p < data->port_count; p++) { > - connector =3D 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] =3D link_status =3D=3D 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 =3D igt_hotplug_detected(mon, *timeout); > - igt_assert_eq(igt_gettime(&end), 0); > - > - elapsed =3D igt_time_elapsed(&start, &end); > - igt_assert_lte(0, elapsed); > - *timeout =3D 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 =3D CHAMELIUM_HOTPLUG_TIMEOUT; > - int hotplug_count =3D 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) =3D=3D 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->d= isplay, data->chamelium, port)), hotplug_count); > -} > - > - > -static int chamelium_vga_modes[][2] =3D { > - { 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 =3D 0; > - > - while (chamelium_vga_modes[i][0] !=3D -1) { > - if (mode->hdisplay =3D=3D chamelium_vga_modes[i][0] && > - mode->vdisplay =3D=3D 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 =3D NULL; > - drmModeConnector *connector =3D 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) !=3D 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 =3D drmModeGetPropertyBlob(data->drm_fd, > - edid_blob_id)); > - > - edid =3D (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] !=3D 'I' || edid_vendor[1] !=3D 'G' || > - edid_vendor[2] !=3D '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[] =3D { 0xff000000, > - 0xffff0000, > - 0xff00ff00, > - 0xff0000ff, > - 0xffffffff }; > - unsigned i, j; > - > - for (i =3D 0; i < height; i++) > - for (j =3D 0; j < width; j++) > - *(data + i * stride / 4 + j) =3D colors[((j / block_size) + (i / bloc= k_size)) % 5]; > -} > - > -static int chamelium_get_pattern_fb(data_t *data, size_t width, size_t h= eight, > - uint32_t fourcc, size_t block_size, > - struct igt_fb *fb) > -{ > - int fb_id; > - void *ptr; > - > - igt_assert(fourcc =3D=3D DRM_FORMAT_XRGB8888); > - > - fb_id =3D igt_create_fb(data->drm_fd, width, height, fourcc, > - DRM_FORMAT_MOD_LINEAR, fb); > - igt_assert(fb_id > 0); > - > - ptr =3D 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 =3D output->display; > - igt_plane_t *primary =3D igt_output_get_plane_type(output, DRM_PLANE_TY= PE_PRIMARY); > - drmModeConnector *connector =3D 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, NU= LL, 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) =3D=3D 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, drmModeM= odeInfo *mode) > -{ > - int fb_id; > - > - fb_id =3D 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 =3D chamelium_port_get_connector(chamelium, > - port, false); > - drmModeModeInfo mode; > - igt_assert(&connector->modes[0] !=3D 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 =3D > - chamelium_port_get_connector(data->chamelium, port, true); > - igt_output_t *output =3D igt_output_from_connector(&data->display, > - connector); > - drmModeFreeConnector(connector); > - igt_assert(output !=3D NULL); > - return output; > -} > - > -static const char test_hotplug_for_each_pipe_desc[] =3D > - "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 =3D 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 =3D 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[] =3D > - "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 =3D {0}; > - drmModeModeInfo mode; > - struct udev_monitor *mon =3D igt_watch_uevents(); > - igt_output_t *output =3D 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 =3D 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 =3D=3D TEST_MODESET_ON_OFF || > - (modeset_mode =3D=3D TEST_MODESET_ON && i =3D=3D 0 )) { > - if (i =3D=3D 0) { > - /* We can only get mode and pipe once we are connected */ > - output =3D get_output_for_port(data, port); > - pipe =3D get_pipe_for_output(&data->display, output); > - mode =3D 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 =3D=3D 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[] =3D > - "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, enu= m igt_custom_edid_type edid) > -{ > - drmModePropertyBlobPtr edid_blob =3D 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 =3D 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 !=3D 0); > - igt_assert(edid_blob =3D drmModeGetPropertyBlob(data->drm_fd, > - edid_blob_id)); > - > - raw_edid =3D chamelium_edid_get_raw(data->edids[edid], port); > - raw_edid_size =3D edid_get_size(raw_edid); > - igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) =3D=3D 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 =3D connected ? DRM_MODE_DISCONNECTED : > - DRM_MODE_CONNECTED; > - int timeout =3D CHAMELIUM_HOTPLUG_TIMEOUT; > - int delay; > - int p; > - > - igt_flush_uevents(mon); > - > - delay =3D igt_get_autoresume_delay(state) * 1000 / 2; > - > - if (port) { > - chamelium_schedule_hpd_toggle(data->chamelium, port, delay, > - !connected); > - } else { > - for (p =3D 0; p < data->port_count; p++) { > - port =3D data->ports[p]; > - chamelium_schedule_hpd_toggle(data->chamelium, port, > - delay, !connected); > - } > - > - port =3D 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 =3D 0; p < data->port_count; p++) { > - drmModeConnection current_state; > - > - port =3D 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 =3D chamelium_reprobe_connector(&data->display, data->c= hamelium, port); > - if (p > 0 && current_state !=3D target_state) { > - igt_assert(wait_for_hotplug(mon, &timeout)); > - current_state =3D chamelium_reprobe_connector(&data->display, data->= chamelium, port); > - } > - > - igt_assert_eq(current_state, target_state); > - } > - > - port =3D NULL; > - } > -} > - > -static const char test_suspend_resume_hpd_desc[] =3D > - "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 =3D 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[] =3D > - "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 stat= e, > - enum igt_suspend_test test) > -{ > - struct udev_monitor *mon =3D igt_watch_uevents(); > - struct chamelium_port *port; > - int p; > - > - for (p =3D 0; p < data->port_count; p++) { > - port =3D 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[] =3D > - "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 *por= t, > - 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 =3D 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 =3D 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_custo= m_edid_type edid) > -{ > - igt_display_t *display =3D &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 =3D get_output_for_port(data, port); > - > - /* Refresh pipe to update connected status */ > - igt_output_set_pipe(output, PIPE_NONE); > - > - pipe =3D 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 =3D chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay, > - DRM_FORMAT_XRGB8888, 64, &fb); > - igt_assert(fb_id > 0); > - > - frame_id =3D igt_fb_convert(&frame_fb, &fb, fourcc, > - DRM_FORMAT_MOD_LINEAR); > - igt_assert(frame_id > 0); > - > - if (check =3D=3D CHAMELIUM_CHECK_CRC) > - fb_crc =3D chamelium_calculate_fb_crc_async_start(data->drm_fd, > - &fb); > - > - enable_output(data, port, output, mode, &frame_fb); > - > - if (check =3D=3D 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 =3D chamelium_read_captured_crcs(data->chamelium, > - &captured_frame_count); > - > - igt_assert(captured_frame_count =3D=3D count); > - > - igt_debug("Captured %d frames\n", captured_frame_count); > - > - expected_crc =3D chamelium_calculate_fb_crc_async_finish(fb_crc); > - > - for (i =3D 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 =3D=3D CHAMELIUM_CHECK_ANALOG || > - check =3D=3D CHAMELIUM_CHECK_CHECKERBOARD) { > - struct chamelium_frame_dump *dump; > - > - igt_assert(count =3D=3D 1); > - > - dump =3D chamelium_port_dump_pixels(data->chamelium, port, 0, 0, > - 0, 0); > - > - if (check =3D=3D 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[] =3D > - "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 *p= ort, > - 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 =3D prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > - connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > - primary =3D 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_LI= NEAR)); > - > - mode =3D &connector->modes[0]; > - if (check =3D=3D CHAMELIUM_CHECK_ANALOG) { > - bool bridge =3D 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[] =3D > - "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 =3D=3D CHAMELIUM_CHECK_ANALOG) > - bridge =3D check_analog_bridge(data, port); > - > - i =3D 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 =3D prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > - connector =3D chamelium_port_get_connector(data->chamelium, port, > - false); > - primary =3D 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 =3D connector->count_modes; > - if (i >=3D count_modes) > - break; > - > - mode =3D &connector->modes[i]; > - > - if (check =3D=3D 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[] =3D > - "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 =3D 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 =3D prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > - connector =3D chamelium_port_get_connector(data->chamelium, port, > - false); > - primary =3D 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 =3D connector->count_modes; > - if (i >=3D count_modes) > - break; > - > - mode =3D &connector->modes[i]; > - > - fb_id =3D 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 =3D 0; j < 5; j++) { > - frame =3D 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_por= t *port, > - drmModeModeInfo *mode) > -{ > - struct chamelium_video_params video_params =3D {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 =3D (double) mode->clock / 1000; > - > - if (chamelium_port_get_type(port) =3D=3D DRM_MODE_CONNECTOR_DisplayPort= ) { > - /* this is what chamelium understands as offsets for DP */ > - mode_hsync_offset =3D mode->htotal - mode->hsync_start; > - mode_vsync_offset =3D mode->vtotal - mode->vsync_start; > - } else { > - /* and this is what they are for other connectors */ > - mode_hsync_offset =3D mode->hsync_start - mode->hdisplay; > - mode_vsync_offset =3D mode->vsync_start - mode->vdisplay; > - } > - > - mode_hsync_width =3D mode->hsync_end - mode->hsync_start; > - mode_vsync_width =3D mode->vsync_end - mode->vsync_start; > - > - mode_hsync_polarity =3D !!(mode->flags & DRM_MODE_FLAG_PHSYNC); > - mode_vsync_polarity =3D !!(mode->flags & DRM_MODE_FLAG_PVSYNC); > - > - igt_debug("Checking video mode:\n"); > - igt_debug("clock: got %f, expected %f =C2=B1 %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 =3D=3D mode->hdisplay); > - igt_assert(video_params.vactive =3D=3D mode->vdisplay); > - igt_assert(video_params.hsync_offset =3D=3D mode_hsync_offset); > - igt_assert(video_params.vsync_offset =3D=3D mode_vsync_offset); > - igt_assert(video_params.htotal =3D=3D mode->htotal); > - igt_assert(video_params.vtotal =3D=3D mode->vtotal); > - igt_assert(video_params.hsync_width =3D=3D mode_hsync_width); > - igt_assert(video_params.vsync_width =3D=3D mode_vsync_width); > - igt_assert(video_params.hsync_polarity =3D=3D mode_hsync_polarity); > - igt_assert(video_params.vsync_polarity =3D=3D mode_vsync_polarity); > -} > - > -static const char test_mode_timings_desc[] =3D > - "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 =3D 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 =3D prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > - connector =3D chamelium_port_get_connector(data->chamelium, port, fals= e); > - primary =3D 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 =3D connector->count_modes; > - if (i >=3D count_modes) > - break; > - > - mode =3D &connector->modes[i]; > - > - fb_id =3D 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[] =3D { > - [16] =3D { > - .hactive =3D 1920, > - .vactive =3D 1080, > - .vrefresh =3D 60, > - .picture_ar =3D DRM_MODE_PICTURE_ASPECT_16_9, > - }, > -}; > - > -/* Maps aspect ratios to their mode flag */ > -static const uint32_t mode_ar_flags[] =3D { > - [DRM_MODE_PICTURE_ASPECT_16_9] =3D 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 =3D mode_ar_flags[vic_mode->picture_ar]; > - > - return vic_mode->hactive =3D=3D drm_mode->hdisplay && > - vic_mode->vactive =3D=3D drm_mode->vdisplay && > - vic_mode->vrefresh =3D=3D drm_mode->vrefresh && > - ar_flag =3D=3D (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK); > -} > - > -static const char test_display_aspect_ratio_desc[] =3D > - "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_por= t *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 =3D 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 =3D prepare_output(data, port, IGT_CUSTOM_EDID_ASPECT_RATIO); > - connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > - primary =3D igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); > - igt_assert(primary); > - > - vic_mode =3D &vic_modes[vic]; > - aspect_ratio =3D vic_mode->picture_ar; > - > - found =3D false; > - igt_assert(connector->count_modes > 0); > - for (i =3D 0; i < connector->count_modes; i++) { > - mode =3D &connector->modes[i]; > - > - if (vic_mode_matches_drm(vic_mode, mode)) { > - found =3D true; > - break; > - } > - } > - igt_assert_f(found, > - "Failed to find mode with the correct aspect ratio\n"); > - > - fb_id =3D 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 =3D chamelium_get_last_infoframe(data->chamelium, port, > - CHAMELIUM_INFOFRAME_AVI); > - igt_assert_f(infoframe, "AVI InfoFrame not received\n"); > - > - ok =3D infoframe_avi_parse(&infoframe_avi, infoframe->version, > - infoframe->payload, infoframe->payload_size); > - igt_assert_f(ok, "Failed to parse AVI InfoFrame\n"); > - > - frame_ar =3D 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 =3D=3D frame_ar); > - igt_assert(infoframe_avi.vic =3D=3D 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 /* =C2=B1 0.1 % of the full am= plitude */ > -#define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */ > - > -/* TODO: enable >48KHz rates, these are not reliable */ > -static int test_sampling_rates[] =3D { > - 32000, > - 44100, > - 48000, > - /* 88200, */ > - /* 96000, */ > - /* 176400, */ > - /* 192000, */ > -}; > - > -static int test_sampling_rates_count =3D sizeof(test_sampling_rates) / s= izeof(int); > - > -/* Test frequencies (Hz): a sine signal will be generated for each. > - * > - * Depending on the sampling rate chosen, it might not be possible to pr= operly > - * detect the generated sine (see Nyquist=E2=80=93Shannon sampling theor= em). > - * Frequencies that can't be reliably detected will be automatically pru= ned in > - * #audio_signal_add_frequency. For instance, the 80KHz frequency can on= ly be > - * tested with a 192KHz sampling rate. > - */ > -static int test_frequencies[] =3D { > - 300, > - 600, > - 1200, > - 10000, > - 80000, > -}; > - > -static int test_frequencies_count =3D sizeof(test_frequencies) / sizeof(= int); > - > -static const snd_pcm_format_t test_formats[] =3D { > - SND_PCM_FORMAT_S16_LE, > - SND_PCM_FORMAT_S24_LE, > - SND_PCM_FORMAT_S32_LE, > -}; > - > -static const size_t test_formats_count =3D 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 =3D -1; > - > - state->alsa =3D alsa; > - state->chamelium =3D data->chamelium; > - state->port =3D port; > - > - state->playback.format =3D format; > - state->playback.channels =3D channels; > - state->playback.rate =3D rate; > - > - alsa_configure_output(alsa, format, channels, rate); > - > - state->stream =3D 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 =3D data; > - > - alsa_run(alsa, -1); > - return NULL; > -} > - > -static void audio_state_start(struct audio_state *state, const char *nam= e) > -{ > - int ret; > - bool ok; > - size_t i, j; > - enum chamelium_stream_realtime_mode stream_mode; > - char dump_suffix[64]; > - > - free(state->name); > - state->name =3D strdup(name); > - state->recv_pages =3D 0; > - state->msec =3D 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 =3D CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW; > - ok =3D 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 =3D true; > - ret =3D pthread_create(&state->thread, NULL, > - run_audio_thread, state->alsa); > - igt_assert_f(ret =3D=3D 0, "Failed to start audio playback thread\n"); > - > - /* The Chamelium device only supports this PCM format. */ > - state->capture.format =3D 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 =3D=3D 0) { > - igt_debug("Audio receiver doesn't indicate the capture " > - "sampling rate, assuming it's %d Hz\n", > - state->playback.rate); > - state->capture.rate =3D 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 =3D 0; i < state->playback.channels; i++) { > - ok =3D false; > - for (j =3D 0; j < state->capture.channels; j++) { > - if (state->channel_mapping[j] =3D=3D i) { > - ok =3D 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 =3D audio_create_wav_file_s32_le(dump_suffix, > - state->capture.rate, > - state->capture.channels, > - &state->dump_path); > - igt_assert_f(state->dump_fd >=3D 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 =3D 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 =3D state->recv_pages * *recv_len > - / (double) state->capture.channels > - / (double) state->capture.rate * 1000; > - state->recv_pages++; > - > - if (state->dump_fd >=3D 0) { > - recv_size =3D *recv_len * sizeof(int32_t); > - igt_assert_f(write(state->dump_fd, *recv, recv_size) =3D=3D 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 =3D false; > - ret =3D pthread_join(state->thread, NULL); > - igt_assert_f(ret =3D=3D 0, "Failed to join audio playback thread\n"); > - > - ok =3D chamelium_stream_stop_realtime_audio(state->stream); > - igt_assert_f(ok, "Failed to stop streaming audio capture\n"); > - > - audio_file =3D 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 >=3D 0) { > - close(state->dump_fd); > - state->dump_fd =3D -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 =3D NULL; > - } > - > - if (success) > - log_level =3D IGT_LOG_DEBUG; > - else > - log_level =3D 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 =3D {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 =3D INFOFRAME_AUDIO_CT_PCM; > - expected.channel_count =3D state->playback.channels; > - expected.sampling_freq =3D state->playback.rate; > - expected.sample_size =3D snd_pcm_format_width(state->playback.format); > - > - infoframe =3D chamelium_get_last_infoframe(state->chamelium, state->por= t, > - CHAMELIUM_INFOFRAME_AUDIO); > - if (infoframe =3D=3D NULL && state->playback.channels <=3D 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 !=3D NULL, "no audio InfoFrame received\n"); > - > - ok =3D 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 !=3D INFOFRAME_AUDIO_CT_UNSPECIFIED) > - igt_assert(infoframe_audio.coding_type =3D=3D expected.coding_type); > - if (infoframe_audio.channel_count >=3D 0) > - igt_assert(infoframe_audio.channel_count =3D=3D expected.channel_count= ); > - if (infoframe_audio.sampling_freq >=3D 0) > - igt_assert(infoframe_audio.sampling_freq =3D=3D expected.sampling_freq= ); > - if (infoframe_audio.sample_size >=3D 0) > - igt_assert(infoframe_audio.sample_size =3D=3D expected.sample_size); > -} > - > -static int > -audio_output_frequencies_callback(void *data, void *buffer, int samples) > -{ > - struct audio_state *state =3D data; > - double *tmp; > - size_t len; > - > - len =3D samples * state->playback.channels; > - tmp =3D 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 =3D 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 =3D=3D 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 =3D 2 * state->playback.rate / CAPTURE_SAMPLES; > - for (i =3D 0; i < test_frequencies_count; i++) { > - for (j =3D 0; j < state->playback.channels; j++) { > - freq =3D 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 =3D=3D 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 =3D CAPTURE_SAMPLES; > - channel =3D malloc(sizeof(double) * channel_len); > - > - buf_cap =3D state->capture.channels * channel_len; > - buf =3D malloc(sizeof(int32_t) * buf_cap); > - buf_len =3D 0; > - > - recv =3D NULL; > - recv_len =3D 0; > - > - success =3D false; > - streak =3D 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 +=3D recv_len; > - > - if (buf_len < buf_cap) > - continue; > - igt_assert(buf_len =3D=3D buf_cap); > - > - igt_debug("Detecting audio signal, t=3D%d msec\n", state->msec); > - > - for (j =3D 0; j < state->playback.channels; j++) { > - capture_chan =3D state->channel_mapping[j]; > - igt_assert(capture_chan >=3D 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 =3D 0; > - } > - > - buf_len =3D 0; > - > - success =3D streak =3D=3D 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 =3D data; > - double *tmp; > - size_t len, i; > - > - len =3D samples * state->playback.channels; > - tmp =3D malloc(len * sizeof(double)); > - for (i =3D 0; i < len; i++) > - tmp[i] =3D (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 =3D max =3D NAN; > - for (i =3D 0; i < buf_len; i++) { > - if (isnan(min) || buf[i] < min) > - min =3D buf[i]; > - if (isnan(max) || buf[i] > max) > - max =3D buf[i]; > - } > - > - expected =3D (pos ? 1 : -1) * FLATLINE_AMPLITUDE; > - ok =3D (min >=3D expected - FLATLINE_AMPLITUDE_ACCURACY && > - max <=3D expected + FLATLINE_AMPLITUDE_ACCURACY); > - if (ok) > - igt_debug("Flatline wave amplitude detected\n"); > - else > - igt_debug("Flatline amplitude not detected (min=3D%f, max=3D%f)\n", > - min, max); > - return ok; > -} > - > -static ssize_t detect_falling_edge(double *buf, size_t buf_len) > -{ > - size_t i; > - > - for (i =3D 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 t= hat: > - * > - * - 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 =3D true; > - > - audio_state_start(state, "flatline"); > - > - for (i =3D 0; i < state->playback.channels; i++) > - falling_edges[i] =3D -1; > - > - recv =3D NULL; > - recv_len =3D 0; > - amp_success =3D false; > - streak =3D 0; > - while (!amp_success && state->msec < AUDIO_TIMEOUT) { > - audio_state_receive(state, &recv, &recv_len); > - > - igt_debug("Detecting audio signal, t=3D%d msec\n", state->msec); > - > - for (i =3D 0; i < state->playback.channels; i++) { > - capture_chan =3D state->channel_mapping[i]; > - igt_assert(capture_chan >=3D 0); > - igt_debug("Processing channel %zu (captured as " > - "channel %d)\n", i, capture_chan); > - > - channel_len =3D audio_extract_channel_s32_le(NULL, 0, > - recv, recv_len, > - state->capture.channels, > - capture_chan); > - channel =3D 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 =3D 0; > - > - /* If we're now sending a negative signal, detect the > - * falling edge */ > - j =3D detect_falling_edge(channel, channel_len); > - if (!state->positive && j >=3D 0) { > - falling_edges[i] =3D recv_len * state->recv_pages > - + j; > - } > - > - free(channel); > - } > - > - amp_success =3D streak =3D=3D MIN_STREAK * state->playback.channels; > - > - if (amp_success && state->positive) { > - /* Switch to a negative signal after we've detected the > - * positive one. */ > - state->positive =3D false; > - amp_success =3D false; > - streak =3D 0; > - igt_debug("Switching to negative square wave\n"); > - } > - } > - > - /* Check alignment between all channels by comparing the index of the > - * falling edge. */ > - align_success =3D true; > - for (i =3D 0; i < state->playback.channels; i++) { > - if (falling_edges[i] < 0) { > - igt_critical("Falling edge not detected for channel %zu\n", > - i); > - align_success =3D 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 =3D false; > - } > - } > - > - success =3D 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 !=3D SND_PCM_FORMAT_S16_LE && sampling_rate >=3D 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[] =3D > - "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 =3D 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 =3D prepare_output(data, port, edid); > - connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > - primary =3D 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 =3D &connector->modes[0]; > - > - fb_id =3D 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 =3D false; > - success =3D true; > - for (i =3D 0; i < test_sampling_rates_count; i++) { > - for (j =3D 0; j < test_formats_count; j++) { > - ret =3D alsa_open_output(alsa, audio_device); > - igt_assert_f(ret >=3D 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 =3D test_formats[j]; > - channels =3D PLAYBACK_CHANNELS; > - sampling_rate =3D test_sampling_rates[i]; > - > - if (!check_audio_configuration(alsa, format, channels, > - sampling_rate)) > - continue; > - > - run =3D true; > - > - audio_state_init(&state, data, alsa, port, > - format, channels, sampling_rate); > - success &=3D test_audio_frequencies(&state); > - success &=3D 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[] =3D > - "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 =3D prepare_output(data, port, edid); > - connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > - primary =3D 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 =3D &connector->modes[0]; > - > - fb_id =3D 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 =3D=3D 1); > - > - sad =3D &eld.sads[0]; > - igt_assert(sad->coding_type =3D=3D CEA_SAD_FORMAT_PCM); > - igt_assert(sad->channels =3D=3D 2); > - igt_assert(sad->rates =3D=3D (CEA_SAD_SAMPLING_RATE_32KHZ | > - CEA_SAD_SAMPLING_RATE_44KHZ | CEA_SAD_SAMPLING_RATE_48KHZ)); > - igt_assert(sad->bits =3D=3D (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 =3D 4, tile_w, tile_h; > - int i; > - struct igt_fb dummy; > - > - stride_min =3D width * igt_format_plane_bpp(format, 0) / 8; > - > - /* Randomize the stride to less than twice the minimum. */ > - *stride =3D (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 =3D 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 =3D 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 =3D 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 =3D=3D DRM_FORMAT_MOD_BROADCOM_SAND256) { > - /* Randomize the column height to less than twice the minimum. */ > - size_t column_height =3D (rand() % height) + height; > - > - igt_debug("Selecting VC4 SAND256 tiling with column height %ld\n", > - column_height); > - > - *modifier =3D 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 =3D 0; > - unsigned int i; > - > - /* First pass to count the supported formats. */ > - for (i =3D 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++] =3D i; > - > - igt_assert(count > 0); > - > - i =3D idx[rand() % count]; > - *format =3D plane->formats[i]; > - *modifier =3D 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 =3D width - (rand % w / 2). > - * > - * Use a minimum dimension of 16 for YUV, because planar YUV > - * subsamples the UV plane. > - */ > - min_dim =3D igt_format_is_yuv(*format) ? 16 : 8; > - > - *width =3D max((rand() % mode->hdisplay) + 1, 2 * min_dim); > - *height =3D 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 =3D igt_format_is_yuv(fb->drm_format); > - uint32_t width =3D fb->width, height =3D fb->height; > - double ratio; > - int ret; > - > - /* Randomize source offset in the first half of the original size. */ > - *src_x =3D rand() % (width / 2); > - *src_y =3D rand() % (height / 2); > - > - /* The source size only includes the active source area. */ > - *src_w =3D width - *src_x; > - *src_h =3D height - *src_y; > - > - if (allow_scaling) { > - *crtc_w =3D (rand() % mode->hdisplay) + 1; > - *crtc_h =3D (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 =3D ((double) *crtc_w / *src_w); > - if (ratio < 0.5) > - *src_w =3D *crtc_w * 2; > - else if (ratio > 0.8 && ratio < 1.2) > - *crtc_w =3D *src_w; > - else if (ratio > 3.0) > - *crtc_w =3D *src_w * 3; > - > - ratio =3D ((double) *crtc_h / *src_h); > - if (ratio < 0.5) > - *src_h =3D *crtc_h * 2; > - else if (ratio > 0.8 && ratio < 1.2) > - *crtc_h =3D *src_h; > - else if (ratio > 3.0) > - *crtc_h =3D *src_h * 3; > - } else { > - *crtc_w =3D *src_w; > - *crtc_h =3D *src_h; > - } > - > - if (*crtc_w !=3D *src_w || *crtc_h !=3D *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 =3D rand() % (mode->hdisplay - *crtc_w); > - else > - *crtc_x =3D 0; > - > - if (*crtc_h < mode->vdisplay) > - *crtc_y =3D rand() % (mode->vdisplay - *crtc_h); > - else > - *crtc_y =3D 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 =3D (rand() % mode->hdisplay) - *crtc_w / 2; > - *crtc_y =3D (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 =3D 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 o= n failure here. */ > - igt_assert_f(ret !=3D -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 &=3D ~1; > - *src_y &=3D ~1; > - *src_w &=3D ~1; > - *src_h &=3D ~1; > - /* To handle 1:1 scaling, clear crtc_w/h too. */ > - *crtc_w &=3D ~1; > - *crtc_h &=3D ~1; > - > - if (*crtc_x < 0 && (*crtc_x & 1)) > - (*crtc_x)++; > - else > - *crtc_x &=3D ~1; > - > - /* If negative, round up to 0 instead of down */ > - if (*crtc_y < 0 && (*crtc_y & 1)) > - (*crtc_y)++; > - else > - *crtc_y &=3D ~1; > - > - configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, > - *crtc_h, *crtc_x, *crtc_y, fb); > - ret =3D 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 =3D *src_w; > - *crtc_h =3D *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 =3D igt_get_cairo_surface(data->drm_fd, fb); > - > - if (src_x || src_y) { > - clipped_surface =3D cairo_image_surface_create(CAIRO_FORMAT_RGB24, > - src_w, src_h); > - > - cr =3D 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 =3D surface; > - } > - > - cr =3D cairo_create(result); > - > - cairo_translate(cr, crtc_x, crtc_y); > - > - if (src_w !=3D crtc_w || src_h !=3D 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 !=3D crtc_w || src_h !=3D 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 =3D (modifier !=3D 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 =3D 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 =3D 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[] =3D > - "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 =3D 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 =3D false; > - allow_yuv =3D false; > - break; > - case CHAMELIUM_CHECK_CHECKERBOARD: > - allow_scaling =3D true; > - allow_yuv =3D 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 =3D prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > - > - mode =3D igt_output_get_mode(output); > - > - /* Get a framebuffer for the primary plane. */ > - primary_plane =3D igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIM= ARY); > - igt_assert(primary_plane); > - > - fb_id =3D 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 =3D 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 =3D 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 =3D > - igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY); > - > - /* Limit the number of planes to a reasonable scene. */ > - overlay_planes_max =3D min(overlay_planes_max, 4u); > - > - overlay_planes_count =3D (rand() % overlay_planes_max) + 1; > - igt_debug("Using %d overlay planes\n", overlay_planes_count); > - > - overlay_fbs =3D calloc(sizeof(struct igt_fb), overlay_planes_count); > - > - for (i =3D 0; i < overlay_planes_count; i++) { > - struct igt_fb *overlay_fb =3D &overlay_fbs[i]; > - igt_plane_t *plane =3D > - 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 =3D=3D CHAMELIUM_CHECK_CRC) > - fb_crc =3D chamelium_calculate_fb_crc_async_start(data->drm_fd, > - &result_fb); > - > - igt_display_commit2(&data->display, COMMIT_ATOMIC); > - > - if (check =3D=3D CHAMELIUM_CHECK_CRC) { > - chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1); > - crc =3D chamelium_read_captured_crcs(data->chamelium, > - &captured_frame_count); > - > - igt_assert(captured_frame_count =3D=3D 1); > - > - expected_crc =3D 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 =3D=3D CHAMELIUM_CHECK_CHECKERBOARD) { > - struct chamelium_frame_dump *dump; > - > - dump =3D 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 =3D 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[] =3D > - "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 =3D 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[] =3D > - "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 wid= th) > -{ > - struct udev_monitor *mon; > - int count =3D 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 =3D 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 +=3D 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[] =3D > - "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 wi= dth) > -{ > - 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[] =3D > - "Stress test the DUT by testing multiple EDIDs, one right after the oth= er," > - "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 =3D data->chamelium; > - struct udev_monitor *mon =3D igt_watch_uevents(); > - > - for (i =3D 0; i < edids_list_len; ++i) { > - struct chamelium_edid *chamelium_edid; > - drmModeModeInfo mode; > - struct igt_fb fb =3D { 0 }; > - igt_output_t *output; > - enum pipe pipe; > - bool is_video_stable; > - int screen_res_w, screen_res_h; > - > - monitor_edid *edid =3D &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 =3D > - 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 =3D get_mode_for_port(chamelium, port); > - create_fb_for_mode(data, &fb, &mode); > - output =3D get_output_for_port(data, port); > - pipe =3D 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 =3D 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 =3D=3D fb.width); > - igt_assert(screen_res_h =3D=3D 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[] =3D > - "Get an EDID with many modes of different configurations, set them on t= he screen and check the" > - " screen resolution matches the mode resolution."; > - > -static void edid_resolution_list(data_t *data, struct chamelium_port *po= rt) > -{ > - struct chamelium *chamelium =3D data->chamelium; > - struct udev_monitor *mon =3D 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 =3D chamelium_port_get_connector(chamelium, port, true); > - modes =3D connector->modes; > - count_modes =3D connector->count_modes; > - > - output =3D get_output_for_port(data, port); > - pipe =3D get_pipe_for_output(&data->display, output); > - igt_output_set_pipe(output, pipe); > - > - for (i =3D 0; i < count_modes; ++i) > - igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh); > - > - for (i =3D 0; i < count_modes; ++i) { > - struct igt_fb fb =3D { 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 =3D 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 =3D 0, port =3D data.ports[p]; \ > - p < data.port_count; \ > - p++, port =3D data.ports[p]) > - > -#define connector_subtest(name__, type__) \ > - igt_subtest(name__) \ > - for_each_port(p, port) \ > - if (chamelium_port_get_type(port) =3D=3D \ > - 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) =3D=3D \ > - 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 =3D 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 =3D chamelium_init(data.drm_fd, &data.display); > - igt_require(data.chamelium); > - > - data.ports =3D chamelium_get_ports(data.chamelium, > - &data.port_count); > - > - for (i =3D 0; i < IGT_CUSTOM_EDID_COUNT; ++i) { > - data.edids[i] =3D 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_Di= splayPort, > - 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_HD= MIA, > - 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 =3D prepare_output(&data, port, IGT_CUSTOM_EDID_BASE); > - primary =3D igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); > - igt_assert(primary); > - > - for (k =3D 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] !=3D 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 =3D prepare_output(&data, port, IGT_CUSTOM_EDID_BASE); > - primary =3D igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); > - igt_assert(primary); > - > - for (k =3D 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] !=3D 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_VG= A, > - 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 > + */ > + > +#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 /* =C2=B1 0.1 % of the full am= plitude */ > +#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[] =3D { > + 32000, 44100, 48000, > + /* 88200, */ > + /* 96000, */ > + /* 176400, */ > + /* 192000, */ > +}; > + > +static int test_sampling_rates_count =3D > + 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 pr= operly > + * detect the generated sine (see Nyquist=E2=80=93Shannon sampling theor= em). > + * Frequencies that can't be reliably detected will be automatically pru= ned in > + * #audio_signal_add_frequency. For instance, the 80KHz frequency can on= ly be > + * tested with a 192KHz sampling rate. > + */ > +static int test_frequencies[] =3D { > + 300, 600, 1200, 10000, 80000, > +}; > + > +static int test_frequencies_count =3D sizeof(test_frequencies) / sizeof(= int); > + > +static const snd_pcm_format_t test_formats[] =3D { > + SND_PCM_FORMAT_S16_LE, > + SND_PCM_FORMAT_S24_LE, > + SND_PCM_FORMAT_S32_LE, > +}; > + > +static const size_t test_formats_count =3D > + 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 =3D -1; > + > + state->alsa =3D alsa; > + state->chamelium =3D data->chamelium; > + state->port =3D port; > + > + state->playback.format =3D format; > + state->playback.channels =3D channels; > + state->playback.rate =3D rate; > + > + alsa_configure_output(alsa, format, channels, rate); > + > + state->stream =3D 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 =3D data; > + > + alsa_run(alsa, -1); > + return NULL; > +} > + > +static void audio_state_start(struct audio_state *state, const char *nam= e) > +{ > + int ret; > + bool ok; > + size_t i, j; > + enum chamelium_stream_realtime_mode stream_mode; > + char dump_suffix[64]; > + > + free(state->name); > + state->name =3D strdup(name); > + state->recv_pages =3D 0; > + state->msec =3D 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 =3D CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW; > + ok =3D 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 =3D true; > + ret =3D pthread_create(&state->thread, NULL, run_audio_thread, > + state->alsa); > + igt_assert_f(ret =3D=3D 0, "Failed to start audio playback thread\n"); > + > + /* The Chamelium device only supports this PCM format. */ > + state->capture.format =3D 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 =3D=3D 0) { > + igt_debug("Audio receiver doesn't indicate the capture " > + "sampling rate, assuming it's %d Hz\n", > + state->playback.rate); > + state->capture.rate =3D 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 =3D 0; i < state->playback.channels; i++) { > + ok =3D false; > + for (j =3D 0; j < state->capture.channels; j++) { > + if (state->channel_mapping[j] =3D=3D i) { > + ok =3D 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 =3D audio_create_wav_file_s32_le( > + dump_suffix, state->capture.rate, > + state->capture.channels, &state->dump_path); > + igt_assert_f(state->dump_fd >=3D 0, > + "Failed to create audio dump file\n"); > + } > +} > + > +static void audio_state_receive(struct audio_state *state, int32_t **rec= v, > + size_t *recv_len) > +{ > + bool ok; > + size_t page_count; > + size_t recv_size; > + > + ok =3D chamelium_stream_receive_realtime_audio(state->stream, &page_cou= nt, > + recv, recv_len); > + igt_assert_f(ok, "Failed to receive audio from stream server\n"); > + > + state->msec =3D state->recv_pages * *recv_len / > + (double)state->capture.channels / > + (double)state->capture.rate * 1000; > + state->recv_pages++; > + > + if (state->dump_fd >=3D 0) { > + recv_size =3D *recv_len * sizeof(int32_t); > + igt_assert_f(write(state->dump_fd, *recv, recv_size) =3D=3D > + 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 =3D false; > + ret =3D pthread_join(state->thread, NULL); > + igt_assert_f(ret =3D=3D 0, "Failed to join audio playback thread\n"); > + > + ok =3D chamelium_stream_stop_realtime_audio(state->stream); > + igt_assert_f(ok, "Failed to stop streaming audio capture\n"); > + > + audio_file =3D > + 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 >=3D 0) { > + close(state->dump_fd); > + state->dump_fd =3D -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 =3D NULL; > + } > + > + if (success) > + log_level =3D IGT_LOG_DEBUG; > + else > + log_level =3D 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 =3D { 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 =3D INFOFRAME_AUDIO_CT_PCM; > + expected.channel_count =3D state->playback.channels; > + expected.sampling_freq =3D state->playback.rate; > + expected.sample_size =3D snd_pcm_format_width(state->playback.format); > + > + infoframe =3D chamelium_get_last_infoframe(state->chamelium, state->por= t, > + CHAMELIUM_INFOFRAME_AUDIO); > + if (infoframe =3D=3D NULL && state->playback.channels <=3D 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 !=3D NULL, "no audio InfoFrame received\n"); > + > + ok =3D 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 !=3D INFOFRAME_AUDIO_CT_UNSPECIFIED) > + igt_assert(infoframe_audio.coding_type =3D=3D expected.coding_type); > + if (infoframe_audio.channel_count >=3D 0) > + igt_assert(infoframe_audio.channel_count =3D=3D > + expected.channel_count); > + if (infoframe_audio.sampling_freq >=3D 0) > + igt_assert(infoframe_audio.sampling_freq =3D=3D > + expected.sampling_freq); > + if (infoframe_audio.sample_size >=3D 0) > + igt_assert(infoframe_audio.sample_size =3D=3D expected.sample_size); > +} > + > +static int audio_output_frequencies_callback(void *data, void *buffer, > + int samples) > +{ > + struct audio_state *state =3D data; > + double *tmp; > + size_t len; > + > + len =3D samples * state->playback.channels; > + tmp =3D 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 =3D 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 =3D=3D 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 =3D 2 * state->playback.rate / CAPTURE_SAMPLES; > + for (i =3D 0; i < test_frequencies_count; i++) { > + for (j =3D 0; j < state->playback.channels; j++) { > + freq =3D 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 =3D=3D 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 =3D CAPTURE_SAMPLES; > + channel =3D malloc(sizeof(double) * channel_len); > + > + buf_cap =3D state->capture.channels * channel_len; > + buf =3D malloc(sizeof(int32_t) * buf_cap); > + buf_len =3D 0; > + > + recv =3D NULL; > + recv_len =3D 0; > + > + success =3D false; > + streak =3D 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 +=3D recv_len; > + > + if (buf_len < buf_cap) > + continue; > + igt_assert(buf_len =3D=3D buf_cap); > + > + igt_debug("Detecting audio signal, t=3D%d msec\n", state->msec); > + > + for (j =3D 0; j < state->playback.channels; j++) { > + capture_chan =3D state->channel_mapping[j]; > + igt_assert(capture_chan >=3D 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 =3D 0; > + } > + > + buf_len =3D 0; > + > + success =3D streak =3D=3D 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 =3D data; > + double *tmp; > + size_t len, i; > + > + len =3D samples * state->playback.channels; > + tmp =3D malloc(len * sizeof(double)); > + for (i =3D 0; i < len; i++) > + tmp[i] =3D (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 =3D max =3D NAN; > + for (i =3D 0; i < buf_len; i++) { > + if (isnan(min) || buf[i] < min) > + min =3D buf[i]; > + if (isnan(max) || buf[i] > max) > + max =3D buf[i]; > + } > + > + expected =3D (pos ? 1 : -1) * FLATLINE_AMPLITUDE; > + ok =3D (min >=3D expected - FLATLINE_AMPLITUDE_ACCURACY && > + max <=3D expected + FLATLINE_AMPLITUDE_ACCURACY); > + if (ok) > + igt_debug("Flatline wave amplitude detected\n"); > + else > + igt_debug("Flatline amplitude not detected (min=3D%f, max=3D%f)\n", > + min, max); > + return ok; > +} > + > +static ssize_t detect_falling_edge(double *buf, size_t buf_len) > +{ > + size_t i; > + > + for (i =3D 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 t= hat: > + * > + * - 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 =3D true; > + > + audio_state_start(state, "flatline"); > + > + for (i =3D 0; i < state->playback.channels; i++) > + falling_edges[i] =3D -1; > + > + recv =3D NULL; > + recv_len =3D 0; > + amp_success =3D false; > + streak =3D 0; > + while (!amp_success && state->msec < AUDIO_TIMEOUT) { > + audio_state_receive(state, &recv, &recv_len); > + > + igt_debug("Detecting audio signal, t=3D%d msec\n", state->msec); > + > + for (i =3D 0; i < state->playback.channels; i++) { > + capture_chan =3D state->channel_mapping[i]; > + igt_assert(capture_chan >=3D 0); > + igt_debug("Processing channel %zu (captured as " > + "channel %d)\n", > + i, capture_chan); > + > + channel_len =3D audio_extract_channel_s32_le( > + NULL, 0, recv, recv_len, > + state->capture.channels, capture_chan); > + channel =3D 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 =3D 0; > + > + /* If we're now sending a negative signal, detect the > + * falling edge */ > + j =3D detect_falling_edge(channel, channel_len); > + if (!state->positive && j >=3D 0) { > + falling_edges[i] =3D > + recv_len * state->recv_pages + j; > + } > + > + free(channel); > + } > + > + amp_success =3D streak =3D=3D MIN_STREAK * state->playback.channels; > + > + if (amp_success && state->positive) { > + /* Switch to a negative signal after we've detected the > + * positive one. */ > + state->positive =3D false; > + amp_success =3D false; > + streak =3D 0; > + igt_debug("Switching to negative square wave\n"); > + } > + } > + > + /* Check alignment between all channels by comparing the index of the > + * falling edge. */ > + align_success =3D true; > + for (i =3D 0; i < state->playback.channels; i++) { > + if (falling_edges[i] < 0) { > + igt_critical( > + "Falling edge not detected for channel %zu\n", > + i); > + align_success =3D 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 =3D false; > + } > + } > + > + success =3D 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 !=3D SND_PCM_FORMAT_S16_LE && sampling_rate >=3D 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[] =3D > + "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 =3D 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 =3D chamelium_prepare_output(data, port, edid); > + connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > + primary =3D 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 =3D &connector->modes[0]; > + > + fb_id =3D 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 =3D false; > + success =3D true; > + for (i =3D 0; i < test_sampling_rates_count; i++) { > + for (j =3D 0; j < test_formats_count; j++) { > + ret =3D alsa_open_output(alsa, audio_device); > + igt_assert_f(ret >=3D 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 =3D test_formats[j]; > + channels =3D PLAYBACK_CHANNELS; > + sampling_rate =3D test_sampling_rates[i]; > + > + if (!check_audio_configuration(alsa, format, channels, > + sampling_rate)) > + continue; > + > + run =3D true; > + > + audio_state_init(&state, data, alsa, port, format, > + channels, sampling_rate); > + success &=3D test_audio_frequencies(&state); > + success &=3D 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[] =3D > + "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 =3D chamelium_prepare_output(data, port, edid); > + connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > + primary =3D 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 =3D &connector->modes[0]; > + > + fb_id =3D 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 =3D=3D 1); > + > + sad =3D &eld.sads[0]; > + igt_assert(sad->coding_type =3D=3D CEA_SAD_FORMAT_PCM); > + igt_assert(sad->channels =3D=3D 2); > + igt_assert(sad->rates =3D=3D > + (CEA_SAD_SAMPLING_RATE_32KHZ | CEA_SAD_SAMPLING_RATE_44KHZ | > + CEA_SAD_SAMPLING_RATE_48KHZ)); > + igt_assert(sad->bits =3D=3D > + (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_c= hamelium_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 > + */ > + > +#include > +#include > +#include > +#include > +#include > + > +#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 =3D 0; p < data->port_count; p++) { > + connector =3D 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] =3D link_status =3D=3D DRM_MODE_LINK_STATUS_BAD; > + > + drmModeFreeProperty(prop); > + drmModeFreeConnector(connector); > + } > +} > + > +static void check_mode(struct chamelium *chamelium, struct chamelium_por= t *port, > + drmModeModeInfo *mode) > +{ > + struct chamelium_video_params video_params =3D { 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 =3D (double)mode->clock / 1000; > + > + if (chamelium_port_get_type(port) =3D=3D DRM_MODE_CONNECTOR_DisplayPort= ) { > + /* this is what chamelium understands as offsets for DP */ > + mode_hsync_offset =3D mode->htotal - mode->hsync_start; > + mode_vsync_offset =3D mode->vtotal - mode->vsync_start; > + } else { > + /* and this is what they are for other connectors */ > + mode_hsync_offset =3D mode->hsync_start - mode->hdisplay; > + mode_vsync_offset =3D mode->vsync_start - mode->vdisplay; > + } > + > + mode_hsync_width =3D mode->hsync_end - mode->hsync_start; > + mode_vsync_width =3D mode->vsync_end - mode->vsync_start; > + > + mode_hsync_polarity =3D !!(mode->flags & DRM_MODE_FLAG_PHSYNC); > + mode_vsync_polarity =3D !!(mode->flags & DRM_MODE_FLAG_PVSYNC); > + > + igt_debug("Checking video mode:\n"); > + igt_debug("clock: got %f, expected %f =C2=B1 %f%%\n", video_params.cloc= k, > + 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 =3D=3D mode->hdisplay); > + igt_assert(video_params.vactive =3D=3D mode->vdisplay); > + igt_assert(video_params.hsync_offset =3D=3D mode_hsync_offset); > + igt_assert(video_params.vsync_offset =3D=3D mode_vsync_offset); > + igt_assert(video_params.htotal =3D=3D mode->htotal); > + igt_assert(video_params.vtotal =3D=3D mode->vtotal); > + igt_assert(video_params.hsync_width =3D=3D mode_hsync_width); > + igt_assert(video_params.vsync_width =3D=3D mode_vsync_width); > + igt_assert(video_params.hsync_polarity =3D=3D mode_hsync_polarity); > + igt_assert(video_params.vsync_polarity =3D=3D mode_vsync_polarity); > +} > + > +static const char igt_custom_edid_type_read_desc[] =3D > + "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 =3D 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 =3D 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 !=3D 0); > + edid_blob =3D drmModeGetPropertyBlob(data->drm_fd, edid_blob_id); > + igt_assert(edid_blob); > + > + raw_edid =3D chamelium_edid_get_raw(data->edids[edid], port); > + raw_edid_size =3D edid_get_size(raw_edid); > + igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) =3D=3D 0); > + > + drmModeFreePropertyBlob(edid_blob); > + drmModeFreeConnector(connector); > +} > + > +static const char igt_edid_stress_resolution_desc[] =3D > + "Stress test the DUT by testing multiple EDIDs, one right after the oth= er," > + "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 =3D data->chamelium; > + struct udev_monitor *mon =3D igt_watch_uevents(); > + > + for (i =3D 0; i < edids_list_len; ++i) { > + struct chamelium_edid *chamelium_edid; > + drmModeModeInfo mode; > + struct igt_fb fb =3D { 0 }; > + igt_output_t *output; > + enum pipe pipe; > + bool is_video_stable; > + int screen_res_w, screen_res_h; > + > + monitor_edid *edid =3D &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 =3D > + 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 =3D chamelium_get_mode_for_port(chamelium, port); > + chamelium_create_fb_for_mode(data, &fb, &mode); > + output =3D chamelium_get_output_for_port(data, port); > + pipe =3D 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 =3D 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 =3D=3D fb.width); > + igt_assert(screen_res_h =3D=3D 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[] =3D > + "Get an EDID with many modes of different configurations, set them on t= he 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 =3D data->chamelium; > + struct udev_monitor *mon =3D 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 =3D chamelium_port_get_connector(chamelium, port, true); > + modes =3D connector->modes; > + count_modes =3D connector->count_modes; > + > + output =3D chamelium_get_output_for_port(data, port); > + pipe =3D chamelium_get_pipe_for_output(&data->display, output); > + igt_output_set_pipe(output, pipe); > + > + for (i =3D 0; i < count_modes; ++i) > + igt_debug("#%d %s %uHz\n", i, modes[i].name, modes[i].vrefresh); > + > + for (i =3D 0; i < count_modes; ++i) { > + struct igt_fb fb =3D { 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 =3D 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[] =3D > + "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 =3D 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 =3D 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[] =3D > + "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 =3D 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 =3D chamelium_prepare_output(data, port, > + IGT_CUSTOM_EDID_BASE); > + connector =3D chamelium_port_get_connector(data->chamelium, port, > + false); > + primary =3D 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 =3D connector->count_modes; > + if (i >=3D count_modes) > + break; > + > + mode =3D &connector->modes[i]; > + > + fb_id =3D 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 =C2=A9 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 "Softwa= re"), > + * to deal in the Software without restriction, including without limita= tion > + * the rights to use, copy, modify, merge, publish, distribute, sublicen= se, > + * 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, EXPRE= SS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILI= TY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SH= ALL > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR = OTHER > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISI= NG > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER D= EALINGS > + * IN THE SOFTWARE. > + * > + * Authors: > + * Lyude Paul > + */ > + > +#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) =3D=3D \ > + DRM_MODE_CONNECTOR_##type__) > + > +struct vic_mode { > + int hactive, vactive; > + int vrefresh; /* Hz */ > + uint32_t picture_ar; > +}; > + > +static int chamelium_vga_modes[][2] =3D { > + { 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[] =3D { > + [16] =3D { > + .hactive =3D 1920, > + .vactive =3D 1080, > + .vrefresh =3D 60, > + .picture_ar =3D DRM_MODE_PICTURE_ASPECT_16_9, > + }, > +}; > + > +/* Maps aspect ratios to their mode flag */ > +static const uint32_t mode_ar_flags[] =3D { > + [DRM_MODE_PICTURE_ASPECT_16_9] =3D DRM_MODE_FLAG_PIC_AR_16_9, > +}; > + > +static bool prune_vga_mode(chamelium_data_t *data, drmModeModeInfo *mode) > +{ > + int i =3D 0; > + > + while (chamelium_vga_modes[i][0] !=3D -1) { > + if (mode->hdisplay =3D=3D chamelium_vga_modes[i][0] && > + mode->vdisplay =3D=3D chamelium_vga_modes[i][1]) > + return false; > + > + i++; > + } > + > + return true; > +} > + > +static void do_test_display(chamelium_data_t *data, struct chamelium_por= t *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 =3D chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay, > + DRM_FORMAT_XRGB8888, 64, &fb); > + igt_assert(fb_id > 0); > + > + frame_id =3D > + igt_fb_convert(&frame_fb, &fb, fourcc, DRM_FORMAT_MOD_LINEAR); > + igt_assert(frame_id > 0); > + > + if (check =3D=3D CHAMELIUM_CHECK_CRC) > + fb_crc =3D chamelium_calculate_fb_crc_async_start(data->drm_fd, > + &fb); > + > + chamelium_enable_output(data, port, output, mode, &frame_fb); > + > + if (check =3D=3D 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 =3D chamelium_read_captured_crcs(data->chamelium, > + &captured_frame_count); > + > + igt_assert(captured_frame_count =3D=3D count); > + > + igt_debug("Captured %d frames\n", captured_frame_count); > + > + expected_crc =3D chamelium_calculate_fb_crc_async_finish(fb_crc); > + > + for (i =3D 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 =3D=3D CHAMELIUM_CHECK_ANALOG || > + check =3D=3D CHAMELIUM_CHECK_CHECKERBOARD) { > + struct chamelium_frame_dump *dump; > + > + igt_assert(count =3D=3D 1); > + > + dump =3D chamelium_port_dump_pixels(data->chamelium, port, 0, 0, > + 0, 0); > + > + if (check =3D=3D 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 =3D mode_ar_flags[vic_mode->picture_ar]; > + > + return vic_mode->hactive =3D=3D drm_mode->hdisplay && > + vic_mode->vactive =3D=3D drm_mode->vdisplay && > + vic_mode->vrefresh =3D=3D drm_mode->vrefresh && > + ar_flag =3D=3D (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK); > +} > + > +static void randomize_plane_stride(chamelium_data_t *data, uint32_t widt= h, > + uint32_t height, uint32_t format, > + uint64_t modifier, size_t *stride) > +{ > + size_t stride_min; > + uint32_t max_tile_w =3D 4, tile_w, tile_h; > + int i; > + struct igt_fb dummy; > + > + stride_min =3D width * igt_format_plane_bpp(format, 0) / 8; > + > + /* Randomize the stride to less than twice the minimum. */ > + *stride =3D (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 =3D 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 =3D 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 =3D 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 =3D=3D DRM_FORMAT_MOD_BROADCOM_SAND256) { > + /* Randomize the column height to less than twice the minimum. > + */ > + size_t column_height =3D (rand() % height) + height; > + > + igt_debug( > + "Selecting VC4 SAND256 tiling with column height %ld\n", > + column_height); > + > + *modifier =3D DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT( > + column_height); > + } > +} > + > +static void randomize_plane_setup(chamelium_data_t *data, igt_plane_t *p= lane, > + 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 =3D 0; > + unsigned int i; > + > + /* First pass to count the supported formats. */ > + for (i =3D 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++] =3D i; > + > + igt_assert(count > 0); > + > + i =3D idx[rand() % count]; > + *format =3D plane->formats[i]; > + *modifier =3D 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 =3D width - (rand % w / 2). > + * > + * Use a minimum dimension of 16 for YUV, because planar YUV > + * subsamples the UV plane. > + */ > + min_dim =3D igt_format_is_yuv(*format) ? 16 : 8; > + > + *width =3D max((rand() % mode->hdisplay) + 1, 2 * min_dim); > + *height =3D 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 =3D igt_format_is_yuv(fb->drm_format); > + uint32_t width =3D fb->width, height =3D fb->height; > + double ratio; > + int ret; > + > + /* Randomize source offset in the first half of the original size. */ > + *src_x =3D rand() % (width / 2); > + *src_y =3D rand() % (height / 2); > + > + /* The source size only includes the active source area. */ > + *src_w =3D width - *src_x; > + *src_h =3D height - *src_y; > + > + if (allow_scaling) { > + *crtc_w =3D (rand() % mode->hdisplay) + 1; > + *crtc_h =3D (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 =3D ((double)*crtc_w / *src_w); > + if (ratio < 0.5) > + *src_w =3D *crtc_w * 2; > + else if (ratio > 0.8 && ratio < 1.2) > + *crtc_w =3D *src_w; > + else if (ratio > 3.0) > + *crtc_w =3D *src_w * 3; > + > + ratio =3D ((double)*crtc_h / *src_h); > + if (ratio < 0.5) > + *src_h =3D *crtc_h * 2; > + else if (ratio > 0.8 && ratio < 1.2) > + *crtc_h =3D *src_h; > + else if (ratio > 3.0) > + *crtc_h =3D *src_h * 3; > + } else { > + *crtc_w =3D *src_w; > + *crtc_h =3D *src_h; > + } > + > + if (*crtc_w !=3D *src_w || *crtc_h !=3D *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 =3D rand() % (mode->hdisplay - *crtc_w); > + else > + *crtc_x =3D 0; > + > + if (*crtc_h < mode->vdisplay) > + *crtc_y =3D rand() % (mode->vdisplay - *crtc_h); > + else > + *crtc_y =3D 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 =3D (rand() % mode->hdisplay) - *crtc_w / 2; > + *crtc_y =3D (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 =3D 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 !=3D -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 &=3D ~1; > + *src_y &=3D ~1; > + *src_w &=3D ~1; > + *src_h &=3D ~1; > + /* To handle 1:1 scaling, clear crtc_w/h too. */ > + *crtc_w &=3D ~1; > + *crtc_h &=3D ~1; > + > + if (*crtc_x < 0 && (*crtc_x & 1)) > + (*crtc_x)++; > + else > + *crtc_x &=3D ~1; > + > + /* If negative, round up to 0 instead of down */ > + if (*crtc_y < 0 && (*crtc_y & 1)) > + (*crtc_y)++; > + else > + *crtc_y &=3D ~1; > + > + configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, > + *crtc_h, *crtc_x, *crtc_y, fb); > + ret =3D 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 =3D *src_w; > + *crtc_h =3D *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 *re= sult, > + 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 =3D igt_get_cairo_surface(data->drm_fd, fb); > + > + if (src_x || src_y) { > + clipped_surface =3D cairo_image_surface_create(CAIRO_FORMAT_RGB24, > + src_w, src_h); > + > + cr =3D 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 =3D surface; > + } > + > + cr =3D cairo_create(result); > + > + cairo_translate(cr, crtc_x, crtc_y); > + > + if (src_w !=3D crtc_w || src_h !=3D 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 !=3D crtc_w || src_h !=3D 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 =3D (modifier !=3D 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 =3D 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 =3D 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[] =3D > + "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 =3D chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > + connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > + primary =3D 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 =3D &connector->modes[0]; > + if (check =3D=3D CHAMELIUM_CHECK_ANALOG) { > + bool bridge =3D 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[] =3D > + "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 =3D=3D CHAMELIUM_CHECK_ANALOG) > + bridge =3D chamelium_check_analog_bridge(data, port); > + > + i =3D 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 =3D chamelium_prepare_output(data, port, > + IGT_CUSTOM_EDID_BASE); > + connector =3D chamelium_port_get_connector(data->chamelium, port, > + false); > + primary =3D 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 =3D connector->count_modes; > + if (i >=3D count_modes) > + break; > + > + mode =3D &connector->modes[i]; > + > + if (check =3D=3D 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[] =3D > + "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 =3D 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 =3D chamelium_prepare_output(data, port, > + IGT_CUSTOM_EDID_BASE); > + connector =3D chamelium_port_get_connector(data->chamelium, port, > + false); > + primary =3D 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 =3D connector->count_modes; > + if (i >=3D count_modes) > + break; > + > + mode =3D &connector->modes[i]; > + > + fb_id =3D 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 =3D 0; j < 5; j++) { > + frame =3D 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[] =3D > + "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 =3D 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 =3D chamelium_prepare_output(data, port, > + IGT_CUSTOM_EDID_ASPECT_RATIO); > + connector =3D chamelium_port_get_connector(data->chamelium, port, false= ); > + primary =3D igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); > + igt_assert(primary); > + > + vic_mode =3D &vic_modes[vic]; > + aspect_ratio =3D vic_mode->picture_ar; > + > + found =3D false; > + igt_assert(connector->count_modes > 0); > + for (i =3D 0; i < connector->count_modes; i++) { > + mode =3D &connector->modes[i]; > + > + if (vic_mode_matches_drm(vic_mode, mode)) { > + found =3D true; > + break; > + } > + } > + igt_assert_f(found, > + "Failed to find mode with the correct aspect ratio\n"); > + > + fb_id =3D 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 =3D chamelium_get_last_infoframe(data->chamelium, port, > + CHAMELIUM_INFOFRAME_AVI); > + igt_assert_f(infoframe, "AVI InfoFrame not received\n"); > + > + ok =3D infoframe_avi_parse(&infoframe_avi, infoframe->version, > + infoframe->payload, infoframe->payload_size); > + igt_assert_f(ok, "Failed to parse AVI InfoFrame\n"); > + > + frame_ar =3D 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 =3D=3D frame_ar); > + igt_assert(infoframe_avi.vic =3D=3D vic); > + > + chamelium_infoframe_destroy(infoframe); > + igt_remove_fb(data->drm_fd, &fb); > + drmModeFreeConnector(connector); > +} > + > +static const char test_display_planes_random_desc[] =3D > + "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 =3D 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 =3D false; > + allow_yuv =3D false; > + break; > + case CHAMELIUM_CHECK_CHECKERBOARD: > + allow_scaling =3D true; > + allow_yuv =3D 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 =3D chamelium_prepare_output(data, port, IGT_CUSTOM_EDID_BASE); > + > + mode =3D igt_output_get_mode(output); > + > + /* Get a framebuffer for the primary plane. */ > + primary_plane =3D > + igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); > + igt_assert(primary_plane); > + > + fb_id =3D 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 =3D 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 =3D 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 =3D > + igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY); > + > + /* Limit the number of planes to a reasonable scene. */ > + overlay_planes_max =3D min(overlay_planes_max, 4u); > + > + overlay_planes_count =3D (rand() % overlay_planes_max) + 1; > + igt_debug("Using %d overlay planes\n", overlay_planes_count); > + > + overlay_fbs =3D calloc(sizeof(struct igt_fb), overlay_planes_count); > + > + for (i =3D 0; i < overlay_planes_count; i++) { > + struct igt_fb *overlay_fb =3D &overlay_fbs[i]; > + igt_plane_t *plane =3D 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 =3D=3D CHAMELIUM_CHECK_CRC) > + fb_crc =3D chamelium_calculate_fb_crc_async_start(data->drm_fd, > + &result_fb); > + > + igt_display_commit2(&data->display, COMMIT_ATOMIC); > + > + if (check =3D=3D CHAMELIUM_CHECK_CRC) { > + chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1); > + crc =3D chamelium_read_captured_crcs(data->chamelium, > + &captured_frame_count); > + > + igt_assert(captured_frame_count =3D=3D 1); > + > + expected_crc =3D 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 =3D=3D CHAMELIUM_CHECK_CHECKERBOARD) { > + struct chamelium_frame_dump *dump; > + > + dump =3D 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 =3D 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 =3D chamelium_prepare_output(&data, port, > + IGT_CUSTOM_EDID_BASE); > + primary =3D igt_output_get_plane_type( > + output, DRM_PLANE_TYPE_PRIMARY); > + igt_assert(primary); > + > + for (k =3D 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] !=3D > + 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 =3D chamelium_prepare_output(&data, port, > + IGT_CUSTOM_EDID_BASE); > + primary =3D igt_output_get_plane_type( > + output, DRM_PLANE_TYPE_PRIMARY); > + igt_assert(primary); > + > + for (k =3D 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] !=3D > + 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 > + */ > + > +#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 =3D 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 =3D chamelium_init(data->drm_fd, &data->display); > + igt_require(data->chamelium); > + > + data->ports =3D chamelium_get_ports(data->chamelium, &data->port_count); > + > + for (i =3D 0; i < IGT_CUSTOM_EDID_COUNT; ++i) { > + data->edids[i] =3D 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 =3D igt_hotplug_detected(mon, *timeout); > + igt_assert_eq(igt_gettime(&end), 0); > + > + elapsed =3D igt_time_elapsed(&start, &end); > + igt_assert_lte(0, elapsed); > + *timeout =3D 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 =3D CHAMELIUM_HOTPLUG_TIMEOUT; > + int hotplug_count =3D 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) =3D=3D 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 mor= e 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 c= onnected > + * 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 =3D &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 =3D chamelium_get_output_for_port(data, port); > + > + /* Refresh pipe to update connected status */ > + igt_output_set_pipe(output, PIPE_NONE); > + > + pipe =3D 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 d= raws 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 =3D output->display; > + igt_plane_t *primary =3D > + igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); > + drmModeConnector *connector =3D > + 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) =3D=3D 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[] =3D { 0xff000000, 0xffff0000, 0xff00ff00, 0xff0000ff, > + 0xffffffff }; > + unsigned i, j; > + > + for (i =3D 0; i < height; i++) > + for (j =3D 0; j < width; j++) > + *(data + i * stride / 4 + > + j) =3D 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 =3D=3D DRM_FORMAT_XRGB8888); > + > + fb_id =3D igt_create_fb(data->drm_fd, width, height, fourcc, > + DRM_FORMAT_MOD_LINEAR, fb); > + igt_assert(fb_id > 0); > + > + ptr =3D 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 =3D 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 =3D > + chamelium_port_get_connector(chamelium, port, false); > + drmModeModeInfo mode; > + igt_assert(&connector->modes[0] !=3D 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 =3D > + chamelium_port_get_connector(data->chamelium, port, true); > + igt_output_t *output =3D > + igt_output_from_connector(&data->display, connector); > + drmModeFreeConnector(connector); > + igt_assert(output !=3D NULL); > + return output; > +} > + > +/* Set the EDID of index @edid to Chamelium's @port. */ > +void chamelium_set_edid(chamelium_data_t *data, struct chamelium_port *p= ort, > + 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 ch= ecking > + * if it has its own EDID. > + * > + */ > +bool chamelium_check_analog_bridge(chamelium_data_t *data, > + struct chamelium_port *port) > +{ > + drmModePropertyBlobPtr edid_blob =3D NULL; > + drmModeConnector *connector =3D > + 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) !=3D 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 =3D > + drmModeGetPropertyBlob(data->drm_fd, edid_blob_id)); > + > + edid =3D (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] !=3D 'I' || edid_vendor[1] !=3D 'G' || > + edid_vendor[2] !=3D '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 > + */ > + > +#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 =3D 0, port =3D data.ports[p]; p < data.port_count; \ > + p++, port =3D data.ports[p]) > + > +#define connector_subtest(name__, type__) \ > + igt_subtest(name__) \ > + for_each_port(p, port) if (chamelium_port_get_type(port) =3D=3D \ > + DRM_MODE_CONNECTOR_##type__) > + > +/* > + * The chamelium data structure is used to store all the information kno= wn 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 *p= ort, > + 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_ch= amelium_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 > + */ > + > +#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 =3D connected ? DRM_MODE_DISCONNECTED : > + DRM_MODE_CONNECTED; > + int timeout =3D CHAMELIUM_HOTPLUG_TIMEOUT; > + int delay; > + int p; > + > + igt_flush_uevents(mon); > + > + delay =3D igt_get_autoresume_delay(state) * 1000 / 2; > + > + if (port) { > + chamelium_schedule_hpd_toggle(data->chamelium, port, delay, > + !connected); > + } else { > + for (p =3D 0; p < data->port_count; p++) { > + port =3D data->ports[p]; > + chamelium_schedule_hpd_toggle(data->chamelium, port, > + delay, !connected); > + } > + > + port =3D 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 =3D 0; p < data->port_count; p++) { > + drmModeConnection current_state; > + > + port =3D 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 =3D chamelium_reprobe_connector( > + &data->display, data->chamelium, port); > + if (p > 0 && current_state !=3D target_state) { > + igt_assert(chamelium_wait_for_hotplug( > + mon, &timeout)); > + current_state =3D chamelium_reprobe_connector( > + &data->display, data->chamelium, port); > + } > + > + igt_assert_eq(current_state, target_state); > + } > + > + port =3D NULL; > + } > +} > + > +static const char test_basic_hotplug_desc[] =3D > + "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 =3D { 0 }; > + drmModeModeInfo mode; > + struct udev_monitor *mon =3D igt_watch_uevents(); > + igt_output_t *output =3D 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 =3D 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 =3D=3D TEST_MODESET_ON_OFF || > + (modeset_mode =3D=3D TEST_MODESET_ON && i =3D=3D 0)) { > + if (i =3D=3D 0) { > + /* We can only get mode and pipe once we are > + * connected */ > + output =3D chamelium_get_output_for_port(data, > + port); > + pipe =3D chamelium_get_pipe_for_output( > + &data->display, output); > + mode =3D 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 =3D=3D 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[] =3D > + "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 =3D 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 =3D 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[] =3D > + "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 =3D 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[] =3D > + "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 =3D igt_watch_uevents(); > + struct chamelium_port *port; > + int p; > + > + for (p =3D 0; p < data->port_count; p++) { > + port =3D 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[] =3D > + "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 =3D 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[] =3D > + "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 =3D 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 =3D 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 +=3D 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[] =3D > + "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 @@ >=20=20 > /* > * 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. > */ >=20=20 > #include > 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 =3D [ > ] >=20=20 > chamelium_progs =3D [ > - 'kms_chamelium', > + 'kms_chamelium_audio', > + 'kms_chamelium_edid', > + 'kms_chamelium_frames', > + 'kms_chamelium_hpd', > ] >=20=20 > test_deps =3D [ igt_deps ] > @@ -309,7 +312,8 @@ endforeach > if chamelium.found() > foreach prog : chamelium_progs > test_executables +=3D executable(prog, > - join_paths('chamelium', prog + '.c'), > + [join_paths('chamelium', prog + '.c'),=20 > + join_paths('chamelium', 'kms_chamelium_helper.c')], > dependencies : test_deps, > install_dir : libexecdir, > install_rpath : libexecdir_rpathdir, > @@ -436,13 +440,13 @@ test_executables +=3D executable('kms_color', > test_list +=3D 'kms_color' >=20=20 > if chamelium.found() > - test_executables +=3D executable('kms_color_chamelium', > - [ 'chamelium/kms_color_chamelium.c', 'kms_c= olor_helper.c' ], > + test_executables +=3D executable('kms_chamelium_color', > + [ 'chamelium/kms_chamelium_color.c', 'kms_c= olor_helper.c' ], > dependencies : test_deps + [ chamelium ], > install_dir : libexecdir, > install_rpath : libexecdir_rpathdir, > install : true) > - test_list +=3D 'kms_color_chamelium' > + test_list +=3D 'kms_chamelium_color' > endif >=20=20 > test_executables +=3D 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 --=20 Jani Nikula, Intel Open Source Graphics Center