On 8 November 2016 at 01:05, Lyude <lyude@xxxxxxxxxx> 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@xxxxxxxxxx> > --- > 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@xxxxxxxxxx> > + */ > + > +#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@xxxxxxxxxx> > + */ > + > +#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@xxxxxxxxxx> > + */ > + > +#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@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/intel-gfx _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx