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 <ly...@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 <ly...@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 <ly...@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 <ly...@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_DISCONNECTED);
> +             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_DISCONNECTED);
> +             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_CONNECTOR);
> +     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_DEVICES,         \
> +                                             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@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to