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

Tomeu Vizoso tomeu at tomeuvizoso.net
Wed Nov 9 15:18:40 UTC 2016


On 8 November 2016 at 01:05, Lyude <lyude at redhat.com> 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.

I think it would be good to split this patch in a few smaller bits,
each with its logical change.

> 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.

Back when I started work on this, it was agreed with Daniel Vetter
that a config file would be used for this mapping (and other
configuration). This is the $HOME/.igtrc I was using during
development:

[Chamelium]
server_ip=192.168.100.123
server_port=9992
port_names=HDMI
connector_names=HDMI-A-1

I used the keyfile API in glib, as we are already depending on it (and
that's also why I chose libsoup instead of libxmlrpc).

For reference, this is the WIP branch that I was using to test frame
CRC capture with Chamelium:

https://git.collabora.com/cgit/user/tomeu/intel-gpu-tools.git/commit/?h=chamelium-crc

Regards,

Tomeu

> 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 ++++++
>  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_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 at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx


More information about the Intel-gfx mailing list