[Intel-gfx] [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium

Lyude Paul lyude at redhat.com
Mon Nov 14 16:46:01 UTC 2016


Well I'm definitely in agreement with the idea of using config files
for this. Would be a lot more reliable then these tricks. Will respin
with this added

On Mon, 2016-11-14 at 08:05 +0100, Daniel Vetter wrote:
> On Mon, Nov 07, 2016 at 07:05:16PM -0500, Lyude wrote:
> > 
> > For the purpose of testing things such as hotplugging and bad
> > monitors,
> > the ChromeOS team ended up designing a neat little device known as
> > the
> > Chamelium. More information on this can be found here:
> > 
> > 	https://www.chromium.org/chromium-os/testing/chamelium
> > 
> > This adds support for a couple of things to intel-gpu-tools:
> >  - igt library functions for connecting to udev and monitoring it
> > for
> >    hotplug events, loosely based off of the unfinished hotplugging
> >    implementation in testdisplay
> >  - Library functions for controlling the chamelium in tests using
> >    xmlrpc. A couple of RPC calls were ommitted here, mainly because
> > they
> >    didn't seem very useful for our needs or because they're just
> > plain
> >    broken
> >  - A set of basic tests using the chamelium.
> > 
> > Because there's no surefire way that I know of where we can map
> > which
> > chamelium port belongs to which port on the system being tested (we
> > could just use hotplugging, but then we'd be relying on something
> > that
> > might be broken on the machine and potentially give false positives
> > for
> > certain tests), most of the chamelium tests will figure out whether
> > or
> > not a connection happened by counting the number of connectors
> > matching
> > the status we're looking for before hotplugging with the chamelium,
> > vs.
> > after hotplugging it.
> > 
> > Tests which require that we know which port belongs to a certain
> > port
> > (such as ones where we actually perform a modeset) will unplug all
> > of
> > the chamelium ports, plug the desired port, then use the first DRM
> > connector with the desired connector type that's marked as
> > connected. In
> > order to ensure we don't end up using the wrong connector, these
> > tests
> > will skip if they find any connectors with the desired type marked
> > as
> > connected before performing the hotplug on the chamelium.
> > 
> > Running these tests requires (of course) a working Chamelium, along
> > with
> > the RPC URL for the chamelium being specified in the environment
> > variable CHAMELIUM_HOST. If no URL is specified, the tests will
> > just
> > skip on their own. As well, tests for connectors which are not
> > actually
> > present on the system or the chamelium will skip on their own as
> > well.
> > 
> > Signed-off-by: Lyude <lyude at redhat.com>
> > ---
> >  configure.ac           |  13 +
> >  lib/Makefile.am        |  10 +-
> >  lib/igt.h              |   1 +
> >  lib/igt_chamelium.c    | 628
> > +++++++++++++++++++++++++++++++++++++++++++++++++
> >  lib/igt_chamelium.h    |  77 ++++++
> 
> Since you typed these nice gtkdocs, please also add it to the .xml in
> docs/ and make sure it looks all good (./autogen.sh --enable-gtk-
> docs).
> 
> Wrt the api itself I think all we need is agreement from Tomeu that
> this
> is the right thing for his chamelium use-cases, too. And Tomeu has
> commit
> rights, so can push this stuff for you.
> -Daniel
> 
> 
> > 
> >  lib/igt_kms.c          | 107 +++++++++
> >  lib/igt_kms.h          |  13 +-
> >  scripts/run-tests.sh   |   4 +-
> >  tests/Makefile.am      |   5 +-
> >  tests/Makefile.sources |   1 +
> >  tests/chamelium.c      | 549
> > ++++++++++++++++++++++++++++++++++++++++++
> >  11 files changed, 1403 insertions(+), 5 deletions(-)
> >  create mode 100644 lib/igt_chamelium.c
> >  create mode 100644 lib/igt_chamelium.h
> >  create mode 100644 tests/chamelium.c
> > 
> > diff --git a/configure.ac b/configure.ac
> > index 735cfd5..88113b2 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -259,6 +259,18 @@ if test "x$with_libunwind" = xyes; then
> >  			  AC_MSG_ERROR([libunwind not found. Use
> > --without-libunwind to disable libunwind support.]))
> >  fi
> >  
> > +# enable support for using the chamelium
> > +AC_ARG_ENABLE(chamelium,
> > +	      AS_HELP_STRING([--without-chamelium],
> > +			     [Build tests without chamelium
> > support]),
> > +	      [], [with_chamelium=yes])
> > +
> > +AM_CONDITIONAL(HAVE_CHAMELIUM, [test "x$with_chamelium" = xyes])
> > +if test "x$with_chamelium" = xyes; then
> > +	AC_DEFINE(HAVE_CHAMELIUM, 1, [chamelium suport])
> > +	PKG_CHECK_MODULES(XMLRPC, xmlrpc_client)
> > +fi
> > +
> >  # enable debug symbols
> >  AC_ARG_ENABLE(debug,
> >  	      AS_HELP_STRING([--disable-debug],
> > @@ -356,6 +368,7 @@ echo "       Assembler          :
> > ${enable_assembler}"
> >  echo "       Debugger           : ${enable_debugger}"
> >  echo "       Overlay            : X: ${enable_overlay_xlib}, Xv:
> > ${enable_overlay_xvlib}"
> >  echo "       x86-specific tools : ${build_x86}"
> > +echo "       Chamelium support  : ${with_chamelium}"
> >  echo ""
> >  echo " • API-Documentation      : ${enable_gtk_doc}"
> >  echo " • Fail on warnings       : ${enable_werror}"
> > diff --git a/lib/Makefile.am b/lib/Makefile.am
> > index 4c0893d..aeac43a 100644
> > --- a/lib/Makefile.am
> > +++ b/lib/Makefile.am
> > @@ -22,8 +22,14 @@ if !HAVE_LIBDRM_INTEL
> >          stubs/drm/intel_bufmgr.h
> >  endif
> >  
> > +if HAVE_CHAMELIUM
> > +    libintel_tools_la_SOURCES +=	\
> > +	igt_chamelium.c			\
> > +	igt_chamelium.h
> > +endif
> > +
> >  AM_CPPFLAGS = -I$(top_srcdir)
> > -AM_CFLAGS = $(CWARNFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS)
> > $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) \
> > +AM_CFLAGS = $(CWARNFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS)
> > $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) $(XMLRPC_CFLAGS) $(UDEV_CFLAGS)
> > \
> >  	    -DIGT_SRCDIR=\""$(abs_top_srcdir)/tests"\" \
> >  	    -DIGT_DATADIR=\""$(pkgdatadir)"\" \
> >  	    -DIGT_LOG_DOMAIN=\""$(subst _,-,$*)"\" \
> > @@ -38,5 +44,7 @@ libintel_tools_la_LIBADD = \
> >  	$(LIBUDEV_LIBS) \
> >  	$(LIBUNWIND_LIBS) \
> >  	$(TIMER_LIBS) \
> > +	$(XMLRPC_LIBS) \
> > +	$(UDEV_LIBS) \
> >  	-lm
> >  
> > diff --git a/lib/igt.h b/lib/igt.h
> > index d751f24..0ea03e4 100644
> > --- a/lib/igt.h
> > +++ b/lib/igt.h
> > @@ -30,6 +30,7 @@
> >  #include "igt_aux.h"
> >  #include "igt_core.h"
> >  #include "igt_core.h"
> > +#include "igt_chamelium.h"
> >  #include "igt_debugfs.h"
> >  #include "igt_draw.h"
> >  #include "igt_fb.h"
> > diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
> > new file mode 100644
> > index 0000000..a281ef6
> > --- /dev/null
> > +++ b/lib/igt_chamelium.c
> > @@ -0,0 +1,628 @@
> > +/*
> > + * 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 at redhat.com>
> > + */
> > +
> > +#include "config.h"
> > +
> > +#include <string.h>
> > +#include <errno.h>
> > +#include <xmlrpc-c/base.h>
> > +#include <xmlrpc-c/client.h>
> > +
> > +#include "igt.h"
> > +
> > +#define check_rpc() \
> > +	igt_assert_f(!env.fault_occurred, "Chamelium RPC call
> > failed: %s\n", \
> > +		     env.fault_string);
> > +
> > +/**
> > + * chamelium_ports:
> > + *
> > + * Contains information on all of the ports that are physically
> > connected from
> > + * the chamelium to the system. This information is initialized
> > when
> > + * #chamelium_init is called.
> > + */
> > +struct chamelium_port *chamelium_ports;
> > +
> > +/**
> > + * chamelium_port_count:
> > + *
> > + * How many ports are physically connected from the chamelium to
> > the system.
> > + */
> > +int chamelium_port_count;
> > +
> > +static const char *chamelium_url;
> > +static xmlrpc_env env;
> > +
> > +struct chamelium_edid {
> > +	int id;
> > +	struct igt_list link;
> > +};
> > +struct chamelium_edid *allocated_edids;
> > +
> > +/**
> > + * chamelium_plug:
> > + * @id: The ID of the port on the chamelium to plug in
> > + *
> > + * Simulate a display connector being plugged into the system
> > using the
> > + * chamelium.
> > + */
> > +void chamelium_plug(int id)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	igt_debug("Plugging port %d\n", id);
> > +	res = xmlrpc_client_call(&env, chamelium_url, "Plug",
> > "(i)", id);
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_unplug:
> > + * @id: The ID of the port on the chamelium to unplug
> > + *
> > + * Simulate a display connector being unplugged from the system
> > using the
> > + * chamelium.
> > + */
> > +void chamelium_unplug(int id)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	igt_debug("Unplugging port %d\n", id);
> > +	res = xmlrpc_client_call(&env, chamelium_url, "Unplug",
> > "(i)", id);
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_is_plugged:
> > + * @id: The ID of 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(int id)
> > +{
> > +	xmlrpc_value *res;
> > +	xmlrpc_bool is_plugged;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url, "IsPlugged",
> > "(i)", id);
> > +	check_rpc();
> > +
> > +	xmlrpc_read_bool(&env, res, &is_plugged);
> > +	xmlrpc_DECREF(res);
> > +
> > +	return is_plugged;
> > +}
> > +
> > +/**
> > + * chamelium_port_wait_video_input_stable:
> > + * @id: The ID of 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(int id, int
> > timeout_secs)
> > +{
> > +	xmlrpc_value *res;
> > +	xmlrpc_bool is_on;
> > +
> > +	igt_debug("Waiting for video input to stabalize on port
> > %d\n", id);
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "WaitVideoInputStable",
> > +				 "(ii)", id, timeout_secs);
> > +	check_rpc();
> > +
> > +	xmlrpc_read_bool(&env, res, &is_on);
> > +	xmlrpc_DECREF(res);
> > +
> > +	return is_on;
> > +}
> > +
> > +/**
> > + * chamelium_fire_hpd_pulses:
> > + * @id: The ID of the port to fire hotplug 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(int port, int width_msec, int
> > count)
> > +{
> > +	xmlrpc_value *pulse_widths = xmlrpc_array_new(&env),
> > +		     *width = xmlrpc_int_new(&env, width_msec),
> > *res;
> > +	int i;
> > +
> > +	igt_debug("Firing %d HPD pulses with width of %d msec on
> > port %d\n",
> > +		  count, width_msec, port);
> > +
> > +	for (i = 0; i < count; i++)
> > +		xmlrpc_array_append_item(&env, pulse_widths,
> > width);
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "FireMixedHpdPulses",
> > +				 "(iA)", port, pulse_widths);
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +	xmlrpc_DECREF(width);
> > +	xmlrpc_DECREF(pulse_widths);
> > +}
> > +
> > +/**
> > + * chamelium_fire_mixed_hpd_pulses:
> > + * @id: The ID of the port to fire hotplug 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(int id, ...)
> > +{
> > +	va_list args;
> > +	xmlrpc_value *pulse_widths = xmlrpc_array_new(&env),
> > *width, *res;
> > +	int arg;
> > +
> > +	igt_debug("Firing mixed HPD pulses on port %d\n", id);
> > +
> > +	va_start(args, id);
> > +	for (arg = va_arg(args, int); arg; arg = va_arg(args,
> > int)) {
> > +		width = xmlrpc_int_new(&env, arg);
> > +		xmlrpc_array_append_item(&env, pulse_widths,
> > width);
> > +		xmlrpc_DECREF(width);
> > +	}
> > +	va_end(args);
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "FireMixedHpdPulses",
> > +				 "(iA)", id, pulse_widths);
> > +	check_rpc();
> > +	xmlrpc_DECREF(res);
> > +
> > +	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:
> > + * @id: The ID of the port to fire a hotplug pulse 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(int id, bool high, int
> > delay_secs)
> > +{
> > +	xmlrpc_value *pulse_widths = xmlrpc_array_new(&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 port %d with %d
> > second delay\n",
> > +		  high ? "high->low" : "low->high", id,
> > 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(&env, 0);
> > +		xmlrpc_array_append_item(&env, pulse_widths,
> > width);
> > +		xmlrpc_DECREF(width);
> > +	}
> > +
> > +	width = xmlrpc_int_new(&env, delay_secs * 1000);
> > +	xmlrpc_array_append_item(&env, pulse_widths, width);
> > +	xmlrpc_DECREF(width);
> > +
> > +	xmlrpc_client_call_asynch(chamelium_url,
> > "FireMixedHpdPulses",
> > +				  async_rpc_handler, NULL, "(iA)",
> > +				  id, pulse_widths);
> > +	xmlrpc_DECREF(pulse_widths);
> > +}
> > +
> > +/**
> > + * chamelium_async_hpd_pulse_finish:
> > + *
> > + * 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(void)
> > +{
> > +	xmlrpc_client_event_loop_finish_asynch();
> > +}
> > +
> > +/**
> > + * chamelium_new_edid:
> > + * @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(const unsigned char *edid)
> > +{
> > +	xmlrpc_value *res;
> > +	struct chamelium_edid *allocated_edid;
> > +	int edid_id;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "CreateEdid",
> > +				 "(6)", edid, EDID_LENGTH);
> > +	check_rpc();
> > +
> > +	xmlrpc_read_int(&env, res, &edid_id);
> > +	xmlrpc_DECREF(res);
> > +
> > +	allocated_edid = malloc(sizeof(struct chamelium_edid));
> > +	igt_assert(allocated_edid);
> > +
> > +	allocated_edid->id = edid_id;
> > +	if (allocated_edids) {
> > +		igt_list_insert(&allocated_edids->link,
> > &allocated_edid->link);
> > +	} else {
> > +		igt_list_init(&allocated_edid->link);
> > +		allocated_edids = allocated_edid;
> > +	}
> > +
> > +	return edid_id;
> > +}
> > +
> > +static void chamelium_destroy_edid(int edid_id)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "DestroyEdid",
> > +				 "(i)", edid_id);
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_port_set_edid:
> > + * @id: The ID of the port 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(int id, int edid_id)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url, "ApplyEdid",
> > +				 "(ii)", id, edid_id);
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_port_set_ddc_state:
> > + * @id: The ID of the port whose DDC bus we want to modify
> > + * @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(int port, bool enabled)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	igt_debug("%sabling DDC bus on port %d\n",
> > +		  enabled ? "En" : "Dis", port);
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "SetDdcState",
> > +				 "(ib)", port, enabled);
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_port_get_ddc_state:
> > + * @id: The ID of the port whose DDC bus we want 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(int id)
> > +{
> > +	xmlrpc_value *res;
> > +	xmlrpc_bool enabled;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "IsDdcEnabled",
> > +				 "(i)", id);
> > +	check_rpc();
> > +
> > +	xmlrpc_read_bool(&env, res, &enabled);
> > +
> > +	xmlrpc_DECREF(res);
> > +	return enabled;
> > +}
> > +
> > +/**
> > + * chamelium_port_get_resolution:
> > + * @id: The ID of the port whose display resolution we want 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(int id, int *x, int *y)
> > +{
> > +	xmlrpc_value *res, *res_x, *res_y;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "DetectResolution",
> > +				 "(i)", id);
> > +	check_rpc();
> > +
> > +	xmlrpc_array_read_item(&env, res, 0, &res_x);
> > +	xmlrpc_array_read_item(&env, res, 1, &res_y);
> > +	xmlrpc_read_int(&env, res_x, x);
> > +	xmlrpc_read_int(&env, res_y, y);
> > +
> > +	xmlrpc_DECREF(res_x);
> > +	xmlrpc_DECREF(res_y);
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_get_crc_for_area:
> > + * @id: The ID of the port from which we want to retrieve the CRC
> > + * @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
> > + * @h: The height of the area to fetch the CRC from
> > + *
> > + * 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.
> > + *
> > + * Returns: The CRC read back from the chamelium
> > + */
> > +unsigned int chamelium_get_crc_for_area(int id, int x, int y, int
> > w, int h)
> > +{
> > +	xmlrpc_value *res;
> > +	unsigned int crc;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "ComputePixelChecksum",
> > +				 "(iiiii)", id, x, y, w, h);
> > +	check_rpc();
> > +
> > +	xmlrpc_read_int(&env, res, (int*)(&crc));
> > +
> > +	xmlrpc_DECREF(res);
> > +	return crc;
> > +}
> > +
> > +static unsigned int chamelium_get_port_type(int port)
> > +{
> > +	xmlrpc_value *res;
> > +	const char *port_type_str;
> > +	unsigned int port_type;
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "GetConnectorType",
> > +				 "(i)", port);
> > +	check_rpc();
> > +
> > +	xmlrpc_read_string(&env, res, &port_type_str);
> > +	igt_debug("Port %d is of type '%s'\n", port,
> > 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 void chamelium_probe_ports(void)
> > +{
> > +	xmlrpc_value *res, *port_val;
> > +	struct chamelium_port *port;
> > +	unsigned int port_type;
> > +	int id, i, len;
> > +
> > +	/* Figure out what ports are connected, along with their
> > types */
> > +	res = xmlrpc_client_call(&env, chamelium_url,
> > "ProbeInputs", "()");
> > +	check_rpc();
> > +
> > +	len = xmlrpc_array_size(&env, res);
> > +	chamelium_ports = calloc(sizeof(struct chamelium_port),
> > len);
> > +
> > +	igt_assert(chamelium_ports);
> > +
> > +	for (i = 0; i < len; i++) {
> > +		xmlrpc_array_read_item(&env, res, i, &port_val);
> > +		xmlrpc_read_int(&env, port_val, &id);
> > +		xmlrpc_DECREF(port_val);
> > +
> > +		port_type = chamelium_get_port_type(id);
> > +		if (port_type == DRM_MODE_CONNECTOR_Unknown)
> > +			continue;
> > +
> > +		port = &chamelium_ports[chamelium_port_count];
> > +		port->id = id;
> > +		port->type = port_type;
> > +		port->original_plugged = chamelium_is_plugged(id);
> > +		chamelium_port_count++;
> > +	}
> > +
> > +	chamelium_ports = realloc(chamelium_ports,
> > +				  sizeof(struct chamelium_port) *
> > +				  chamelium_port_count);
> > +	igt_assert(chamelium_ports);
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +/**
> > + * chamelium_reset:
> > + *
> > + * 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(void)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	igt_debug("Resetting the chamelium\n");
> > +
> > +	res = xmlrpc_client_call(&env, chamelium_url, "Reset",
> > "()");
> > +	check_rpc();
> > +
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +static void chamelium_exit_handler(int sig)
> > +{
> > +	chamelium_deinit();
> > +}
> > +
> > +/**
> > + * chamelium_init:
> > + *
> > + * Sets up a connection with a chamelium, using the url provided
> > in the
> > + * CHAMELIUM_HOST enviornment variable. This must be called first
> > before trying
> > + * to use the chamelium. When the connection is no longer needed,
> > the user
> > + * should call #chamelium_deinit to free the resources used by the
> > connection.
> > + *
> > + * If we fail to establish a connection with the chamelium, we
> > fail the current
> > + * test.
> > + */
> > +void chamelium_init(void)
> > +{
> > +	chamelium_url = getenv("CHAMELIUM_HOST");
> > +	igt_assert(chamelium_url != NULL);
> > +
> > +	xmlrpc_env_init(&env);
> > +
> > +	xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, PACKAGE,
> > +			    PACKAGE_VERSION, NULL, 0);
> > +	igt_fail_on_f(env.fault_occurred,
> > +		      "Failed to init xmlrpc: %s\n",
> > +		      env.fault_string);
> > +
> > +	chamelium_probe_ports();
> > +	chamelium_reset();
> > +
> > +	igt_install_exit_handler(chamelium_exit_handler);
> > +}
> > +
> > +/**
> > + * chamelium_deinit:
> > + *
> > + * 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(void)
> > +{
> > +	int i;
> > +	struct chamelium_edid *pos, *tmp;
> > +
> > +	if (!chamelium_url)
> > +		return;
> > +
> > +	/* Restore the original state of all of the chamelium
> > ports */
> > +	igt_debug("Restoring original state of chamelium\n");
> > +	chamelium_reset();
> > +	for (i = 0; i < chamelium_port_count; i++) {
> > +		if (chamelium_ports[i].original_plugged)
> > +			chamelium_plug(chamelium_ports[i].id);
> > +	}
> > +
> > +	/* Destroy any EDIDs we created to make sure we don't leak
> > them */
> > +	igt_list_for_each_safe(pos, tmp, &allocated_edids->link,
> > link) {
> > +		chamelium_destroy_edid(pos->id);
> > +		free(pos);
> > +	}
> > +
> > +	xmlrpc_client_cleanup();
> > +	xmlrpc_env_clean(&env);
> > +
> > +	free(chamelium_ports);
> > +	allocated_edids = NULL;
> > +	chamelium_url = NULL;
> > +	chamelium_ports = NULL;
> > +	chamelium_port_count = 0;
> > +}
> > +
> > diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
> > new file mode 100644
> > index 0000000..900615c
> > --- /dev/null
> > +++ b/lib/igt_chamelium.h
> > @@ -0,0 +1,77 @@
> > +/*
> > + * 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 at redhat.com>
> > + */
> > +
> > +#ifndef IGT_CHAMELIUM_H
> > +#define IGT_CHAMELIUM_H
> > +
> > +#include "config.h"
> > +#include "igt.h"
> > +#include <stdbool.h>
> > +
> > +/**
> > + * chamelium_port:
> > + * @type: The DRM connector type of the chamelium port
> > + * @id: The ID of the chamelium port
> > + */
> > +struct chamelium_port {
> > +	unsigned int type;
> > +	int id;
> > +
> > +	/* For restoring the original port state after finishing
> > tests */
> > +	bool original_plugged;
> > +};
> > +
> > +extern int chamelium_port_count;
> > +extern struct chamelium_port *chamelium_ports;
> > +
> > +/**
> > + * igt_require_chamelium:
> > + *
> > + * Checks whether or not the environment variable CHAMELIUM_HOST
> > is non-null,
> > + * otherwise skips the current test.
> > + */
> > +#define igt_require_chamelium() \
> > +	igt_require(getenv("CHAMELIUM_HOST") != NULL);
> > +
> > +void chamelium_init(void);
> > +void chamelium_deinit(void);
> > +void chamelium_reset(void);
> > +
> > +void chamelium_plug(int id);
> > +void chamelium_unplug(int id);
> > +bool chamelium_is_plugged(int id);
> > +bool chamelium_port_wait_video_input_stable(int id, int
> > timeout_secs);
> > +void chamelium_fire_mixed_hpd_pulses(int id, ...);
> > +void chamelium_fire_hpd_pulses(int id, int width, int count);
> > +void chamelium_async_hpd_pulse_start(int id, bool high, int
> > delay_secs);
> > +void chamelium_async_hpd_pulse_finish(void);
> > +int chamelium_new_edid(const unsigned char *edid);
> > +void chamelium_port_set_edid(int id, int edid_id);
> > +bool chamelium_port_get_ddc_state(int id);
> > +void chamelium_port_set_ddc_state(int id, bool enabled);
> > +void chamelium_port_get_resolution(int id, int *x, int *y);
> > +unsigned int chamelium_get_crc_for_area(int id, int x, int y, int
> > w, int h);
> > +
> > +#endif /* IGT_CHAMELIUM_H */
> > diff --git a/lib/igt_kms.c b/lib/igt_kms.c
> > index 989704e..7768d7b 100644
> > --- a/lib/igt_kms.c
> > +++ b/lib/igt_kms.c
> > @@ -40,6 +40,10 @@
> >  #endif
> >  #include <errno.h>
> >  #include <time.h>
> > +#ifdef HAVE_CHAMELIUM
> > +#include <libudev.h>
> > +#include <poll.h>
> > +#endif
> >  
> >  #include <i915_drm.h>
> >  
> > @@ -2760,6 +2764,109 @@ void igt_reset_connectors(void)
> >  			      "detect");
> >  }
> >  
> > +#ifdef HAVE_CHAMELIUM
> > +static struct udev_monitor *hotplug_mon;
> > +
> > +/**
> > + * igt_watch_hotplug:
> > + *
> > + * Begin monitoring udev for hotplug events.
> > + */
> > +void igt_watch_hotplug(void)
> > +{
> > +	struct udev *udev;
> > +	int ret, flags, fd;
> > +
> > +	if (hotplug_mon)
> > +		igt_cleanup_hotplug();
> > +
> > +	udev = udev_new();
> > +	igt_assert(udev != NULL);
> > +
> > +	hotplug_mon = udev_monitor_new_from_netlink(udev, "udev");
> > +	igt_assert(hotplug_mon != NULL);
> > +
> > +	ret =
> > udev_monitor_filter_add_match_subsystem_devtype(hotplug_mon,
> > +							      "drm
> > ",
> > +							      "drm
> > _minor");
> > +	igt_assert_eq(ret, 0);
> > +	ret = udev_monitor_filter_update(hotplug_mon);
> > +	igt_assert_eq(ret, 0);
> > +	ret = udev_monitor_enable_receiving(hotplug_mon);
> > +	igt_assert_eq(ret, 0);
> > +
> > +	/* Set the fd for udev as non blocking */
> > +	fd = udev_monitor_get_fd(hotplug_mon);
> > +	flags = fcntl(fd, F_GETFL, 0);
> > +	igt_assert(flags);
> > +
> > +	flags |= O_NONBLOCK;
> > +	igt_assert_neq(fcntl(fd, F_SETFL, flags), -1);
> > +}
> > +
> > +/**
> > + * igt_hotplug_detected:
> > + * @timeout_secs: How long to wait for a hotplug event to occur.
> > + *
> > + * Assert that a hotplug event was received since we last checked
> > the monitor.
> > + */
> > +bool igt_hotplug_detected(int timeout_secs)
> > +{
> > +	struct udev_device *dev;
> > +	const char *hotplug_val;
> > +	struct pollfd fd = {
> > +		.fd = udev_monitor_get_fd(hotplug_mon),
> > +		.events = POLLIN
> > +	};
> > +	bool hotplug_received = false;
> > +
> > +	/* Go through all of the events pending on the udev
> > monitor. Once we
> > +	 * receive a hotplug, we continue going through the rest
> > of the events
> > +	 * so that redundant hotplug events don't change the
> > results of future
> > +	 * checks
> > +	 */
> > +	while (!hotplug_received && poll(&fd, 1, timeout_secs *
> > 1000)) {
> > +		dev = udev_monitor_receive_device(hotplug_mon);
> > +
> > +		hotplug_val = udev_device_get_property_value(dev,
> > "HOTPLUG");
> > +		if (hotplug_val && atoi(hotplug_val) == 1)
> > +			hotplug_received = true;
> > +
> > +		udev_device_unref(dev);
> > +	}
> > +
> > +	return hotplug_received;
> > +}
> > +
> > +/**
> > + * igt_flush_hotplugs:
> > + * @mon: A udev monitor created by #igt_watch_hotplug
> > + *
> > + * Get rid of any pending hotplug events waiting on the udev
> > monitor
> > + */
> > +void igt_flush_hotplugs(void)
> > +{
> > +	struct udev_device *dev;
> > +
> > +	while ((dev = udev_monitor_receive_device(hotplug_mon)))
> > +		udev_device_unref(dev);
> > +}
> > +
> > +/**
> > + * igt_cleanup_hotplug:
> > + *
> > + * Cleanup the resources allocated by #igt_watch_hotplug
> > + */
> > +void igt_cleanup_hotplug(void)
> > +{
> > +	struct udev *udev = udev_monitor_get_udev(hotplug_mon);
> > +
> > +	udev_monitor_unref(hotplug_mon);
> > +	hotplug_mon = NULL;
> > +	udev_unref(udev);
> > +}
> > +#endif
> > +
> >  /**
> >   * kmstest_get_vbl_flag:
> >   * @pipe_id: Pipe to convert to flag representation.
> > diff --git a/lib/igt_kms.h b/lib/igt_kms.h
> > index 6422adc..d0b67e0 100644
> > --- a/lib/igt_kms.h
> > +++ b/lib/igt_kms.h
> > @@ -31,6 +31,9 @@
> >  #include <stdbool.h>
> >  #include <stdint.h>
> >  #include <stddef.h>
> > +#ifdef HAVE_CHAMELIUM
> > +#include <libudev.h>
> > +#endif
> >  
> >  #include <xf86drmMode.h>
> >  
> > @@ -333,6 +336,7 @@ igt_plane_t *igt_output_get_plane(igt_output_t
> > *output, enum igt_plane plane);
> >  bool igt_pipe_get_property(igt_pipe_t *pipe, const char *name,
> >  			   uint32_t *prop_id, uint64_t *value,
> >  			   drmModePropertyPtr *prop);
> > +void igt_output_get_edid(igt_output_t *output, unsigned char
> > *edid_out);
> >  
> >  static inline bool igt_plane_supports_rotation(igt_plane_t *plane)
> >  {
> > @@ -478,6 +482,13 @@ uint32_t kmstest_get_vbl_flag(uint32_t
> > pipe_id);
> >  #define EDID_LENGTH 128
> >  const unsigned char* igt_kms_get_base_edid(void);
> >  const unsigned char* igt_kms_get_alt_edid(void);
> > -
> > +bool igt_compare_output_edid(igt_output_t *output, const unsigned
> > char *edid);
> > +
> > +#ifdef HAVE_CHAMELIUM
> > +void igt_watch_hotplug(void);
> > +bool igt_hotplug_detected(int timeout_secs);
> > +void igt_flush_hotplugs(void);
> > +void igt_cleanup_hotplug(void);
> > +#endif
> >  
> >  #endif /* __IGT_KMS_H__ */
> > diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
> > index 97ba9e5..6539bf9 100755
> > --- a/scripts/run-tests.sh
> > +++ b/scripts/run-tests.sh
> > @@ -122,10 +122,10 @@ if [ ! -x "$PIGLIT" ]; then
> >  fi
> >  
> >  if [ "x$RESUME" != "x" ]; then
> > -	sudo IGT_TEST_ROOT="$IGT_TEST_ROOT" "$PIGLIT" resume
> > "$RESULTS" $NORETRY
> > +	sudo IGT_TEST_ROOT="$IGT_TEST_ROOT"
> > CHAMELIUM_HOST="$CHAMELIUM_HOST" "$PIGLIT" resume "$RESULTS"
> > $NORETRY
> >  else
> >  	mkdir -p "$RESULTS"
> > -	sudo IGT_TEST_ROOT="$IGT_TEST_ROOT" "$PIGLIT" run igt -o
> > "$RESULTS" -s $VERBOSE $EXCLUDE $FILTER
> > +	sudo IGT_TEST_ROOT="$IGT_TEST_ROOT"
> > CHAMELIUM_HOST="$CHAMELIUM_HOST" "$PIGLIT" run igt -o "$RESULTS" -s
> > $VERBOSE $EXCLUDE $FILTER
> >  fi
> >  
> >  if [ "$SUMMARY" == "html" ]; then
> > diff --git a/tests/Makefile.am b/tests/Makefile.am
> > index a408126..06a8e6b 100644
> > --- a/tests/Makefile.am
> > +++ b/tests/Makefile.am
> > @@ -63,7 +63,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
> > @@ -119,5 +119,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) $(UDEV_CFLAGS)
> > +chamelium_LDADD = $(LDADD) $(XMLRPC_LIBS) $(UDEV_LIBS)
> >  endif
> >  
> > diff --git a/tests/Makefile.sources b/tests/Makefile.sources
> > index 6d081c3..3e01852 100644
> > --- a/tests/Makefile.sources
> > +++ b/tests/Makefile.sources
> > @@ -131,6 +131,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 0000000..769cfdc
> > --- /dev/null
> > +++ b/tests/chamelium.c
> > @@ -0,0 +1,549 @@
> > +/*
> > + * 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 at redhat.com>
> > + */
> > +
> > +#include "config.h"
> > +#include "igt.h"
> > +
> > +#include <fcntl.h>
> > +#include <string.h>
> > +
> > +struct connector_info {
> > +	int id;
> > +	unsigned int type;
> > +};
> > +
> > +typedef struct {
> > +	int drm_fd;
> > +	struct connector_info *connectors;
> > +	int connector_count;
> > +} data_t;
> > +
> > +#define HOTPLUG_TIMEOUT 30 /* seconds */
> > +
> > +/*
> > + * Since we can't get an exact mapping of which chamelium ports
> > are connected
> > + * to each of the DUT's ports, we have to figure out whether or
> > not the status
> > + * of a port on the chamelium has changed by counting the number
> > of connectors
> > + * with the connector type and status we want, and then comparing
> > the values
> > + * from before hotplugging and after
> > + */
> > +static void
> > +reprobe_connectors(data_t *data, unsigned int type)
> > +{
> > +	drmModeConnector *connector;
> > +	int i;
> > +
> > +	igt_debug("Reprobing %s connectors...\n",
> > +		  kmstest_connector_type_str(type));
> > +
> > +	for (i = 0; i < data->connector_count; i++) {
> > +		if (data->connectors[i].type != type)
> > +			continue;
> > +
> > +		connector = drmModeGetConnector(data->drm_fd,
> > +						data-
> > >connectors[i].id);
> > +		igt_assert(connector);
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +}
> > +
> > +static void
> > +reset_chamelium_state(data_t *data)
> > +{
> > +	chamelium_reset();
> > +	reprobe_connectors(data, DRM_MODE_CONNECTOR_DisplayPort);
> > +	reprobe_connectors(data, DRM_MODE_CONNECTOR_HDMIA);
> > +	reprobe_connectors(data, DRM_MODE_CONNECTOR_VGA);
> > +}
> > +
> > +static int
> > +connector_status_count(data_t *data, unsigned int type, unsigned
> > int status)
> > +{
> > +	struct connector_info *info;
> > +	drmModeConnector *connector;
> > +	int count = 0;
> > +
> > +	for (int i = 0; i < data->connector_count; i++) {
> > +		info = &data->connectors[i];
> > +		if (info->type != type)
> > +			continue;
> > +
> > +		connector = drmModeGetConnectorCurrent(data-
> > >drm_fd, info->id);
> > +		igt_assert(connector);
> > +
> > +		if (connector->connection == status)
> > +			count++;
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +
> > +	return count;
> > +}
> > +
> > +static void
> > +require_connector_present(data_t *data, unsigned int type)
> > +{
> > +	int i;
> > +	bool found = false;
> > +
> > +	for (i = 0; i < data->connector_count && !found; i++) {
> > +		if (data->connectors[i].type == type)
> > +			found = true;
> > +	}
> > +
> > +	igt_require_f(found, "No port of type %s was found on the
> > system\n",
> > +		      kmstest_connector_type_str(type));
> > +
> > +	for (i = 0, found = false; i < chamelium_port_count &&
> > !found; i++) {
> > +		if (chamelium_ports[i].type == type)
> > +			found = true;
> > +	}
> > +
> > +	igt_require_f(found, "No connected port of type %s was
> > found on the chamelium\n",
> > +		      kmstest_connector_type_str(type));
> > +}
> > +
> > +static drmModeConnector *
> > +find_connected(data_t *data, unsigned int type)
> > +{
> > +	drmModeConnector *connector;
> > +	int i;
> > +
> > +	for (i = 0; i < data->connector_count; i++) {
> > +		if (data->connectors[i].type != type)
> > +			continue;
> > +
> > +		connector = drmModeGetConnector(data->drm_fd,
> > +						data-
> > >connectors[i].id);
> > +		igt_assert(connector);
> > +
> > +		if (connector->connection == DRM_MODE_CONNECTED)
> > +			return connector;
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +/*
> > + * Skips the test if we find any connectors with a matching type
> > connected.
> > + * This is necessary when we need to identify which port on the
> > machine is
> > + * connected to which port on the chamelium, since any other ports
> > that are
> > + * connected to other displays could cause us to choose the wrong
> > port.
> > + *
> > + * This also has the effect of reprobing all of the connected
> > ports.
> > + */
> > +static void
> > +skip_on_any_connected(data_t *data, unsigned int type)
> > +{
> > +	drmModeConnector *connector;
> > +
> > +	connector = find_connected(data, type);
> > +	if (connector)
> > +		drmModeFreeConnector(connector);
> > +
> > +	igt_skip_on(connector);
> > +}
> > +
> > +static void
> > +test_basic_hotplug(data_t *data, struct chamelium_port *port)
> > +{
> > +	int before, after;
> > +	int i;
> > +
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	for (i = 0; i < 15; i++) {
> > +		igt_flush_hotplugs();
> > +
> > +		/* Check if we get a sysfs hotplug event */
> > +		before = connector_status_count(data, port->type,
> > +						DRM_MODE_CONNECTED
> > );
> > +		chamelium_plug(port->id);
> > +		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +		/* Now we should have one additional port
> > connected */
> > +		reprobe_connectors(data, port->type);
> > +		after = connector_status_count(data, port->type,
> > +					       DRM_MODE_CONNECTED)
> > ;
> > +		igt_assert_lt(before, after);
> > +
> > +		igt_flush_hotplugs();
> > +
> > +		/* Now check if we get a hotplug from
> > disconnection */
> > +		before = connector_status_count(data, port->type,
> > +						DRM_MODE_DISCONNEC
> > TED);
> > +		chamelium_unplug(port->id);
> > +		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +		/* And make sure we now have one more disconnected
> > port */
> > +		reprobe_connectors(data, port->type);
> > +		after = connector_status_count(data, port->type,
> > +					       DRM_MODE_DISCONNECT
> > ED);
> > +		igt_assert_lt(before, after);
> > +
> > +		/* Sleep so we don't accidentally cause an hpd
> > storm */
> > +		sleep(1);
> > +	}
> > +}
> > +
> > +static void
> > +test_edid_read(data_t *data, struct chamelium_port *port,
> > +	       int edid_id, const unsigned char *edid)
> > +{
> > +	drmModeConnector *connector;
> > +	drmModeObjectProperties *props;
> > +	drmModePropertyBlobPtr edid_blob = NULL;
> > +	bool edid_found = false;
> > +	int i;
> > +
> > +	reset_chamelium_state(data);
> > +	skip_on_any_connected(data, port->type);
> > +
> > +	chamelium_port_set_edid(port->id, edid_id);
> > +	chamelium_plug(port->id);
> > +	sleep(1);
> > +	igt_assert(connector = find_connected(data, port->type));
> > +
> > +	props = drmModeObjectGetProperties(data->drm_fd,
> > +					   connector-
> > >connector_id,
> > +					   DRM_MODE_OBJECT_CONNECT
> > OR);
> > +	igt_assert(props);
> > +
> > +	/* Get the edid */
> > +	for (i = 0; i < props->count_props && !edid_blob; i++) {
> > +		drmModePropertyPtr prop =
> > +			drmModeGetProperty(data->drm_fd,
> > +					   props->props[i]);
> > +
> > +		igt_assert(prop);
> > +
> > +		if (strcmp(prop->name, "EDID") == 0) {
> > +			edid_blob = drmModeGetPropertyBlob(
> > +			    data->drm_fd, props->prop_values[i]);
> > +		}
> > +
> > +		drmModeFreeProperty(prop);
> > +	}
> > +
> > +	/* And make sure it matches to what we expected */
> > +	edid_found = memcmp(edid, edid_blob->data, EDID_LENGTH) ==
> > 0;
> > +
> > +	drmModeFreePropertyBlob(edid_blob);
> > +	drmModeFreeObjectProperties(props);
> > +	drmModeFreeConnector(connector);
> > +
> > +	igt_assert(edid_found);
> > +}
> > +
> > +static void
> > +test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> > +			enum igt_suspend_state state,
> > +			enum igt_suspend_test test)
> > +{
> > +	int before, after;
> > +	int delay = 7;
> > +
> > +	igt_skip_without_suspend_support(state, test);
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	igt_set_autoresume_delay(15);
> > +
> > +	/* Make sure we notice new connectors after resuming */
> > +	before = connector_status_count(data, port->type,
> > DRM_MODE_CONNECTED);
> > +	sleep(1);
> > +	igt_flush_hotplugs();
> > +
> > +	chamelium_async_hpd_pulse_start(port->id, false, delay);
> > +	igt_system_suspend_autoresume(state, test);
> > +	chamelium_async_hpd_pulse_finish();
> > +
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +	reprobe_connectors(data, port->type);
> > +	after = connector_status_count(data, port->type,
> > DRM_MODE_CONNECTED);
> > +	igt_assert_lt(before, after);
> > +
> > +	igt_flush_hotplugs();
> > +
> > +	/* Now make sure we notice disconnected connectors after
> > resuming */
> > +	before = connector_status_count(data, port->type,
> > DRM_MODE_DISCONNECTED);
> > +
> > +	chamelium_async_hpd_pulse_start(port->id, true, delay);
> > +	igt_system_suspend_autoresume(state, test);
> > +	chamelium_async_hpd_pulse_finish();
> > +
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +	reprobe_connectors(data, port->type);
> > +	after = connector_status_count(data, port->type,
> > DRM_MODE_DISCONNECTED);
> > +	igt_assert_lt(before, after);
> > +}
> > +
> > +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)
> > +{
> > +	igt_skip_without_suspend_support(state, test);
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	/* First plug in the port */
> > +	chamelium_port_set_edid(port->id, edid_id);
> > +	chamelium_plug(port->id);
> > +
> > +	reprobe_connectors(data, port->type);
> > +	sleep(1);
> > +	igt_flush_hotplugs();
> > +
> > +	/*
> > +	 * Change the edid before we suspend. On resume, the
> > machine should
> > +	 * notice the EDID change and fire a hotplug event.
> > +	 */
> > +	chamelium_port_set_edid(port->id, alt_edid_id);
> > +
> > +	igt_system_suspend_autoresume(state, test);
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +}
> > +
> > +static void
> > +test_display(data_t *data, struct chamelium_port *port)
> > +{
> > +	igt_display_t display;
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +	struct igt_fb fb;
> > +	drmModeRes *res;
> > +	drmModeModeInfo *mode;
> > +	int connector_found = false, fb_id;
> > +
> > +	chamelium_plug(port->id);
> > +	igt_assert(res = drmModeGetResources(data->drm_fd));
> > +	kmstest_unset_all_crtcs(data->drm_fd, res);
> > +
> > +	igt_display_init(&display, data->drm_fd);
> > +
> > +	/* Find the active connector */
> > +	for_each_connected_output(&display, output) {
> > +		drmModeConnector *connector = output-
> > >config.connector;
> > +
> > +		if (connector && connector->connector_type ==
> > port->type &&
> > +		    connector->connection == DRM_MODE_CONNECTED) {
> > +			connector_found = true;
> > +			break;
> > +		}
> > +	}
> > +	igt_assert(connector_found);
> > +
> > +	/* Setup the display */
> > +	igt_output_set_pipe(output, PIPE_A);
> > +	mode = igt_output_get_mode(output);
> > +	primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	fb_id = igt_create_pattern_fb(data->drm_fd,
> > +				      mode->hdisplay,
> > +				      mode->vdisplay,
> > +				      DRM_FORMAT_XRGB8888,
> > +				      LOCAL_DRM_FORMAT_MOD_NONE,
> > +				      &fb);
> > +	igt_assert(fb_id > 0);
> > +	igt_plane_set_fb(primary, &fb);
> > +
> > +	igt_display_commit(&display);
> > +
> > +	igt_assert(chamelium_port_wait_video_input_stable(port-
> > >id,
> > +							  HOTPLUG_
> > TIMEOUT));
> > +
> > +	drmModeFreeResources(res);
> > +	igt_display_fini(&display);
> > +}
> > +
> > +static void
> > +test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> > +{
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	/* Disable the DDC on the connector and make sure we still
> > get a
> > +	 * hotplug
> > +	 */
> > +	chamelium_port_set_ddc_state(port->id, false);
> > +	chamelium_plug(port->id);
> > +
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +}
> > +
> > +static void
> > +cache_connector_info(data_t *data)
> > +{
> > +	drmModeRes *res = drmModeGetResources(data->drm_fd);
> > +	drmModeConnector *connector;
> > +	int i;
> > +
> > +	igt_assert(res);
> > +
> > +	data->connector_count = res->count_connectors;
> > +	data->connectors = calloc(sizeof(struct connector_info),
> > +				  res->count_connectors);
> > +	igt_assert(data->connectors);
> > +
> > +	for (i = 0; i < res->count_connectors; i++) {
> > +		connector = drmModeGetConnectorCurrent(data-
> > >drm_fd,
> > +						       res-
> > >connectors[i]);
> > +		igt_assert(connector);
> > +
> > +		data->connectors[i].id = connector->connector_id;
> > +		data->connectors[i].type = connector-
> > >connector_type;
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +
> > +	drmModeFreeResources(res);
> > +}
> > +
> > +#define for_each_port(p, port)                  \
> > +	for (p = 0, port = &chamelium_ports[p]; \
> > +	     p < chamelium_port_count;          \
> > +	     p++, port = &chamelium_ports[p])   \
> > +
> > +#define connector_subtest(name__, type__) \
> > +	igt_subtest(name__)               \
> > +		for_each_port(p, port)    \
> > +			if (port->type == DRM_MODE_CONNECTOR_ ##
> > type__)
> > +
> > +#define define_common_connector_tests(type_str__,
> > type__)                     \
> > +	connector_subtest(type_str__ "-hpd",
> > type__)                          \
> > +		test_basic_hotplug(&data,
> > port);                              \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-edid-read", type__)
> > {                  \
> > +		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(type_str__ "-hpd-after-suspend",
> > type__)            \
> > +		test_suspend_resume_hpd(&data,
> > port,                          \
> > +					SUSPEND_STATE_MEM,        
> >             \
> > +					SUSPEND_TEST_NONE);       
> >             \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-hpd-after-hibernate",
> > type__)          \
> > +		test_suspend_resume_hpd(&data,
> > port,                          \
> > +					SUSPEND_STATE_DISK,       
> >             \
> > +					SUSPEND_TEST_DEVICES);    
> >             \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-edid-change-during-
> > suspend", type__)   \
> > +		test_suspend_resume_edid_change(&data,
> > port,                  \
> > +						SUSPEND_STATE_MEM,
> >             \
> > +						SUSPEND_TEST_NONE,
> >             \
> > +						edid_id,
> > alt_edid_id);        \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-edid-change-during-
> > hibernate", type__) \
> > +		test_suspend_resume_edid_change(&data,
> > port,                  \
> > +						SUSPEND_STATE_DISK
> > ,           \
> > +						SUSPEND_TEST_DEVIC
> > ES,         \
> > +						edid_id,
> > alt_edid_id);        \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-display",
> > type__)                      \
> > +		test_display(&data, port);
> > +
> > +static data_t data;
> > +
> > +igt_main
> > +{
> > +	struct chamelium_port *port;
> > +	int edid_id, alt_edid_id, p;
> > +
> > +	igt_fixture {
> > +		igt_require_chamelium();
> > +		igt_skip_on_simulation();
> > +
> > +		chamelium_init();
> > +
> > +		edid_id =
> > chamelium_new_edid(igt_kms_get_base_edid());
> > +		alt_edid_id =
> > chamelium_new_edid(igt_kms_get_alt_edid());
> > +
> > +		data.drm_fd =
> > drm_open_driver_master(DRIVER_INTEL);
> > +		cache_connector_info(&data);
> > +
> > +		/* 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);
> > +		}
> > +
> > +		define_common_connector_tests("dp", DisplayPort);
> > +	}
> > +
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			require_connector_present(
> > +			    &data, DRM_MODE_CONNECTOR_HDMIA);
> > +		}
> > +
> > +		define_common_connector_tests("hdmi", HDMIA);
> > +	}
> > +
> > +	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);
> > +
> > +		connector_subtest("vga-display", VGA)
> > +			test_display(&data, port);
> > +	}
> > +}
> > -- 
> > 2.7.4
> > 
> > _______________________________________________
> > Intel-gfx mailing list
> > Intel-gfx at lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
> 
-- 
Cheers,
	Lyude


More information about the Intel-gfx mailing list