summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac4
-rw-r--r--docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml1
-rw-r--r--lib/Makefile.am6
-rw-r--r--lib/Makefile.sources2
-rw-r--r--lib/igt.h1
-rw-r--r--lib/igt_chamelium.c1346
-rw-r--r--lib/igt_chamelium.h103
-rw-r--r--tests/Makefile.am5
-rw-r--r--tests/Makefile.sources1
-rw-r--r--tests/chamelium.c689
10 files changed, 2157 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index d2681400..ac13dbe4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -170,6 +170,10 @@ if test x"$udev" = xyes; then
fi
PKG_CHECK_MODULES(GLIB, glib-2.0)
+# for chamelium
+PKG_CHECK_MODULES(XMLRPC, xmlrpc_client)
+PKG_CHECK_MODULES(PIXMAN, pixman-1)
+
# -----------------------------------------------------------------------------
# Configuration options
# -----------------------------------------------------------------------------
diff --git a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml
index 91b24991..990bbb80 100644
--- a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml
+++ b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml
@@ -34,6 +34,7 @@
<xi:include href="xml/igt_vc4.xml"/>
<xi:include href="xml/igt_vgem.xml"/>
<xi:include href="xml/igt_dummyload.xml"/>
+ <xi:include href="xml/igt_chamelium.xml"/>
</chapter>
<xi:include href="xml/igt_test_programs.xml"/>
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 57d3dbb7..7030ea27 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -31,6 +31,9 @@ AM_CFLAGS = \
$(KMOD_CFLAGS) \
$(PROCPS_CFLAGS) \
$(DEBUG_CFLAGS) \
+ $(XMLRPC_CFLAGS) \
+ $(LIBUDEV_CFLAGS) \
+ $(PIXMAN_CFLAGS) \
-DIGT_SRCDIR=\""$(abs_top_srcdir)/tests"\" \
-DIGT_DATADIR=\""$(pkgdatadir)"\" \
-DIGT_LOG_DOMAIN=\""$(subst _,-,$*)"\" \
@@ -47,5 +50,8 @@ libintel_tools_la_LIBADD = \
$(LIBUDEV_LIBS) \
$(LIBUNWIND_LIBS) \
$(TIMER_LIBS) \
+ $(XMLRPC_LIBS) \
+ $(LIBUDEV_LIBS) \
+ $(PIXMAN_LIBS) \
-lm
diff --git a/lib/Makefile.sources b/lib/Makefile.sources
index 53fdb54c..8cb95d83 100644
--- a/lib/Makefile.sources
+++ b/lib/Makefile.sources
@@ -83,6 +83,8 @@ lib_source_list = \
uwildmat/uwildmat.c \
igt_kmod.c \
igt_kmod.h \
+ igt_chamelium.h \
+ igt_chamelium.c \
$(NULL)
.PHONY: version.h.tmp
diff --git a/lib/igt.h b/lib/igt.h
index a0028d5e..a97923ec 100644
--- a/lib/igt.h
+++ b/lib/igt.h
@@ -38,6 +38,7 @@
#include "igt_kms.h"
#include "igt_pm.h"
#include "igt_stats.h"
+#include "igt_chamelium.h"
#include "instdone.h"
#include "intel_batchbuffer.h"
#include "intel_chipset.h"
diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
new file mode 100644
index 00000000..2315ce60
--- /dev/null
+++ b/lib/igt_chamelium.c
@@ -0,0 +1,1346 @@
+/*
+ * Copyright © 2016 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Lyude Paul <lyude@redhat.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+#include <xmlrpc-c/base.h>
+#include <xmlrpc-c/client.h>
+#include <pthread.h>
+#include <glib.h>
+#include <pixman.h>
+#include <cairo.h>
+
+#include "igt.h"
+
+/**
+ * SECTION:igt_chamelium
+ * @short_description: Library for using the Chamelium into igt tests
+ * @title: Chamelium
+ * @include: igt_chamelium.h
+ *
+ * This library contains helpers for using Chameliums in IGT tests. This allows
+ * for tests to simulate more difficult tasks to automate such as display
+ * hotplugging, faulty display behaviors, etc.
+ *
+ * More information on the Chamelium can be found
+ * [on the ChromeOS project page](https://www.chromium.org/chromium-os/testing/chamelium).
+ *
+ * In order to run tests using the Chamelium, a valid configuration file must be
+ * present. The configuration file is a normal Glib keyfile (similar to Windows
+ * INI) structured like so:
+ *
+ * |[<!-- language="plain" -->
+ * [Chamelium]
+ * URL=http://chameleon:9992 # The URL used for connecting to the Chamelium's RPC server
+ *
+ * # The rest of the sections are used for defining connector mappings.
+ * # This is required so any tests using the Chamelium know which connector
+ * # on the test machine should be connected to each Chamelium port.
+ * #
+ * # In the event that any of these mappings are specified incorrectly,
+ * # any hotplugging tests for the incorrect connector mapping will fail.
+ *
+ * [Chamelium:DP-1] # The name of the DRM connector
+ * ChameliumPortID=1 # The ID of the port on the Chamelium this connector is attached to
+ *
+ * [Chamelium:HDMI-A-1]
+ * ChameliumPortID=3
+ * ]|
+ *
+ * By default, this file is expected to exist in ~/.igtrc . The directory for
+ * this can be overriden by setting the environment variable %IGT_CONFIG_PATH.
+ */
+
+struct chamelium_edid {
+ int id;
+ struct igt_list link;
+};
+
+struct chamelium_port {
+ unsigned int type;
+ int id;
+ int connector_id;
+ char *name;
+};
+
+struct chamelium_frame_dump {
+ unsigned char *bgr;
+ size_t size;
+ int width;
+ int height;
+ struct chamelium_port *port;
+};
+
+struct chamelium {
+ xmlrpc_env env;
+ xmlrpc_client *client;
+ char *url;
+
+ /* Indicates the last port to have been used for capturing video */
+ struct chamelium_port *capturing_port;
+
+ int drm_fd;
+
+ struct chamelium_edid *edids;
+ struct chamelium_port *ports;
+ int port_count;
+};
+
+static struct chamelium *cleanup_instance;
+
+/**
+ * chamelium_get_ports:
+ * @chamelium: The Chamelium instance to use
+ * @count: Where to store the number of ports
+ *
+ * Retrieves all of the ports currently configured for use with this chamelium
+ *
+ * Returns: an array containing a pointer to each configured chamelium port
+ */
+struct chamelium_port **chamelium_get_ports(struct chamelium *chamelium,
+ int *count)
+{
+ int i;
+ struct chamelium_port **ret =
+ calloc(sizeof(void*), chamelium->port_count);
+
+ *count = chamelium->port_count;
+ for (i = 0; i < chamelium->port_count; i++)
+ ret[i] = &chamelium->ports[i];
+
+ return ret;
+}
+
+/**
+ * chamelium_port_get_type:
+ * @port: The chamelium port to retrieve the type from
+ *
+ * Retrieves the DRM connector type of the physical port on the Chamelium. It
+ * should be noted that this type may differ from the type provided by the
+ * driver.
+ *
+ * Returns: the DRM connector type of the physical Chamelium port
+ */
+unsigned int chamelium_port_get_type(const struct chamelium_port *port) {
+ return port->type;
+}
+
+/**
+ * chamelium_port_get_connector:
+ * @chamelium: The Chamelium instance to use
+ * @port: The chamelium port to retrieve the DRM connector for
+ * @reprobe: Whether or not to reprobe the DRM connector
+ *
+ * Get a drmModeConnector object for the given Chamelium port, and optionally
+ * reprobe the port in the process
+ *
+ * Returns: a drmModeConnector object corresponding to the given port
+ */
+drmModeConnector *chamelium_port_get_connector(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ bool reprobe)
+{
+ drmModeConnector *connector;
+
+ if (reprobe)
+ connector = drmModeGetConnector(chamelium->drm_fd,
+ port->connector_id);
+ else
+ connector = drmModeGetConnectorCurrent(
+ chamelium->drm_fd, port->connector_id);
+
+ return connector;
+}
+
+/**
+ * chamelium_port_get_name:
+ * @port: The chamelium port to retrieve the name of
+ *
+ * Gets the name of the DRM connector corresponding to the given Chamelium
+ * port.
+ *
+ * Returns: the name of the DRM connector
+ */
+const char *chamelium_port_get_name(struct chamelium_port *port)
+{
+ return port->name;
+}
+
+/**
+ * chamelium_destroy_frame_dump:
+ * @dump: The frame dump to destroy
+ *
+ * Destroys the given frame dump and frees all of the resources associated with
+ * it.
+ */
+void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
+{
+ free(dump->bgr);
+ free(dump);
+}
+
+struct fsm_monitor_args {
+ struct chamelium *chamelium;
+ struct chamelium_port *port;
+ struct udev_monitor *mon;
+};
+
+/*
+ * Whenever resolutions or other factors change with the display output, the
+ * Chamelium's display receivers need to be fully reset in order to perform any
+ * frame-capturing related tasks. This requires cutting off the display then
+ * turning it back on, and is indicated by the Chamelium sending hotplug events
+ */
+static void *chamelium_fsm_mon(void *data)
+{
+ struct fsm_monitor_args *args = data;
+ drmModeConnector *connector;
+ int drm_fd = args->chamelium->drm_fd;
+
+ /*
+ * Wait for the chamelium to try unplugging the connector, otherwise
+ * the thread calling chamelium_rpc will kill us
+ */
+ igt_hotplug_detected(args->mon, 60);
+
+ /*
+ * Just in case the RPC call being executed returns before we complete
+ * the FSM modesetting sequence, so we don't leave the display in a bad
+ * state.
+ */
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+ igt_debug("Chamelium needs FSM, handling\n");
+ connector = chamelium_port_get_connector(args->chamelium, args->port,
+ false);
+ kmstest_set_connector_dpms(drm_fd, connector, DRM_MODE_DPMS_OFF);
+ kmstest_set_connector_dpms(drm_fd, connector, DRM_MODE_DPMS_ON);
+
+ drmModeFreeConnector(connector);
+ return NULL;
+}
+
+static xmlrpc_value *chamelium_rpc(struct chamelium *chamelium,
+ struct chamelium_port *fsm_port,
+ const char *method_name,
+ const char *format_str,
+ ...)
+{
+ xmlrpc_value *res;
+ va_list va_args;
+ struct fsm_monitor_args monitor_args;
+ pthread_t fsm_thread_id;
+
+ /* Cleanup the last error, if any */
+ if (chamelium->env.fault_occurred) {
+ xmlrpc_env_clean(&chamelium->env);
+ xmlrpc_env_init(&chamelium->env);
+ }
+
+ /* Unfortunately xmlrpc_client's event loop helpers are rather useless
+ * for implementing any sort of event loop, since they provide no way
+ * to poll for events other then the RPC response. This means in order
+ * to handle the chamelium attempting FSM, we have to fork into another
+ * thread and have that handle hotplugging displays
+ */
+ if (fsm_port) {
+ monitor_args.chamelium = chamelium;
+ monitor_args.port = fsm_port;
+ monitor_args.mon = igt_watch_hotplug();
+ pthread_create(&fsm_thread_id, NULL, chamelium_fsm_mon,
+ &monitor_args);
+ }
+
+ va_start(va_args, format_str);
+ xmlrpc_client_call2f_va(&chamelium->env, chamelium->client,
+ chamelium->url, method_name, format_str, &res,
+ va_args);
+ va_end(va_args);
+
+ if (fsm_port) {
+ pthread_cancel(fsm_thread_id);
+ igt_cleanup_hotplug(monitor_args.mon);
+ }
+
+ igt_assert_f(!chamelium->env.fault_occurred,
+ "Chamelium RPC call failed: %s\n",
+ chamelium->env.fault_string);
+
+ return res;
+}
+
+/**
+ * chamelium_plug:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the chamelium to plug
+ *
+ * Simulate a display connector being plugged into the system using the
+ * chamelium.
+ */
+void chamelium_plug(struct chamelium *chamelium, struct chamelium_port *port)
+{
+ igt_debug("Plugging %s\n", port->name);
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Plug", "(i)", port->id));
+}
+
+/**
+ * chamelium_unplug:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the chamelium to unplug
+ *
+ * Simulate a display connector being unplugged from the system using the
+ * chamelium.
+ */
+void chamelium_unplug(struct chamelium *chamelium, struct chamelium_port *port)
+{
+ igt_debug("Unplugging port %s\n", port->name);
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Unplug", "(i)",
+ port->id));
+}
+
+/**
+ * chamelium_is_plugged:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the Chamelium to check the status of
+ *
+ * Check whether or not the given port has been plugged into the system using
+ * #chamelium_plug.
+ *
+ * Returns: %true if the connector is set to plugged in, %false otherwise.
+ */
+bool chamelium_is_plugged(struct chamelium *chamelium,
+ struct chamelium_port *port)
+{
+ xmlrpc_value *res;
+ xmlrpc_bool is_plugged;
+
+ res = chamelium_rpc(chamelium, NULL, "IsPlugged", "(i)", port->id);
+
+ xmlrpc_read_bool(&chamelium->env, res, &is_plugged);
+ xmlrpc_DECREF(res);
+
+ return is_plugged;
+}
+
+/**
+ * chamelium_port_wait_video_input_stable:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the Chamelium to check the status of
+ * @timeout_secs: How long to wait for a video signal to appear before timing
+ * out
+ *
+ * Waits for a video signal to appear on the given port. This is useful for
+ * checking whether or not we've setup a monitor correctly.
+ *
+ * Returns: %true if a video signal was detected, %false if we timed out
+ */
+bool chamelium_port_wait_video_input_stable(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int timeout_secs)
+{
+ xmlrpc_value *res;
+ xmlrpc_bool is_on;
+
+ igt_debug("Waiting for video input to stabalize on %s\n", port->name);
+
+ res = chamelium_rpc(chamelium, port, "WaitVideoInputStable", "(ii)",
+ port->id, timeout_secs);
+
+ xmlrpc_read_bool(&chamelium->env, res, &is_on);
+ xmlrpc_DECREF(res);
+
+ return is_on;
+}
+
+/**
+ * chamelium_fire_hpd_pulses:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to fire the HPD pulses on
+ * @width_msec: How long each pulse should last
+ * @count: The number of pulses to send
+ *
+ * A convienence function for sending multiple hotplug pulses to the system.
+ * The pulses start at low (e.g. connector is disconnected), and then alternate
+ * from high (e.g. connector is plugged in) to low. This is the equivalent of
+ * repeatedly calling #chamelium_plug and #chamelium_unplug, waiting
+ * @width_msec between each call.
+ *
+ * If @count is even, the last pulse sent will be high, and if it's odd then it
+ * will be low. Resetting the HPD line back to it's previous state, if desired,
+ * is the responsibility of the caller.
+ */
+void chamelium_fire_hpd_pulses(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int width_msec, int count)
+{
+ xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env);
+ xmlrpc_value *width = xmlrpc_int_new(&chamelium->env, width_msec);
+ int i;
+
+ igt_debug("Firing %d HPD pulses with width of %d msec on %s\n",
+ count, width_msec, port->name);
+
+ for (i = 0; i < count; i++)
+ xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
+
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "FireMixedHpdPulses",
+ "(iA)", port->id, pulse_widths));
+
+ xmlrpc_DECREF(width);
+ xmlrpc_DECREF(pulse_widths);
+}
+
+/**
+ * chamelium_fire_mixed_hpd_pulses:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to fire the HPD pulses on
+ * @...: The length of each pulse in milliseconds, terminated with a %0
+ *
+ * Does the same thing as #chamelium_fire_hpd_pulses, but allows the caller to
+ * specify the length of each individual pulse.
+ */
+void chamelium_fire_mixed_hpd_pulses(struct chamelium *chamelium,
+ struct chamelium_port *port, ...)
+{
+ va_list args;
+ xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env), *width;
+ int arg;
+
+ igt_debug("Firing mixed HPD pulses on %s\n", port->name);
+
+ va_start(args, port);
+ for (arg = va_arg(args, int); arg; arg = va_arg(args, int)) {
+ width = xmlrpc_int_new(&chamelium->env, arg);
+ xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
+ xmlrpc_DECREF(width);
+ }
+ va_end(args);
+
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "FireMixedHpdPulses",
+ "(iA)", port->id, pulse_widths));
+
+ xmlrpc_DECREF(pulse_widths);
+}
+
+static void async_rpc_handler(const char *server_url, const char *method_name,
+ xmlrpc_value *param_array, void *user_data,
+ xmlrpc_env *fault, xmlrpc_value *result)
+{
+ /* We don't care about the responses */
+}
+
+/**
+ * chamelium_async_hpd_pulse_start:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to fire the HPD pulses on
+ * @high: Whether to fire a high pulse (e.g. simulate a connect), or a low
+ * pulse (e.g. simulate a disconnect)
+ * @delay_secs: How long to wait before sending the HPD pulse.
+ *
+ * Instructs the chamelium to send an hpd pulse after @delay_secs seconds have
+ * passed, without waiting for the chamelium to finish. This is useful for
+ * testing things such as hpd after a suspend/resume cycle, since we can't tell
+ * the chamelium to send a hotplug at the same time that our system is
+ * suspended.
+ *
+ * It is required that the user eventually call
+ * #chamelium_async_hpd_pulse_finish, to clean up the leftover XML-RPC
+ * responses from the chamelium.
+ */
+void chamelium_async_hpd_pulse_start(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ bool high, int delay_secs)
+{
+ xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env), *width;
+
+ /* TODO: Actually implement something in the chameleon server to allow
+ * for delayed actions such as hotplugs. This would work a bit better
+ * and allow us to test suspend/resume on ports without hpd like VGA
+ */
+
+ igt_debug("Sending HPD pulse (%s) on %s with %d second delay\n",
+ high ? "high->low" : "low->high", port->name, delay_secs);
+
+ /* If we're starting at high, make the first pulse width 0 so we keep
+ * the port connected */
+ if (high) {
+ width = xmlrpc_int_new(&chamelium->env, 0);
+ xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
+ xmlrpc_DECREF(width);
+ }
+
+ width = xmlrpc_int_new(&chamelium->env, delay_secs * 1000);
+ xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
+ xmlrpc_DECREF(width);
+
+ xmlrpc_client_start_rpcf(&chamelium->env, chamelium->client,
+ chamelium->url,
+ "FireMixedHpdPulses", async_rpc_handler, NULL,
+ "(iA)", port->id, pulse_widths);
+ xmlrpc_DECREF(pulse_widths);
+}
+
+/**
+ * chamelium_async_hpd_pulse_finish:
+ * @chamelium: The Chamelium instance to use
+ *
+ * Waits for any asynchronous RPC started by #chamelium_async_hpd_pulse_start
+ * to complete, and then cleans up any leftover responses from the chamelium.
+ * If all of the RPC calls have already completed, this function returns
+ * immediately.
+ */
+void chamelium_async_hpd_pulse_finish(struct chamelium *chamelium)
+{
+ xmlrpc_client_event_loop_finish(chamelium->client);
+}
+
+/**
+ * chamelium_new_edid:
+ * @chamelium: The Chamelium instance to use
+ * @edid: The edid blob to upload to the chamelium
+ *
+ * Uploads and registers a new EDID with the chamelium. The EDID will be
+ * destroyed automatically when #chamelium_deinit is called.
+ *
+ * Returns: The ID of the EDID uploaded to the chamelium.
+ */
+int chamelium_new_edid(struct chamelium *chamelium, const unsigned char *edid)
+{
+ xmlrpc_value *res;
+ struct chamelium_edid *allocated_edid;
+ int edid_id;
+
+ res = chamelium_rpc(chamelium, NULL, "CreateEdid", "(6)",
+ edid, EDID_LENGTH);
+
+ xmlrpc_read_int(&chamelium->env, res, &edid_id);
+ xmlrpc_DECREF(res);
+
+ allocated_edid = malloc(sizeof(struct chamelium_edid));
+ memset(allocated_edid, 0, sizeof(*allocated_edid));
+
+ allocated_edid->id = edid_id;
+ igt_list_init(&allocated_edid->link);
+
+ if (chamelium->edids)
+ igt_list_add(&chamelium->edids->link, &allocated_edid->link);
+ else
+ chamelium->edids = allocated_edid;
+
+ return edid_id;
+}
+
+static void chamelium_destroy_edid(struct chamelium *chamelium, int edid_id)
+{
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "DestroyEdid", "(i)",
+ edid_id));
+}
+
+/**
+ * chamelium_port_set_edid:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the Chamelium to set the EDID on
+ * @edid_id: The ID of an EDID on the chamelium created with
+ * #chamelium_new_edid, or 0 to disable the EDID on the port
+ *
+ * Sets a port on the chamelium to use the specified EDID. This does not fire a
+ * hotplug pulse on it's own, and merely changes what EDID the chamelium port
+ * will report to us the next time we probe it. Users will need to reprobe the
+ * connectors themselves if they want to see the EDID reported by the port
+ * change.
+ */
+void chamelium_port_set_edid(struct chamelium *chamelium,
+ struct chamelium_port *port, int edid_id)
+{
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "ApplyEdid", "(ii)",
+ port->id, edid_id));
+}
+
+/**
+ * chamelium_port_set_ddc_state:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to change the DDC state on
+ * @enabled: Whether or not to enable the DDC bus
+ *
+ * This disables the DDC bus (e.g. the i2c line on the connector that gives us
+ * an EDID) of the specified port on the chamelium. This is useful for testing
+ * behavior on legacy connectors such as VGA, where the presence of a DDC bus
+ * is not always guaranteed.
+ */
+void chamelium_port_set_ddc_state(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ bool enabled)
+{
+ igt_debug("%sabling DDC bus on %s\n",
+ enabled ? "En" : "Dis", port->name);
+
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "SetDdcState", "(ib)",
+ port->id, enabled));
+}
+
+/**
+ * chamelium_port_get_ddc_state:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the Chamelium to check the status of
+ *
+ * Check whether or not the DDC bus on the specified chamelium port is enabled
+ * or not.
+ *
+ * Returns: %true if the DDC bus is enabled, %false otherwise.
+ */
+bool chamelium_port_get_ddc_state(struct chamelium *chamelium,
+ struct chamelium_port *port)
+{
+ xmlrpc_value *res;
+ xmlrpc_bool enabled;
+
+ res = chamelium_rpc(chamelium, NULL, "IsDdcEnabled", "(i)", port->id);
+ xmlrpc_read_bool(&chamelium->env, res, &enabled);
+
+ xmlrpc_DECREF(res);
+ return enabled;
+}
+
+/**
+ * chamelium_port_get_resolution:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port on the Chamelium to check
+ * @x: Where to store the horizontal resolution of the port
+ * @y: Where to store the verical resolution of the port
+ *
+ * Check the current reported display resolution of the specified port on the
+ * chamelium. This information is provided by the chamelium itself, not DRM.
+ * Useful for verifying that we really are scanning out at the resolution we
+ * think we are.
+ */
+void chamelium_port_get_resolution(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int *x, int *y)
+{
+ xmlrpc_value *res, *res_x, *res_y;
+
+ res = chamelium_rpc(chamelium, port, "DetectResolution", "(i)",
+ port->id);
+
+ xmlrpc_array_read_item(&chamelium->env, res, 0, &res_x);
+ xmlrpc_array_read_item(&chamelium->env, res, 1, &res_y);
+ xmlrpc_read_int(&chamelium->env, res_x, x);
+ xmlrpc_read_int(&chamelium->env, res_y, y);
+
+ xmlrpc_DECREF(res_x);
+ xmlrpc_DECREF(res_y);
+ xmlrpc_DECREF(res);
+}
+
+static void chamelium_get_captured_resolution(struct chamelium *chamelium,
+ int *w, int *h)
+{
+ xmlrpc_value *res, *res_w, *res_h;
+
+ res = chamelium_rpc(chamelium, NULL, "GetCapturedResolution", "()");
+
+ xmlrpc_array_read_item(&chamelium->env, res, 0, &res_w);
+ xmlrpc_array_read_item(&chamelium->env, res, 1, &res_h);
+ xmlrpc_read_int(&chamelium->env, res_w, w);
+ xmlrpc_read_int(&chamelium->env, res_h, h);
+
+ xmlrpc_DECREF(res_w);
+ xmlrpc_DECREF(res_h);
+ xmlrpc_DECREF(res);
+}
+
+static struct chamelium_frame_dump *frame_from_xml(struct chamelium *chamelium,
+ xmlrpc_value *frame_xml)
+{
+ struct chamelium_frame_dump *ret = malloc(sizeof(*ret));
+
+ chamelium_get_captured_resolution(chamelium, &ret->width, &ret->height);
+ ret->port = chamelium->capturing_port;
+ xmlrpc_read_base64(&chamelium->env, frame_xml, &ret->size,
+ (void*)&ret->bgr);
+
+ return ret;
+}
+
+/**
+ * chamelium_port_dump_pixels:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to perform the video capture on
+ * @x: The X coordinate to crop the screen capture to
+ * @y: The Y coordinate to crop the screen capture to
+ * @w: The width of the area to crop the screen capture to, or 0 for the whole
+ * screen
+ * @h: The height of the area to crop the screen capture to, or 0 for the whole
+ * screen
+ *
+ * Captures the currently displayed image on the given chamelium port,
+ * optionally cropped to a given region. In situations where pre-calculating
+ * CRCs may not be reliable, this can be used as an alternative for figuring
+ * out whether or not the correct images are being displayed on the screen.
+ *
+ * The frame dump data returned by this function should be freed when the
+ * caller is done with it using #chamelium_destroy_frame_dump.
+ *
+ * As an important note: some of the EDIDs provided by the Chamelium cause
+ * certain GPU drivers to default to using limited color ranges. This can cause
+ * video captures from the Chamelium to provide different images then expected
+ * due to the difference in color ranges (framebuffer uses full color range,
+ * but the video output doesn't), and as a result lead to CRC mismatches. To
+ * workaround this, the caller should force the connector to use full color
+ * ranges by using #kmstest_set_connector_broadcast_rgb before setting up the
+ * display.
+ *
+ * Returns: a chamelium_frame_dump struct
+ */
+struct chamelium_frame_dump *chamelium_port_dump_pixels(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int x, int y,
+ int w, int h)
+{
+ xmlrpc_value *res;
+ struct chamelium_frame_dump *frame;
+
+ res = chamelium_rpc(chamelium, port, "DumpPixels",
+ (w && h) ? "(iiiii)" : "(innnn)",
+ port->id, x, y, w, h);
+ chamelium->capturing_port = port;
+
+ frame = frame_from_xml(chamelium, res);
+ xmlrpc_DECREF(res);
+
+ return frame;
+}
+
+static void crc_from_xml(struct chamelium *chamelium,
+ xmlrpc_value *xml_crc, igt_crc_t *out)
+{
+ xmlrpc_value *res;
+ int i;
+
+ out->n_words = xmlrpc_array_size(&chamelium->env, xml_crc);
+ for (i = 0; i < out->n_words; i++) {
+ xmlrpc_array_read_item(&chamelium->env, xml_crc, i, &res);
+ xmlrpc_read_int(&chamelium->env, res, (int*)&out->crc[i]);
+ xmlrpc_DECREF(res);
+ }
+}
+
+/**
+ * chamelium_get_crc_for_area:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to perform the CRC checking on
+ * @x: The X coordinate on the emulated display to start calculating the CRC
+ * from
+ * @y: The Y coordinate on the emulated display to start calculating the CRC
+ * from
+ * @w: The width of the area to fetch the CRC from, or %0 for the whole display
+ * @h: The height of the area to fetch the CRC from, or %0 for the whole display
+ *
+ * Reads back the pixel CRC for an area on the specified chamelium port. This
+ * is the same as using the CRC readback from a GPU, the main difference being
+ * the data is provided by the chamelium and also allows us to specify a region
+ * of the screen to use as opposed to the entire thing.
+ *
+ * As an important note: some of the EDIDs provided by the Chamelium cause
+ * certain GPU drivers to default to using limited color ranges. This can cause
+ * video captures from the Chamelium to provide different images then expected
+ * due to the difference in color ranges (framebuffer uses full color range,
+ * but the video output doesn't), and as a result lead to CRC mismatches. To
+ * workaround this, the caller should force the connector to use full color
+ * ranges by using #kmstest_set_connector_broadcast_rgb before setting up the
+ * display.
+ *
+ * After the caller is finished with the EDID returned by this function, the
+ * caller should manually free the resources associated with it.
+ *
+ * Returns: The CRC read back from the chamelium
+ */
+igt_crc_t *chamelium_get_crc_for_area(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int x, int y, int w, int h)
+{
+ xmlrpc_value *res;
+ igt_crc_t *ret = malloc(sizeof(igt_crc_t));
+
+ res = chamelium_rpc(chamelium, port, "ComputePixelChecksum",
+ (w && h) ? "(iiiii)" : "(innnn)",
+ port->id, x, y, w, h);
+ chamelium->capturing_port = port;
+
+ crc_from_xml(chamelium, res, ret);
+ xmlrpc_DECREF(res);
+
+ return ret;
+}
+
+/**
+ * chamelium_start_capture:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to perform the video capture on
+ * @x: The X coordinate to crop the video to
+ * @y: The Y coordinate to crop the video to
+ * @w: The width of the cropped video, or %0 for the whole display
+ * @h: The height of the cropped video, or %0 for the whole display
+ *
+ * Starts capturing video frames on the given Chamelium port. Once the user is
+ * finished capturing frames, they should call #chamelium_stop_capture.
+ *
+ * A blocking, one-shot version of this function is available: see
+ * #chamelium_capture
+ *
+ * As an important note: some of the EDIDs provided by the Chamelium cause
+ * certain GPU drivers to default to using limited color ranges. This can cause
+ * video captures from the Chamelium to provide different images then expected
+ * due to the difference in color ranges (framebuffer uses full color range,
+ * but the video output doesn't), and as a result lead to CRC and frame dump
+ * comparison mismatches. To workaround this, the caller should force the
+ * connector to use full color ranges by using
+ * #kmstest_set_connector_broadcast_rgb before setting up the display.
+ */
+void chamelium_start_capture(struct chamelium *chamelium,
+ struct chamelium_port *port, int x, int y, int w, int h)
+{
+ xmlrpc_DECREF(chamelium_rpc(chamelium, port, "StartCapturingVideo",
+ (w && h) ? "(iiiii)" : "(innnn)",
+ port->id, x, y, w, h));
+ chamelium->capturing_port = port;
+}
+
+/**
+ * chamelium_stop_capture:
+ * @chamelium: The Chamelium instance to use
+ * @frame_count: The number of frames to wait to capture, or %0 to stop
+ * immediately
+ *
+ * Finishes capturing video frames on the given Chamelium port. If @frame_count
+ * is specified, this call will block until the given number of frames have been
+ * captured.
+ */
+void chamelium_stop_capture(struct chamelium *chamelium, int frame_count)
+{
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "StopCapturingVideo",
+ "(i)", frame_count));
+}
+
+/**
+ * chamelium_capture:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to perform the video capture on
+ * @x: The X coordinate to crop the video to
+ * @y: The Y coordinate to crop the video to
+ * @w: The width of the cropped video, or %0 for the whole display
+ * @h: The height of the cropped video, or %0 for the whole display
+ * @frame_count: The number of frames to capture
+ *
+ * Captures the given number of frames on the chamelium. This is equivalent to
+ * calling #chamelium_start_capture immediately followed by
+ * #chamelium_stop_capture. The caller is blocked until all of the frames have
+ * been captured.
+ *
+ * As an important note: some of the EDIDs provided by the Chamelium cause
+ * certain GPU drivers to default to using limited color ranges. This can cause
+ * video captures from the Chamelium to provide different images then expected
+ * due to the difference in color ranges (framebuffer uses full color range,
+ * but the video output doesn't), and as a result lead to CRC and frame dump
+ * comparison mismatches. To workaround this, the caller should force the
+ * connector to use full color ranges by using
+ * #kmstest_set_connector_broadcast_rgb before setting up the display.
+ */
+void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
+ int x, int y, int w, int h, int frame_count)
+{
+ xmlrpc_DECREF(chamelium_rpc(chamelium, port, "CaptureVideo",
+ (w && h) ? "(iiiiii)" : "(iinnnn)",
+ port->id, frame_count, x, y, w, h));
+ chamelium->capturing_port = port;
+}
+
+/**
+ * chamelium_read_captured_crcs:
+ * @chamelium: The Chamelium instance to use
+ * @frame_count: Where to store the number of CRCs we read in
+ *
+ * Reads all of the CRCs that have been captured thus far from the Chamelium.
+ *
+ * Returns: An array of @frame_count length containing all of the CRCs we read
+ */
+igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
+ int *frame_count)
+{
+ igt_crc_t *ret;
+ xmlrpc_value *res, *elem;
+ int i;
+
+ res = chamelium_rpc(chamelium, NULL, "GetCapturedChecksums", "(in)", 0);
+
+ *frame_count = xmlrpc_array_size(&chamelium->env, res);
+ ret = calloc(sizeof(igt_crc_t), *frame_count);
+
+ for (i = 0; i < *frame_count; i++) {
+ xmlrpc_array_read_item(&chamelium->env, res, i, &elem);
+
+ crc_from_xml(chamelium, elem, &ret[i]);
+ ret[i].frame = i;
+
+ xmlrpc_DECREF(elem);
+ }
+
+ xmlrpc_DECREF(res);
+
+ return ret;
+}
+
+/**
+ * chamelium_port_read_captured_frame:
+ *
+ * @chamelium: The Chamelium instance to use
+ * @index: The index of the captured frame we want to get
+ *
+ * Retrieves a single video frame captured during the last video capture on the
+ * Chamelium. This data should be freed using #chamelium_destroy_frame_data
+ *
+ * Returns: a chamelium_frame_dump struct
+ */
+struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
+ unsigned int index)
+{
+ xmlrpc_value *res;
+ struct chamelium_frame_dump *frame;
+
+ res = chamelium_rpc(chamelium, NULL, "ReadCapturedFrame", "(i)", index);
+ frame = frame_from_xml(chamelium, res);
+ xmlrpc_DECREF(res);
+
+ return frame;
+}
+
+/**
+ * chamelium_get_captured_frame_count:
+ * @chamelium: The Chamelium instance to use
+ *
+ * Gets the number of frames that were captured during the last video capture.
+ *
+ * Returns: the number of frames the Chamelium captured during the last video
+ * capture.
+ */
+int chamelium_get_captured_frame_count(struct chamelium *chamelium)
+{
+ xmlrpc_value *res;
+ int ret;
+
+ res = chamelium_rpc(chamelium, NULL, "GetCapturedFrameCount", "()");
+ xmlrpc_read_int(&chamelium->env, res, &ret);
+
+ xmlrpc_DECREF(res);
+ return ret;
+}
+
+static pixman_image_t *convert_frame_format(pixman_image_t *src,
+ int format)
+{
+ pixman_image_t *converted;
+ int w = pixman_image_get_width(src), h = pixman_image_get_height(src);
+
+ converted = pixman_image_create_bits(format, w, h, NULL,
+ PIXMAN_FORMAT_BPP(format) * w);
+ pixman_image_composite(PIXMAN_OP_ADD, src, NULL, converted,
+ 0, 0, 0, 0, 0, 0, w, h);
+
+ return converted;
+}
+
+/**
+ * chamelium_assert_frame_eq:
+ * @chamelium: The chamelium instance the frame dump belongs to
+ * @dump: The chamelium frame dump to check
+ * @fb: The framebuffer to check against
+ *
+ * Asserts that the image contained in the chamelium frame dump is identical to
+ * the given framebuffer. Useful for scenarios where pre-calculating CRCs might
+ * not be ideal.
+ */
+void chamelium_assert_frame_eq(const struct chamelium *chamelium,
+ const struct chamelium_frame_dump *dump,
+ struct igt_fb *fb)
+{
+ cairo_t *cr;
+ cairo_surface_t *fb_surface;
+ pixman_image_t *reference_src, *reference_bgr;
+ int w = dump->width, h = dump->height;
+ bool eq;
+
+ /* Get the cairo surface for the framebuffer */
+ cr = igt_get_cairo_ctx(chamelium->drm_fd, fb);
+ fb_surface = cairo_get_target(cr);
+ cairo_surface_reference(fb_surface);
+ cairo_destroy(cr);
+
+ /*
+ * Convert the reference image into the same format as the chamelium
+ * image
+ */
+ reference_src = pixman_image_create_bits(
+ PIXMAN_x8r8g8b8, w, h,
+ (void*)cairo_image_surface_get_data(fb_surface),
+ cairo_image_surface_get_stride(fb_surface));
+ reference_bgr = convert_frame_format(reference_src, PIXMAN_b8g8r8);
+ pixman_image_unref(reference_src);
+
+ /* Now do the actual comparison */
+ eq = memcmp(dump->bgr, pixman_image_get_data(reference_bgr),
+ dump->size) == 0;
+
+ pixman_image_unref(reference_bgr);
+ cairo_surface_destroy(fb_surface);
+
+ igt_fail_on_f(!eq,
+ "Chamelium frame dump didn't match reference image\n");
+}
+
+/**
+ * chamelium_get_frame_limit:
+ * @chamelium: The Chamelium instance to use
+ * @port: The port to check the frame limit on
+ * @w: The width of the area to get the capture frame limit for, or %0 for the
+ * whole display
+ * @h: The height of the area to get the capture frame limit for, or %0 for the
+ * whole display
+ *
+ * Gets the max number of frames we can capture with the Chamelium for the given
+ * resolution.
+ *
+ * Returns: The number of the max number of frames we can capture
+ */
+int chamelium_get_frame_limit(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int w, int h)
+{
+ xmlrpc_value *res;
+ int ret;
+
+ if (!w && !h)
+ chamelium_port_get_resolution(chamelium, port, &w, &h);
+
+ res = chamelium_rpc(chamelium, port, "GetMaxFrameLimit", "(iii)",
+ port->id, w, h);
+
+ xmlrpc_read_int(&chamelium->env, res, &ret);
+ xmlrpc_DECREF(res);
+
+ return ret;
+}
+
+static unsigned int chamelium_get_port_type(struct chamelium *chamelium,
+ struct chamelium_port *port)
+{
+ xmlrpc_value *res;
+ const char *port_type_str;
+ unsigned int port_type;
+
+ res = chamelium_rpc(chamelium, NULL, "GetConnectorType",
+ "(i)", port->id);
+
+ xmlrpc_read_string(&chamelium->env, res, &port_type_str);
+ igt_debug("Port %d is of type '%s'\n", port->id, port_type_str);
+
+ if (strcmp(port_type_str, "DP") == 0)
+ port_type = DRM_MODE_CONNECTOR_DisplayPort;
+ else if (strcmp(port_type_str, "HDMI") == 0)
+ port_type = DRM_MODE_CONNECTOR_HDMIA;
+ else if (strcmp(port_type_str, "VGA") == 0)
+ port_type = DRM_MODE_CONNECTOR_VGA;
+ else
+ port_type = DRM_MODE_CONNECTOR_Unknown;
+
+ free((void*)port_type_str);
+ xmlrpc_DECREF(res);
+
+ return port_type;
+}
+
+static bool chamelium_read_port_mappings(struct chamelium *chamelium,
+ int drm_fd, GKeyFile *key_file)
+{
+ drmModeRes *res;
+ drmModeConnector *connector;
+ struct chamelium_port *port;
+ GError *error = NULL;
+ char **group_list;
+ char *group, *map_name;
+ int port_i, i, j;
+ bool ret = true;
+
+ group_list = g_key_file_get_groups(key_file, NULL);
+
+ /* Count how many connector mappings are specified in the config */
+ for (i = 0; group_list[i] != NULL; i++) {
+ if (strstr(group_list[i], "Chamelium:"))
+ chamelium->port_count++;
+ }
+
+ chamelium->ports = calloc(sizeof(struct chamelium_port),
+ chamelium->port_count);
+ port_i = 0;
+ res = drmModeGetResources(drm_fd);
+
+ for (i = 0; group_list[i] != NULL; i++) {
+ group = group_list[i];
+
+ if (!strstr(group, "Chamelium:"))
+ continue;
+
+ map_name = group + (sizeof("Chamelium:") - 1);
+
+ port = &chamelium->ports[port_i++];
+ port->name = strdup(map_name);
+ port->id = g_key_file_get_integer(key_file, group,
+ "ChameliumPortID",
+ &error);
+ if (!port->id) {
+ igt_warn("Failed to read chamelium port ID for %s: %s\n",
+ map_name, error->message);
+ ret = false;
+ goto out;
+ }
+
+ port->type = chamelium_get_port_type(chamelium, port);
+ if (port->type == DRM_MODE_CONNECTOR_Unknown) {
+ igt_warn("Unable to retrieve the physical port type from the Chamelium for '%s'\n",
+ map_name);
+ ret = false;
+ goto out;
+ }
+
+ for (j = 0;
+ j < res->count_connectors && !port->connector_id;
+ j++) {
+ char name[50];
+
+ connector = drmModeGetConnectorCurrent(
+ drm_fd, res->connectors[j]);
+
+ /* We have to generate the connector name on our own */
+ snprintf(name, 50, "%s-%u",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id);
+
+ if (strcmp(name, map_name) == 0)
+ port->connector_id = connector->connector_id;
+
+ drmModeFreeConnector(connector);
+ }
+ if (!port->connector_id) {
+ igt_warn("No connector found with name '%s'\n",
+ map_name);
+ ret = false;
+ goto out;
+ }
+
+ igt_debug("Port '%s' with physical type '%s' mapped to Chamelium port %d\n",
+ map_name, kmstest_connector_type_str(port->type),
+ port->id);
+ }
+
+out:
+ drmModeFreeResources(res);
+ g_strfreev(group_list);
+
+ return ret;
+}
+
+static bool chamelium_read_config(struct chamelium *chamelium, int drm_fd)
+{
+ GKeyFile *key_file = g_key_file_new();
+ GError *error = NULL;
+ char *key_file_loc, *key_file_env;
+ int rc;
+ bool ret = true;
+
+ key_file_env = getenv("IGT_CONFIG_PATH");
+ if (key_file_env) {
+ key_file_loc = key_file_env;
+ } else {
+ key_file_loc = malloc(100);
+ snprintf(key_file_loc, 100, "%s/.igtrc", g_get_home_dir());
+ }
+
+ rc = g_key_file_load_from_file(key_file, key_file_loc,
+ G_KEY_FILE_NONE, &error);
+ if (!rc) {
+ igt_warn("Failed to read chamelium configuration file: %s\n",
+ error->message);
+ ret = false;
+ goto out;
+ }
+
+ chamelium->url = g_key_file_get_string(key_file, "Chamelium", "URL",
+ &error);
+ if (!chamelium->url) {
+ igt_warn("Couldn't read chamelium URL from config file: %s\n",
+ error->message);
+ ret = false;
+ goto out;
+ }
+
+ ret = chamelium_read_port_mappings(chamelium, drm_fd, key_file);
+
+out:
+ g_key_file_free(key_file);
+
+ if (!key_file_env)
+ free(key_file_loc);
+
+ return ret;
+}
+
+/**
+ * chamelium_reset:
+ * @chamelium: The Chamelium instance to use
+ *
+ * Resets the chamelium's IO board. As well, this also has the effect of
+ * causing all of the chamelium ports to get set to unplugged
+ */
+void chamelium_reset(struct chamelium *chamelium)
+{
+ igt_debug("Resetting the chamelium\n");
+ xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Reset", "()"));
+}
+
+static void chamelium_exit_handler(int sig)
+{
+ igt_debug("Deinitializing Chamelium\n");
+
+ if (cleanup_instance)
+ chamelium_deinit(cleanup_instance);
+}
+
+/**
+ * chamelium_init:
+ * @chamelium: The Chamelium instance to use
+ * @drm_fd: a display initialized with #igt_display_init
+ *
+ * Sets up a connection with a chamelium, using the URL specified in the
+ * Chamelium configuration. This must be called first before trying to use the
+ * chamelium.
+ *
+ * If we fail to establish a connection with the chamelium, fail to find a
+ * configured connector, etc. we fail the current test.
+ *
+ * Returns: A newly initialized chamelium struct, or NULL on error
+ */
+struct chamelium *chamelium_init(int drm_fd)
+{
+ struct chamelium *chamelium = malloc(sizeof(struct chamelium));
+
+ if (!chamelium)
+ return NULL;
+
+ /* A chamelium instance was set up previously, so clean it up before
+ * starting a new one
+ */
+ if (cleanup_instance)
+ chamelium_deinit(cleanup_instance);
+
+ memset(chamelium, 0, sizeof(*chamelium));
+ chamelium->drm_fd = drm_fd;
+
+ /* Setup the libxmlrpc context */
+ xmlrpc_env_init(&chamelium->env);
+ xmlrpc_client_setup_global_const(&chamelium->env);
+ xmlrpc_client_create(&chamelium->env, XMLRPC_CLIENT_NO_FLAGS, PACKAGE,
+ PACKAGE_VERSION, NULL, 0, &chamelium->client);
+ if (chamelium->env.fault_occurred) {
+ igt_debug("Failed to init xmlrpc: %s\n",
+ chamelium->env.fault_string);
+ goto error;
+ }
+
+ if (!chamelium_read_config(chamelium, drm_fd))
+ goto error;
+
+ chamelium_reset(chamelium);
+
+ cleanup_instance = chamelium;
+ igt_install_exit_handler(chamelium_exit_handler);
+
+ return chamelium;
+
+error:
+ xmlrpc_env_clean(&chamelium->env);
+ free(chamelium);
+
+ return NULL;
+}
+
+/**
+ * chamelium_deinit:
+ * @chamelium: The Chamelium instance to use
+ *
+ * Frees the resources used by a connection to the chamelium that was set up
+ * with #chamelium_init. As well, this function restores the state of the
+ * chamelium like it was before calling #chamelium_init. This function is also
+ * called as an exit handler, so users only need to call manually if they don't
+ * want the chamelium interfering with other tests in the same file.
+ */
+void chamelium_deinit(struct chamelium *chamelium)
+{
+ int i;
+ struct chamelium_edid *pos, *tmp;
+
+ /* We want to make sure we leave all of the ports plugged in, since
+ * testing setups requiring multiple monitors are probably using the
+ * chamelium to provide said monitors
+ */
+ chamelium_reset(chamelium);
+ for (i = 0; i < chamelium->port_count; i++)
+ chamelium_plug(chamelium, &chamelium->ports[i]);
+
+ /* Destroy any EDIDs we created to make sure we don't leak them */
+ igt_list_for_each_safe(pos, tmp, &chamelium->edids->link, link) {
+ chamelium_destroy_edid(chamelium, pos->id);
+ free(pos);
+ }
+
+ xmlrpc_client_destroy(chamelium->client);
+ xmlrpc_env_clean(&chamelium->env);
+
+ for (i = 0; i < chamelium->port_count; i++)
+ free(chamelium->ports[i].name);
+
+ free(chamelium->ports);
+ free(chamelium);
+}
+
+igt_constructor {
+ /* Frame dumps can be large, so we need to be able to handle very large
+ * responses
+ *
+ * Limit here is 10MB
+ */
+ xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 10485760);
+}
diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
new file mode 100644
index 00000000..f421d837
--- /dev/null
+++ b/lib/igt_chamelium.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2016 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors: Lyude Paul <lyude@redhat.com>
+ */
+
+#ifndef IGT_CHAMELIUM_H
+#define IGT_CHAMELIUM_H
+
+#include "config.h"
+#include "igt.h"
+#include <stdbool.h>
+
+struct chamelium;
+struct chamelium_port;
+struct chamelium_frame_dump;
+
+struct chamelium *chamelium_init(int drm_fd);
+void chamelium_deinit(struct chamelium *chamelium);
+void chamelium_reset(struct chamelium *chamelium);
+
+struct chamelium_port **chamelium_get_ports(struct chamelium *chamelium,
+ int *count);
+unsigned int chamelium_port_get_type(const struct chamelium_port *port);
+drmModeConnector *chamelium_port_get_connector(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ bool reprobe);
+const char *chamelium_port_get_name(struct chamelium_port *port);
+
+void chamelium_plug(struct chamelium *chamelium, struct chamelium_port *port);
+void chamelium_unplug(struct chamelium *chamelium, struct chamelium_port *port);
+bool chamelium_is_plugged(struct chamelium *chamelium,
+ struct chamelium_port *port);
+bool chamelium_port_wait_video_input_stable(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int timeout_secs);
+void chamelium_fire_mixed_hpd_pulses(struct chamelium *chamelium,
+ struct chamelium_port *port, ...);
+void chamelium_fire_hpd_pulses(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int width_msec, int count);
+void chamelium_async_hpd_pulse_start(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ bool high, int delay_secs);
+void chamelium_async_hpd_pulse_finish(struct chamelium *chamelium);
+int chamelium_new_edid(struct chamelium *chamelium, const unsigned char *edid);
+void chamelium_port_set_edid(struct chamelium *chamelium,
+ struct chamelium_port *port, int edid_id);
+bool chamelium_port_get_ddc_state(struct chamelium *chamelium,
+ struct chamelium_port *port);
+void chamelium_port_set_ddc_state(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ bool enabled);
+void chamelium_port_get_resolution(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int *x, int *y);
+igt_crc_t *chamelium_get_crc_for_area(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int x, int y, int w, int h);
+void chamelium_start_capture(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int x, int y, int w, int h);
+void chamelium_stop_capture(struct chamelium *chamelium, int frame_count);
+void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
+ int x, int y, int w, int h, int frame_count);
+igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
+ int *frame_count);
+struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
+ unsigned int index);
+struct chamelium_frame_dump *chamelium_port_dump_pixels(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int x, int y,
+ int w, int h);
+int chamelium_get_captured_frame_count(struct chamelium *chamelium);
+int chamelium_get_frame_limit(struct chamelium *chamelium,
+ struct chamelium_port *port,
+ int w, int h);
+
+void chamelium_assert_frame_eq(const struct chamelium *chamelium,
+ const struct chamelium_frame_dump *dump,
+ struct igt_fb *fb);
+void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump);
+
+#endif /* IGT_CHAMELIUM_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 14a41ae3..8930c245 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -62,7 +62,7 @@ AM_CFLAGS = $(DRM_CFLAGS) $(CWARNFLAGS) -Wno-unused-result $(DEBUG_CFLAGS)\
$(LIBUNWIND_CFLAGS) $(WERROR_CFLAGS) \
$(NULL)
-LDADD = ../lib/libintel_tools.la $(GLIB_LIBS)
+LDADD = ../lib/libintel_tools.la $(GLIB_LIBS) $(XMLRPC_LIBS)
AM_CFLAGS += $(CAIRO_CFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS)
AM_LDFLAGS = -Wl,--as-needed
@@ -118,5 +118,8 @@ vc4_wait_bo_CFLAGS = $(AM_CFLAGS) $(DRM_VC4_CFLAGS)
vc4_wait_bo_LDADD = $(LDADD) $(DRM_VC4_LIBS)
vc4_wait_seqno_CFLAGS = $(AM_CFLAGS) $(DRM_VC4_CFLAGS)
vc4_wait_seqno_LDADD = $(LDADD) $(DRM_VC4_LIBS)
+
+chamelium_CFLAGS = $(AM_CFLAGS) $(XMLRPC_CFLAGS) $(LIBUDEV_CFLAGS)
+chamelium_LDADD = $(LDADD) $(XMLRPC_LIBS) $(LIBUDEV_LIBS)
endif
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index 38385aaf..016cd865 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -142,6 +142,7 @@ TESTS_progs_M = \
template \
vgem_basic \
vgem_slow \
+ chamelium \
$(NULL)
TESTS_progs_XM = \
diff --git a/tests/chamelium.c b/tests/chamelium.c
new file mode 100644
index 00000000..d914d5b7
--- /dev/null
+++ b/tests/chamelium.c
@@ -0,0 +1,689 @@
+/*
+ * Copyright © 2016 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Lyude Paul <lyude@redhat.com>
+ */
+
+#include "config.h"
+#include "igt.h"
+
+#include <fcntl.h>
+#include <string.h>
+
+typedef struct {
+ struct chamelium *chamelium;
+ struct chamelium_port **ports;
+ int port_count;
+
+ int drm_fd;
+
+ int edid_id;
+ int alt_edid_id;
+} data_t;
+
+#define HOTPLUG_TIMEOUT 20 /* seconds */
+#define SUSPEND_RESUME_DELAY 20 /* seconds */
+
+/* Pre-calculated CRCs for the pattern fb, for all the modes in the default
+ * chamelium edid
+ */
+struct crc_entry {
+ int width;
+ int height;
+ igt_crc_t crc;
+};
+
+#define CRC_ENTRY(w_, h_, ...) \
+ { w_, h_, { .n_words = 4, .crc = { __VA_ARGS__ } } }
+
+static const struct crc_entry pattern_fb_crcs[] = {
+ CRC_ENTRY(1920, 1080, 0xf859, 0xa751, 0x8c81, 0x45a1),
+ CRC_ENTRY(1280, 720, 0xcec2, 0x4246, 0x6cfd, 0xeb43),
+ CRC_ENTRY(1024, 768, 0x85e5, 0xf0cd, 0xafe3, 0x7f18),
+ CRC_ENTRY( 800, 600, 0x6b39, 0x32b6, 0x831a, 0xb03e),
+ CRC_ENTRY( 640, 480, 0xa121, 0x2473, 0xb150, 0x8c47),
+};
+#undef CRC_ENTRY
+
+static const igt_crc_t *
+get_precalculated_crc(struct chamelium_port *port, int w, int h)
+{
+ int i;
+ const struct crc_entry *entry;
+
+ for (i = 0; i < ARRAY_SIZE(pattern_fb_crcs); i++) {
+ entry = &pattern_fb_crcs[i];
+
+ if (entry->width == w && entry->height == h)
+ return &entry->crc;
+ }
+
+ return NULL;
+}
+
+static void
+require_connector_present(data_t *data, unsigned int type)
+{
+ int i;
+ bool found = false;
+
+ for (i = 0; i < data->port_count && !found; i++) {
+ if (chamelium_port_get_type(data->ports[i]) == type)
+ found = true;
+ }
+
+ igt_require_f(found, "No port of type %s was found\n",
+ kmstest_connector_type_str(type));
+}
+
+static drmModeConnection
+reprobe_connector(data_t *data, struct chamelium_port *port)
+{
+ drmModeConnector *connector;
+ drmModeConnection status;
+
+ igt_debug("Reprobing %s...\n", chamelium_port_get_name(port));
+ connector = chamelium_port_get_connector(data->chamelium, port, true);
+ igt_assert(connector);
+ status = connector->connection;
+
+ drmModeFreeConnector(connector);
+ return status;
+}
+
+static void
+wait_for_connector(data_t *data, struct chamelium_port *port,
+ drmModeConnection status)
+{
+ bool finished = false;
+
+ igt_debug("Waiting for %s to %sconnect...\n",
+ chamelium_port_get_name(port),
+ status == DRM_MODE_DISCONNECTED ? "dis" : "");
+
+ /*
+ * Rely on simple reprobing so we don't fail tests that don't require
+ * that hpd events work in the event that hpd doesn't work on the system
+ */
+ igt_until_timeout(HOTPLUG_TIMEOUT) {
+ if (reprobe_connector(data, port) == status) {
+ finished = true;
+ return;
+ }
+
+ sleep(1);
+ }
+
+ igt_assert(finished);
+}
+
+static void
+reset_state(data_t *data, struct chamelium_port *port)
+{
+ chamelium_reset(data->chamelium);
+ wait_for_connector(data, port, DRM_MODE_DISCONNECTED);
+}
+
+static void
+test_basic_hotplug(data_t *data, struct chamelium_port *port)
+{
+ struct udev_monitor *mon = igt_watch_hotplug();
+ int i;
+
+ reset_state(data, port);
+
+ for (i = 0; i < 15; i++) {
+ igt_flush_hotplugs(mon);
+
+ /* Check if we get a sysfs hotplug event */
+ chamelium_plug(data->chamelium, port);
+ igt_assert(igt_hotplug_detected(mon, HOTPLUG_TIMEOUT));
+ igt_assert_eq(reprobe_connector(data, port),
+ DRM_MODE_CONNECTED);
+
+ igt_flush_hotplugs(mon);
+
+ /* Now check if we get a hotplug from disconnection */
+ chamelium_unplug(data->chamelium, port);
+ igt_assert(igt_hotplug_detected(mon, HOTPLUG_TIMEOUT));
+ igt_assert_eq(reprobe_connector(data, port),
+ DRM_MODE_DISCONNECTED);
+
+ /* Sleep so we don't accidentally cause an hpd storm */
+ usleep(500 * 1000);
+ }
+
+ igt_cleanup_hotplug(mon);
+}
+
+static void
+test_edid_read(data_t *data, struct chamelium_port *port,
+ int edid_id, const unsigned char *edid)
+{
+ drmModePropertyBlobPtr edid_blob = NULL;
+ drmModeConnector *connector = chamelium_port_get_connector(
+ data->chamelium, port, false);
+ uint64_t edid_blob_id;
+
+ reset_state(data, port);
+
+ chamelium_port_set_edid(data->chamelium, port, edid_id);
+ chamelium_plug(data->chamelium, port);
+ wait_for_connector(data, port, DRM_MODE_CONNECTED);
+
+ igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id,
+ DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL,
+ &edid_blob_id, NULL));
+ igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd,
+ edid_blob_id));
+
+ igt_assert(memcmp(edid, edid_blob->data, EDID_LENGTH) == 0);
+
+ drmModeFreePropertyBlob(edid_blob);
+ drmModeFreeConnector(connector);
+}
+
+static void
+test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
+ enum igt_suspend_state state,
+ enum igt_suspend_test test)
+{
+ struct udev_monitor *mon = igt_watch_hotplug();
+
+ reset_state(data, port);
+
+ igt_set_autoresume_delay(SUSPEND_RESUME_DELAY);
+ igt_flush_hotplugs(mon);
+
+ /* Make sure we notice new connectors after resuming */
+ chamelium_async_hpd_pulse_start(data->chamelium, port, false,
+ SUSPEND_RESUME_DELAY / 2);
+ igt_system_suspend_autoresume(state, test);
+ chamelium_async_hpd_pulse_finish(data->chamelium);
+
+ igt_assert(igt_hotplug_detected(mon, HOTPLUG_TIMEOUT));
+ igt_assert_eq(reprobe_connector(data, port), DRM_MODE_CONNECTED);
+
+ igt_flush_hotplugs(mon);
+
+ /* Now make sure we notice disconnected connectors after resuming */
+ chamelium_async_hpd_pulse_start(data->chamelium, port, true,
+ SUSPEND_RESUME_DELAY / 2);
+ igt_system_suspend_autoresume(state, test);
+ chamelium_async_hpd_pulse_finish(data->chamelium);
+
+ igt_assert(igt_hotplug_detected(mon, HOTPLUG_TIMEOUT));
+ igt_assert_eq(reprobe_connector(data, port), DRM_MODE_DISCONNECTED);
+
+ igt_cleanup_hotplug(mon);
+}
+
+static void
+test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
+ enum igt_suspend_state state,
+ enum igt_suspend_test test,
+ int edid_id,
+ int alt_edid_id)
+{
+ struct udev_monitor *mon = igt_watch_hotplug();
+
+ reset_state(data, port);
+
+ /* First plug in the port */
+ chamelium_port_set_edid(data->chamelium, port, edid_id);
+ chamelium_plug(data->chamelium, port);
+ wait_for_connector(data, port, DRM_MODE_CONNECTED);
+
+ igt_flush_hotplugs(mon);
+
+ /*
+ * Change the edid before we suspend. On resume, the machine should
+ * notice the EDID change and fire a hotplug event.
+ */
+ chamelium_port_set_edid(data->chamelium, port, alt_edid_id);
+
+ igt_system_suspend_autoresume(state, test);
+ igt_assert(igt_hotplug_detected(mon, HOTPLUG_TIMEOUT));
+}
+
+static igt_output_t *
+prepare_output(data_t *data,
+ igt_display_t *display,
+ struct chamelium_port *port)
+{
+ igt_output_t *output;
+ drmModeRes *res;
+ drmModeConnector *connector =
+ chamelium_port_get_connector(data->chamelium, port, false);
+
+ chamelium_reset(data->chamelium);
+
+ igt_assert(res = drmModeGetResources(data->drm_fd));
+ kmstest_unset_all_crtcs(data->drm_fd, res);
+
+ /* The chamelium's default EDID has a lot of resolutions, way more then
+ * we need to test
+ */
+ chamelium_port_set_edid(data->chamelium, port, data->edid_id);
+
+ chamelium_plug(data->chamelium, port);
+ wait_for_connector(data, port, DRM_MODE_CONNECTED);
+
+ igt_display_init(display, data->drm_fd);
+ output = igt_output_from_connector(display, connector);
+
+ igt_assert(kmstest_probe_connector_config(
+ data->drm_fd, connector->connector_id, ~0, &output->config));
+ igt_output_set_pipe(output, output->config.pipe);
+
+ drmModeFreeConnector(connector);
+ drmModeFreeResources(res);
+
+ return output;
+}
+
+static void
+enable_output(data_t *data,
+ struct chamelium_port *port,
+ igt_output_t *output,
+ drmModeModeInfo *mode,
+ struct igt_fb *fb)
+{
+ igt_display_t *display = output->display;
+ igt_plane_t *primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
+ drmModeConnector *connector = chamelium_port_get_connector(
+ data->chamelium, port, false);
+
+ igt_assert(primary);
+
+ igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay);
+ igt_plane_set_fb(primary, fb);
+ igt_output_override_mode(output, mode);
+
+ /* Clear any color correction values that might be enabled */
+ igt_pipe_set_degamma_lut(primary->pipe, NULL, 0);
+ igt_pipe_set_gamma_lut(primary->pipe, NULL, 0);
+ igt_pipe_set_ctm_matrix(primary->pipe, NULL, 0);
+
+ kmstest_set_connector_broadcast_rgb(display->drm_fd, connector,
+ BROADCAST_RGB_FULL);
+
+ igt_display_commit(display);
+ chamelium_port_wait_video_input_stable(data->chamelium, port,
+ HOTPLUG_TIMEOUT);
+
+ drmModeFreeConnector(connector);
+}
+
+static void
+disable_output(data_t *data,
+ struct chamelium_port *port,
+ igt_output_t *output)
+{
+ igt_display_t *display = output->display;
+ igt_plane_t *primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
+ igt_assert(primary);
+
+ /* Disable the display */
+ igt_plane_set_fb(primary, NULL);
+ igt_display_commit(display);
+}
+
+static void
+test_display_crc_single(data_t *data, struct chamelium_port *port)
+{
+ igt_display_t display;
+ igt_output_t *output;
+ igt_plane_t *primary;
+ igt_crc_t *crc;
+ const igt_crc_t *expected_crc;
+ struct igt_fb fb;
+ drmModeModeInfo *mode;
+ drmModeConnector *connector;
+ int fb_id, i;
+
+ output = prepare_output(data, &display, port);
+ connector = chamelium_port_get_connector(data->chamelium, port, false);
+ primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
+ igt_assert(primary);
+
+ for (i = 0; i < connector->count_modes; i++) {
+ mode = &connector->modes[i];
+ fb_id = igt_create_color_pattern_fb(data->drm_fd,
+ mode->hdisplay,
+ mode->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ LOCAL_DRM_FORMAT_MOD_NONE,
+ 0, 0, 0, &fb);
+ igt_assert(fb_id > 0);
+
+ enable_output(data, port, output, mode, &fb);
+
+ expected_crc = get_precalculated_crc(port,
+ mode->hdisplay,
+ mode->vdisplay);
+ if (!expected_crc) {
+ igt_warn("No precalculated CRC found for %dx%d, skipping CRC check\n",
+ mode->hdisplay, mode->vdisplay);
+ goto next;
+ }
+
+ igt_debug("Testing single CRC fetch\n");
+ crc = chamelium_get_crc_for_area(data->chamelium, port,
+ 0, 0, 0, 0);
+ igt_assert_crc_equal(crc, expected_crc);
+ free(crc);
+
+next:
+ disable_output(data, port, output);
+ igt_remove_fb(data->drm_fd, &fb);
+ }
+
+ drmModeFreeConnector(connector);
+ igt_display_fini(&display);
+}
+
+static void
+test_display_crc_multiple(data_t *data, struct chamelium_port *port)
+{
+ igt_display_t display;
+ igt_output_t *output;
+ igt_plane_t *primary;
+ igt_crc_t *crc;
+ const igt_crc_t *expected_crc;
+ struct igt_fb fb;
+ drmModeModeInfo *mode;
+ drmModeConnector *connector;
+ int fb_id, i, j, captured_frame_count;
+
+ output = prepare_output(data, &display, port);
+ connector = chamelium_port_get_connector(data->chamelium, port, false);
+ primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
+ igt_assert(primary);
+
+ for (i = 0; i < connector->count_modes; i++) {
+ mode = &connector->modes[i];
+ fb_id = igt_create_color_pattern_fb(data->drm_fd,
+ mode->hdisplay,
+ mode->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ LOCAL_DRM_FORMAT_MOD_NONE,
+ 0, 0, 0, &fb);
+ igt_assert(fb_id > 0);
+
+ enable_output(data, port, output, mode, &fb);
+
+ expected_crc = get_precalculated_crc(port, mode->hdisplay,
+ mode->vdisplay);
+ if (!expected_crc) {
+ igt_warn("No precalculated CRC found for %dx%d, skipping CRC check\n",
+ mode->hdisplay, mode->vdisplay);
+ goto next;
+ }
+
+ /* 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, 3);
+ crc = chamelium_read_captured_crcs(data->chamelium,
+ &captured_frame_count);
+
+ igt_debug("Captured %d frames\n", captured_frame_count);
+ for (j = 0; j < captured_frame_count; j++)
+ igt_assert_crc_equal(&crc[j], expected_crc);
+ free(crc);
+
+next:
+ disable_output(data, port, output);
+ igt_remove_fb(data->drm_fd, &fb);
+ }
+
+ drmModeFreeConnector(connector);
+ igt_display_fini(&display);
+}
+
+static void
+test_display_frame_dump(data_t *data, struct chamelium_port *port)
+{
+ igt_display_t display;
+ igt_output_t *output;
+ igt_plane_t *primary;
+ struct igt_fb fb;
+ struct chamelium_frame_dump *frame;
+ drmModeModeInfo *mode;
+ drmModeConnector *connector;
+ int fb_id, i, j;
+
+ output = prepare_output(data, &display, port);
+ connector = chamelium_port_get_connector(data->chamelium, port, false);
+ primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
+ igt_assert(primary);
+
+ for (i = 0; i < connector->count_modes; i++) {
+ mode = &connector->modes[i];
+ fb_id = igt_create_color_pattern_fb(data->drm_fd,
+ mode->hdisplay, mode->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ LOCAL_DRM_FORMAT_MOD_NONE,
+ 0, 0, 0, &fb);
+ igt_assert(fb_id > 0);
+
+ enable_output(data, port, output, mode, &fb);
+
+ igt_debug("Reading frame dumps from Chamelium...\n");
+ chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5);
+ for (j = 0; j < 5; j++) {
+ frame = chamelium_read_captured_frame(
+ data->chamelium, j);
+ chamelium_assert_frame_eq(data->chamelium, frame, &fb);
+ chamelium_destroy_frame_dump(frame);
+ }
+
+ disable_output(data, port, output);
+ igt_remove_fb(data->drm_fd, &fb);
+ }
+
+ drmModeFreeConnector(connector);
+ igt_display_fini(&display);
+}
+
+static void
+test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
+{
+ struct udev_monitor *mon = igt_watch_hotplug();
+
+ reset_state(data, port);
+
+ /* 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, HOTPLUG_TIMEOUT));
+ igt_assert_eq(reprobe_connector(data, port), DRM_MODE_CONNECTED);
+
+ igt_cleanup_hotplug(mon);
+}
+
+#define for_each_port(p, port) \
+ for (p = 0, port = data.ports[p]; \
+ p < data.port_count; \
+ p++, port = data.ports[p])
+
+#define connector_subtest(name__, type__) \
+ igt_subtest(name__) \
+ for_each_port(p, port) \
+ if (chamelium_port_get_type(port) == \
+ DRM_MODE_CONNECTOR_ ## type__)
+
+static data_t data;
+
+igt_main
+{
+ struct chamelium_port *port;
+ int edid_id, alt_edid_id, p;
+
+ igt_fixture {
+ igt_skip_on_simulation();
+
+ data.drm_fd = drm_open_driver_master(DRIVER_ANY);
+ data.chamelium = chamelium_init(data.drm_fd);
+ igt_require(data.chamelium);
+
+ data.ports = chamelium_get_ports(data.chamelium,
+ &data.port_count);
+
+ edid_id = chamelium_new_edid(data.chamelium,
+ igt_kms_get_base_edid());
+ alt_edid_id = chamelium_new_edid(data.chamelium,
+ igt_kms_get_alt_edid());
+ data.edid_id = edid_id;
+ data.alt_edid_id = alt_edid_id;
+
+ /* So fbcon doesn't try to reprobe things itself */
+ kmstest_set_vt_graphics_mode();
+ }
+
+ igt_subtest_group {
+ igt_fixture {
+ require_connector_present(
+ &data, DRM_MODE_CONNECTOR_DisplayPort);
+ }
+
+ connector_subtest("dp-hpd", DisplayPort)
+ test_basic_hotplug(&data, port);
+
+ connector_subtest("dp-edid-read", DisplayPort) {
+ test_edid_read(&data, port, edid_id,
+ igt_kms_get_base_edid());
+ test_edid_read(&data, port, alt_edid_id,
+ igt_kms_get_alt_edid());
+ }
+
+ connector_subtest("dp-hpd-after-suspend", DisplayPort)
+ test_suspend_resume_hpd(&data, port,
+ SUSPEND_STATE_MEM,
+ SUSPEND_TEST_NONE);
+
+ connector_subtest("dp-hpd-after-hibernate", DisplayPort)
+ test_suspend_resume_hpd(&data, port,
+ SUSPEND_STATE_DISK,
+ SUSPEND_TEST_DEVICES);
+
+ connector_subtest("dp-edid-change-during-suspend", DisplayPort)
+ test_suspend_resume_edid_change(&data, port,
+ SUSPEND_STATE_MEM,
+ SUSPEND_TEST_NONE,
+ edid_id, alt_edid_id);
+
+ connector_subtest("dp-edid-change-during-hibernate", DisplayPort)
+ test_suspend_resume_edid_change(&data, port,
+ SUSPEND_STATE_DISK,
+ SUSPEND_TEST_DEVICES,
+ edid_id, alt_edid_id);
+
+ connector_subtest("dp-crc-single", DisplayPort)
+ test_display_crc_single(&data, port);
+
+ connector_subtest("dp-crc-multiple", DisplayPort)
+ test_display_crc_multiple(&data, port);
+
+ connector_subtest("dp-frame-dump", DisplayPort)
+ test_display_frame_dump(&data, port);
+ }
+
+ igt_subtest_group {
+ igt_fixture {
+ require_connector_present(
+ &data, DRM_MODE_CONNECTOR_HDMIA);
+ }
+
+ connector_subtest("hdmi-hpd", HDMIA)
+ test_basic_hotplug(&data, port);
+
+ connector_subtest("hdmi-edid-read", HDMIA) {
+ test_edid_read(&data, port, edid_id,
+ igt_kms_get_base_edid());
+ test_edid_read(&data, port, alt_edid_id,
+ igt_kms_get_alt_edid());
+ }
+
+ connector_subtest("hdmi-hpd-after-suspend", HDMIA)
+ test_suspend_resume_hpd(&data, port,
+ SUSPEND_STATE_MEM,
+ SUSPEND_TEST_NONE);
+
+ connector_subtest("hdmi-hpd-after-hibernate", HDMIA)
+ test_suspend_resume_hpd(&data, port,
+ SUSPEND_STATE_DISK,
+ SUSPEND_TEST_DEVICES);
+
+ connector_subtest("hdmi-edid-change-during-suspend", HDMIA)
+ test_suspend_resume_edid_change(&data, port,
+ SUSPEND_STATE_MEM,
+ SUSPEND_TEST_NONE,
+ edid_id, alt_edid_id);
+
+ connector_subtest("hdmi-edid-change-during-hibernate", HDMIA)
+ test_suspend_resume_edid_change(&data, port,
+ SUSPEND_STATE_DISK,
+ SUSPEND_TEST_DEVICES,
+ edid_id, alt_edid_id);
+
+ connector_subtest("hdmi-crc-single", HDMIA)
+ test_display_crc_single(&data, port);
+
+ connector_subtest("hdmi-crc-multiple", HDMIA)
+ test_display_crc_multiple(&data, port);
+
+ connector_subtest("hdmi-frame-dump", HDMIA)
+ test_display_frame_dump(&data, port);
+ }
+
+ igt_subtest_group {
+ igt_fixture {
+ require_connector_present(
+ &data, DRM_MODE_CONNECTOR_VGA);
+ }
+
+ connector_subtest("vga-hpd", VGA)
+ test_basic_hotplug(&data, port);
+
+ connector_subtest("vga-edid-read", VGA) {
+ test_edid_read(&data, port, edid_id,
+ igt_kms_get_base_edid());
+ test_edid_read(&data, port, alt_edid_id,
+ igt_kms_get_alt_edid());
+ }
+
+ /* FIXME: Right now there isn't a way to do any sort of delayed
+ * psuedo-hotplug with VGA, so testing detection after a
+ * suspend/resume cycle isn't possible yet
+ */
+
+ connector_subtest("vga-hpd-without-ddc", VGA)
+ test_hpd_without_ddc(&data, port);
+ }
+}