[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