All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
@ 2016-11-08  0:05 Lyude
  2016-11-08  0:05 ` [RFC i-g-t 1/4] igt_aux: Add igt_skip_without_suspend_support() Lyude
                   ` (4 more replies)
  0 siblings, 5 replies; 16+ messages in thread
From: Lyude @ 2016-11-08  0:05 UTC (permalink / raw)
  To: intel-gfx; +Cc: Lyude

For a very long time, we've had basically no automated testing coverage of
hotplugging functionality mainly because the only way to test this sort of
thing was to manually plug and unplug the connectors yourself. As well, this
also means we could never automate testing of various DisplayPort quirks, MST,
etc. At long last however, we might finally have a solution for this.

A while back, the ChromeOS guys came up with a device known as the Chamelium:

	https://www.chromium.org/chromium-os/testing/chamelium

And actually sent both me and airlied one. This little device allows us to
emulate any kind of display we want, along with simulating hotplugs and various
quirky behaviors with displays. On top of that, it even allows for grabbing the
video input on all of the connectors, manual control of the i2c line for all of
the display connectors, etc.

With this patch series, we can now actually write intel-gpu-tools tests with
this gadget!

However, there's a couple of things that haven't been done yet and need to be
kept in mind:

 - The current fpga_tio board that the Chamelium uses unfortunately doesn't
   have native support for DisplayPort MST on it's DisplayPort receivers (they
   only go up to version 1.1a of the spec). While annoying testing MST with
   this isn't impossible, as we can implement all of the hotplugging paths we
   care about by manually controlling the i2c line and emulating an MST
   display. IMO, this is really the biggest thing we need coverage for anyway.
   This hasn't been done yet, but it's definitely on my to-do list.

 - While writing this patch series, I found that quite a few of the RPC calls
   for chameleond don't work as expected. For instance, I have had absolutely
   no luck getting CRCs from any of the display types that the chamelium
   supports. This isn't a huge deal though, since we usually just use the
   native CRC read back on the GPU anyway.

 - Among other things that are broken with the chameleon, video signal
   detection for DisplayPort is one of them. After the first plug/unplug cycle,
   the DisplayPort receiver gets stuck and gives the wrong results for
   WaitForInputStable. Luckily I've already got a fix I'll be submitting to the
   ChromeOS guys when I get around to setting up their homebrew git tools:

	https://github.com/Lyude/chameleond/tree/wip/chameleon-fixes

   For now, expect the dp-display tests to fail without those patches.

Lyude (4):
  igt_aux: Add igt_skip_without_suspend_support()
  igt_aux: Add igt_set_autoresume_delay()
  igt_aux: Add some list helpers from wayland
  Add support for hotplug testing with the Chamelium

 configure.ac           |  13 +
 lib/Makefile.am        |  10 +-
 lib/igt.h              |   1 +
 lib/igt_aux.c          |  94 ++++++++
 lib/igt_aux.h          |  41 ++++
 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 ++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 1538 insertions(+), 5 deletions(-)
 create mode 100644 lib/igt_chamelium.c
 create mode 100644 lib/igt_chamelium.h
 create mode 100644 tests/chamelium.c

-- 
2.7.4

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [RFC i-g-t 1/4] igt_aux: Add igt_skip_without_suspend_support()
  2016-11-08  0:05 [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium Lyude
@ 2016-11-08  0:05 ` Lyude
  2016-11-08  0:05 ` [RFC i-g-t 2/4] igt_aux: Add igt_set_autoresume_delay() Lyude
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 16+ messages in thread
From: Lyude @ 2016-11-08  0:05 UTC (permalink / raw)
  To: intel-gfx; +Cc: Lyude

Since all of the chamelium calls are blocking, we need to be able to
make suspend/resume tests with it multi-threaded. As such, it's not the
best idea to rely on igt_system_suspend_autoresume() for skipping tests
on systems without suspend/resume support since we could accidentally
leave the thread controlling the chamelium running after the test gets
skipped.

Signed-off-by: Lyude <lyude@redhat.com>
---
 lib/igt_aux.c | 21 +++++++++++++++++++++
 lib/igt_aux.h |  2 ++
 2 files changed, 23 insertions(+)

diff --git a/lib/igt_aux.c b/lib/igt_aux.c
index 421f6d4..9754148 100644
--- a/lib/igt_aux.c
+++ b/lib/igt_aux.c
@@ -743,6 +743,27 @@ static uint32_t get_supported_suspend_states(int power_dir)
 }
 
 /**
+ * igt_skip_without_suspend_state:
+ * @state: an #igt_suspend_state to check for
+ *
+ * Check whether or not the system supports the given @state, and skip the
+ * current test if it doesn't. Useful for tests we want to skip before
+ * attempting to call #igt_system_suspend_autoresume.
+ */
+void igt_skip_without_suspend_support(enum igt_suspend_state state,
+				      enum igt_suspend_test test)
+{
+	int power_dir;
+
+	igt_require((power_dir = open("/sys/power", O_RDONLY)) >= 0);
+	igt_require(get_supported_suspend_states(power_dir) & (1 << state));
+	igt_require(test == SUSPEND_TEST_NONE ||
+		    faccessat(power_dir, "pm_test", R_OK | W_OK, 0) == 0);
+
+	close(power_dir);
+}
+
+/**
  * igt_system_suspend_autoresume:
  * @state: an #igt_suspend_state, the target suspend state
  * @test: an #igt_suspend_test, test point at which to complete the suspend
diff --git a/lib/igt_aux.h b/lib/igt_aux.h
index d30196b..973a9cd 100644
--- a/lib/igt_aux.h
+++ b/lib/igt_aux.h
@@ -170,6 +170,8 @@ enum igt_suspend_test {
 	SUSPEND_TEST_NUM,
 };
 
+void igt_skip_without_suspend_support(enum igt_suspend_state state,
+				      enum igt_suspend_test test);
 void igt_system_suspend_autoresume(enum igt_suspend_state state,
 				   enum igt_suspend_test test);
 
-- 
2.7.4

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC i-g-t 2/4] igt_aux: Add igt_set_autoresume_delay()
  2016-11-08  0:05 [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium Lyude
  2016-11-08  0:05 ` [RFC i-g-t 1/4] igt_aux: Add igt_skip_without_suspend_support() Lyude
@ 2016-11-08  0:05 ` Lyude
  2016-11-08  9:39   ` Chris Wilson
  2016-11-08  0:05 ` [RFC i-g-t 3/4] igt_aux: Add some list helpers from wayland Lyude
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 16+ messages in thread
From: Lyude @ 2016-11-08  0:05 UTC (permalink / raw)
  To: intel-gfx; +Cc: Lyude

The default autoresume delay is about 5 seconds. It's possible on a
system that's not very fast this might not be a long enough time, since
an asynchronous hotplug event we scheduled on the chamelium that was
intended to happen during suspend could happen before we actually manage
to suspend. So, add a function that allows us to increase the autoresume
time to ensure this never happens during suspend/resume tests with the
chamelium.

Signed-off-by: Lyude <lyude@redhat.com>
---
 lib/igt_aux.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/igt_aux.h |  1 +
 2 files changed, 47 insertions(+)

diff --git a/lib/igt_aux.c b/lib/igt_aux.c
index 9754148..26d32fd 100644
--- a/lib/igt_aux.c
+++ b/lib/igt_aux.c
@@ -812,6 +812,52 @@ void igt_system_suspend_autoresume(enum igt_suspend_state state,
 	close(power_dir);
 }
 
+static int original_autoresume_delay;
+
+static void igt_restore_autoresume_delay(int sig)
+{
+	int delay_fd;
+	char delay_str[10];
+
+	igt_assert((delay_fd = open("/sys/module/suspend/parameters/pm_test_delay",
+				    O_WRONLY)) >= 0);
+
+	snprintf(delay_str, sizeof(delay_str), "%d", original_autoresume_delay);
+	igt_assert(write(delay_fd, delay_str, strlen(delay_str)));
+
+	close(delay_fd);
+}
+
+/**
+ * igt_set_autoresume_delay:
+ * @delay_secs: The delay in seconds before resuming the system
+ *
+ * Sets how long we wait to resume the system after suspending it, using the
+ * suspend.pm_test_delay variable. On exit, the original delay value is
+ * restored.
+ */
+void igt_set_autoresume_delay(int delay_secs)
+{
+	int delay_fd;
+	char delay_str[10];
+
+	igt_skip_on_simulation();
+
+	igt_assert((delay_fd = open("/sys/module/suspend/parameters/pm_test_delay",
+				    O_RDWR)) >= 0);
+
+	if (!original_autoresume_delay) {
+		igt_assert(read(delay_fd, delay_str, sizeof(delay_str)));
+		original_autoresume_delay = atoi(delay_str);
+		igt_install_exit_handler(igt_restore_autoresume_delay);
+	}
+
+	snprintf(delay_str, sizeof(delay_str), "%d", delay_secs);
+	igt_assert(write(delay_fd, delay_str, strlen(delay_str)));
+
+	close(delay_fd);
+}
+
 /**
  * igt_drop_root:
  *
diff --git a/lib/igt_aux.h b/lib/igt_aux.h
index 973a9cd..7cee901 100644
--- a/lib/igt_aux.h
+++ b/lib/igt_aux.h
@@ -174,6 +174,7 @@ void igt_skip_without_suspend_support(enum igt_suspend_state state,
 				      enum igt_suspend_test test);
 void igt_system_suspend_autoresume(enum igt_suspend_state state,
 				   enum igt_suspend_test test);
+void igt_set_autoresume_delay(int delay_secs);
 
 /* dropping priviledges */
 void igt_drop_root(void);
-- 
2.7.4

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC i-g-t 3/4] igt_aux: Add some list helpers from wayland
  2016-11-08  0:05 [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium Lyude
  2016-11-08  0:05 ` [RFC i-g-t 1/4] igt_aux: Add igt_skip_without_suspend_support() Lyude
  2016-11-08  0:05 ` [RFC i-g-t 2/4] igt_aux: Add igt_set_autoresume_delay() Lyude
@ 2016-11-08  0:05 ` Lyude
  2016-11-08  9:37   ` Chris Wilson
  2016-11-08  0:05 ` [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium Lyude
  2016-11-09 15:09 ` [RFC i-g-t 0/4] intel-gpu-tools: Add support for " Tomeu Vizoso
  4 siblings, 1 reply; 16+ messages in thread
From: Lyude @ 2016-11-08  0:05 UTC (permalink / raw)
  To: intel-gfx; +Cc: Lyude

Since we're going to be using lists for keeping track of EDIDs we've
allocated on the chamelium, add some generic list helpers from the
wayland project.

Signed-off-by: Lyude <lyude@redhat.com>
---
 lib/igt_aux.c | 27 +++++++++++++++++++++++++++
 lib/igt_aux.h | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)

diff --git a/lib/igt_aux.c b/lib/igt_aux.c
index 26d32fd..8d85828 100644
--- a/lib/igt_aux.c
+++ b/lib/igt_aux.c
@@ -1343,3 +1343,30 @@ double igt_stop_siglatency(struct igt_mean *result)
 
 	return mean;
 }
+
+void igt_list_init(struct igt_list *igt_list)
+{
+	igt_list->prev = igt_list;
+	igt_list->next = igt_list;
+}
+
+void igt_list_insert(struct igt_list *igt_list, struct igt_list *elm)
+{
+	elm->prev = igt_list;
+	elm->next = igt_list->next;
+	igt_list->next = elm;
+	elm->next->prev = elm;
+}
+
+void igt_list_remove(struct igt_list *elm)
+{
+	elm->prev->next = elm->next;
+	elm->next->prev = elm->prev;
+	elm->next = NULL;
+	elm->prev = NULL;
+}
+
+bool igt_list_empty(const struct igt_list *igt_list)
+{
+	return igt_list->next == igt_list;
+}
diff --git a/lib/igt_aux.h b/lib/igt_aux.h
index 7cee901..1ac73e7 100644
--- a/lib/igt_aux.h
+++ b/lib/igt_aux.h
@@ -267,4 +267,42 @@ double igt_stop_siglatency(struct igt_mean *result);
 void igt_set_module_param(const char *name, const char *val);
 void igt_set_module_param_int(const char *name, int val);
 
+/*
+ * This list data structure is a verbatim copy from wayland-util.h from the
+ * Wayland project; except that wl_ prefix has been removed.
+ */
+
+struct igt_list {
+	struct igt_list *prev;
+	struct igt_list *next;
+};
+
+void igt_list_init(struct igt_list *list);
+void igt_list_insert(struct igt_list *list, struct igt_list *elm);
+void igt_list_remove(struct igt_list *elm);
+bool igt_list_empty(const struct igt_list *list);
+
+#ifdef __GNUC__
+#define container_of(ptr, sample, member)				\
+	(__typeof__(sample))((char *)(ptr)	-			\
+		 ((char *)&(sample)->member - (char *)(sample)))
+#else
+#define container_of(ptr, sample, member)				\
+	(void *)((char *)(ptr)	-				        \
+		 ((char *)&(sample)->member - (char *)(sample)))
+#endif
+
+#define igt_list_for_each(pos, head, member)				\
+	for (pos = 0, pos = container_of((head)->next, pos, member);	\
+	     &pos->member != (head);					\
+	     pos = container_of(pos->member.next, pos, member))
+
+#define igt_list_for_each_safe(pos, tmp, head, member)			\
+	for (pos = 0, tmp = 0, 						\
+	     pos = container_of((head)->next, pos, member),		\
+	     tmp = container_of((pos)->member.next, tmp, member);	\
+	     &pos->member != (head);					\
+	     pos = tmp,							\
+	     tmp = container_of(pos->member.next, tmp, member))
+
 #endif /* IGT_AUX_H */
-- 
2.7.4

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium
  2016-11-08  0:05 [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium Lyude
                   ` (2 preceding siblings ...)
  2016-11-08  0:05 ` [RFC i-g-t 3/4] igt_aux: Add some list helpers from wayland Lyude
@ 2016-11-08  0:05 ` Lyude
  2016-11-09 15:18   ` Tomeu Vizoso
  2016-11-14  7:05   ` Daniel Vetter
  2016-11-09 15:09 ` [RFC i-g-t 0/4] intel-gpu-tools: Add support for " Tomeu Vizoso
  4 siblings, 2 replies; 16+ messages in thread
From: Lyude @ 2016-11-08  0:05 UTC (permalink / raw)
  To: intel-gfx; +Cc: Lyude

For the purpose of testing things such as hotplugging and bad monitors,
the ChromeOS team ended up designing a neat little device known as the
Chamelium. More information on this can be found here:

	https://www.chromium.org/chromium-os/testing/chamelium

This adds support for a couple of things to intel-gpu-tools:
 - igt library functions for connecting to udev and monitoring it for
   hotplug events, loosely based off of the unfinished hotplugging
   implementation in testdisplay
 - Library functions for controlling the chamelium in tests using
   xmlrpc. A couple of RPC calls were ommitted here, mainly because they
   didn't seem very useful for our needs or because they're just plain
   broken
 - A set of basic tests using the chamelium.

Because there's no surefire way that I know of where we can map which
chamelium port belongs to which port on the system being tested (we
could just use hotplugging, but then we'd be relying on something that
might be broken on the machine and potentially give false positives for
certain tests), most of the chamelium tests will figure out whether or
not a connection happened by counting the number of connectors matching
the status we're looking for before hotplugging with the chamelium, vs.
after hotplugging it.

Tests which require that we know which port belongs to a certain port
(such as ones where we actually perform a modeset) will unplug all of
the chamelium ports, plug the desired port, then use the first DRM
connector with the desired connector type that's marked as connected. In
order to ensure we don't end up using the wrong connector, these tests
will skip if they find any connectors with the desired type marked as
connected before performing the hotplug on the chamelium.

Running these tests requires (of course) a working Chamelium, along with
the RPC URL for the chamelium being specified in the environment
variable CHAMELIUM_HOST. If no URL is specified, the tests will just
skip on their own. As well, tests for connectors which are not actually
present on the system or the chamelium will skip on their own as well.

Signed-off-by: Lyude <lyude@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@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@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@redhat.com>
+ */
+
+#include "config.h"
+#include "igt.h"
+
+#include <fcntl.h>
+#include <string.h>
+
+struct connector_info {
+	int id;
+	unsigned int type;
+};
+
+typedef struct {
+	int drm_fd;
+	struct connector_info *connectors;
+	int connector_count;
+} data_t;
+
+#define HOTPLUG_TIMEOUT 30 /* seconds */
+
+/*
+ * Since we can't get an exact mapping of which chamelium ports are connected
+ * to each of the DUT's ports, we have to figure out whether or not the status
+ * of a port on the chamelium has changed by counting the number of connectors
+ * with the connector type and status we want, and then comparing the values
+ * from before hotplugging and after
+ */
+static void
+reprobe_connectors(data_t *data, unsigned int type)
+{
+	drmModeConnector *connector;
+	int i;
+
+	igt_debug("Reprobing %s connectors...\n",
+		  kmstest_connector_type_str(type));
+
+	for (i = 0; i < data->connector_count; i++) {
+		if (data->connectors[i].type != type)
+			continue;
+
+		connector = drmModeGetConnector(data->drm_fd,
+						data->connectors[i].id);
+		igt_assert(connector);
+
+		drmModeFreeConnector(connector);
+	}
+}
+
+static void
+reset_chamelium_state(data_t *data)
+{
+	chamelium_reset();
+	reprobe_connectors(data, DRM_MODE_CONNECTOR_DisplayPort);
+	reprobe_connectors(data, DRM_MODE_CONNECTOR_HDMIA);
+	reprobe_connectors(data, DRM_MODE_CONNECTOR_VGA);
+}
+
+static int
+connector_status_count(data_t *data, unsigned int type, unsigned int status)
+{
+	struct connector_info *info;
+	drmModeConnector *connector;
+	int count = 0;
+
+	for (int i = 0; i < data->connector_count; i++) {
+		info = &data->connectors[i];
+		if (info->type != type)
+			continue;
+
+		connector = drmModeGetConnectorCurrent(data->drm_fd, info->id);
+		igt_assert(connector);
+
+		if (connector->connection == status)
+			count++;
+
+		drmModeFreeConnector(connector);
+	}
+
+	return count;
+}
+
+static void
+require_connector_present(data_t *data, unsigned int type)
+{
+	int i;
+	bool found = false;
+
+	for (i = 0; i < data->connector_count && !found; i++) {
+		if (data->connectors[i].type == type)
+			found = true;
+	}
+
+	igt_require_f(found, "No port of type %s was found on the system\n",
+		      kmstest_connector_type_str(type));
+
+	for (i = 0, found = false; i < chamelium_port_count && !found; i++) {
+		if (chamelium_ports[i].type == type)
+			found = true;
+	}
+
+	igt_require_f(found, "No connected port of type %s was found on the chamelium\n",
+		      kmstest_connector_type_str(type));
+}
+
+static drmModeConnector *
+find_connected(data_t *data, unsigned int type)
+{
+	drmModeConnector *connector;
+	int i;
+
+	for (i = 0; i < data->connector_count; i++) {
+		if (data->connectors[i].type != type)
+			continue;
+
+		connector = drmModeGetConnector(data->drm_fd,
+						data->connectors[i].id);
+		igt_assert(connector);
+
+		if (connector->connection == DRM_MODE_CONNECTED)
+			return connector;
+
+		drmModeFreeConnector(connector);
+	}
+
+	return NULL;
+}
+
+/*
+ * Skips the test if we find any connectors with a matching type connected.
+ * This is necessary when we need to identify which port on the machine is
+ * connected to which port on the chamelium, since any other ports that are
+ * connected to other displays could cause us to choose the wrong port.
+ *
+ * This also has the effect of reprobing all of the connected ports.
+ */
+static void
+skip_on_any_connected(data_t *data, unsigned int type)
+{
+	drmModeConnector *connector;
+
+	connector = find_connected(data, type);
+	if (connector)
+		drmModeFreeConnector(connector);
+
+	igt_skip_on(connector);
+}
+
+static void
+test_basic_hotplug(data_t *data, struct chamelium_port *port)
+{
+	int before, after;
+	int i;
+
+	reset_chamelium_state(data);
+	igt_watch_hotplug();
+
+	for (i = 0; i < 15; i++) {
+		igt_flush_hotplugs();
+
+		/* Check if we get a sysfs hotplug event */
+		before = connector_status_count(data, port->type,
+						DRM_MODE_CONNECTED);
+		chamelium_plug(port->id);
+		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
+
+		/* Now we should have one additional port connected */
+		reprobe_connectors(data, port->type);
+		after = connector_status_count(data, port->type,
+					       DRM_MODE_CONNECTED);
+		igt_assert_lt(before, after);
+
+		igt_flush_hotplugs();
+
+		/* Now check if we get a hotplug from disconnection */
+		before = connector_status_count(data, port->type,
+						DRM_MODE_DISCONNECTED);
+		chamelium_unplug(port->id);
+		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
+
+		/* And make sure we now have one more disconnected port */
+		reprobe_connectors(data, port->type);
+		after = connector_status_count(data, port->type,
+					       DRM_MODE_DISCONNECTED);
+		igt_assert_lt(before, after);
+
+		/* Sleep so we don't accidentally cause an hpd storm */
+		sleep(1);
+	}
+}
+
+static void
+test_edid_read(data_t *data, struct chamelium_port *port,
+	       int edid_id, const unsigned char *edid)
+{
+	drmModeConnector *connector;
+	drmModeObjectProperties *props;
+	drmModePropertyBlobPtr edid_blob = NULL;
+	bool edid_found = false;
+	int i;
+
+	reset_chamelium_state(data);
+	skip_on_any_connected(data, port->type);
+
+	chamelium_port_set_edid(port->id, edid_id);
+	chamelium_plug(port->id);
+	sleep(1);
+	igt_assert(connector = find_connected(data, port->type));
+
+	props = drmModeObjectGetProperties(data->drm_fd,
+					   connector->connector_id,
+					   DRM_MODE_OBJECT_CONNECTOR);
+	igt_assert(props);
+
+	/* Get the edid */
+	for (i = 0; i < props->count_props && !edid_blob; i++) {
+		drmModePropertyPtr prop =
+			drmModeGetProperty(data->drm_fd,
+					   props->props[i]);
+
+		igt_assert(prop);
+
+		if (strcmp(prop->name, "EDID") == 0) {
+			edid_blob = drmModeGetPropertyBlob(
+			    data->drm_fd, props->prop_values[i]);
+		}
+
+		drmModeFreeProperty(prop);
+	}
+
+	/* And make sure it matches to what we expected */
+	edid_found = memcmp(edid, edid_blob->data, EDID_LENGTH) == 0;
+
+	drmModeFreePropertyBlob(edid_blob);
+	drmModeFreeObjectProperties(props);
+	drmModeFreeConnector(connector);
+
+	igt_assert(edid_found);
+}
+
+static void
+test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
+			enum igt_suspend_state state,
+			enum igt_suspend_test test)
+{
+	int before, after;
+	int delay = 7;
+
+	igt_skip_without_suspend_support(state, test);
+	reset_chamelium_state(data);
+	igt_watch_hotplug();
+
+	igt_set_autoresume_delay(15);
+
+	/* Make sure we notice new connectors after resuming */
+	before = connector_status_count(data, port->type, DRM_MODE_CONNECTED);
+	sleep(1);
+	igt_flush_hotplugs();
+
+	chamelium_async_hpd_pulse_start(port->id, false, delay);
+	igt_system_suspend_autoresume(state, test);
+	chamelium_async_hpd_pulse_finish();
+
+	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
+
+	reprobe_connectors(data, port->type);
+	after = connector_status_count(data, port->type, DRM_MODE_CONNECTED);
+	igt_assert_lt(before, after);
+
+	igt_flush_hotplugs();
+
+	/* Now make sure we notice disconnected connectors after resuming */
+	before = connector_status_count(data, port->type, DRM_MODE_DISCONNECTED);
+
+	chamelium_async_hpd_pulse_start(port->id, true, delay);
+	igt_system_suspend_autoresume(state, test);
+	chamelium_async_hpd_pulse_finish();
+
+	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
+
+	reprobe_connectors(data, port->type);
+	after = connector_status_count(data, port->type, DRM_MODE_DISCONNECTED);
+	igt_assert_lt(before, after);
+}
+
+static void
+test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
+				enum igt_suspend_state state,
+				enum igt_suspend_test test,
+				int edid_id,
+				int alt_edid_id)
+{
+	igt_skip_without_suspend_support(state, test);
+	reset_chamelium_state(data);
+	igt_watch_hotplug();
+
+	/* First plug in the port */
+	chamelium_port_set_edid(port->id, edid_id);
+	chamelium_plug(port->id);
+
+	reprobe_connectors(data, port->type);
+	sleep(1);
+	igt_flush_hotplugs();
+
+	/*
+	 * Change the edid before we suspend. On resume, the machine should
+	 * notice the EDID change and fire a hotplug event.
+	 */
+	chamelium_port_set_edid(port->id, alt_edid_id);
+
+	igt_system_suspend_autoresume(state, test);
+	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
+}
+
+static void
+test_display(data_t *data, struct chamelium_port *port)
+{
+	igt_display_t display;
+	igt_output_t *output;
+	igt_plane_t *primary;
+	struct igt_fb fb;
+	drmModeRes *res;
+	drmModeModeInfo *mode;
+	int connector_found = false, fb_id;
+
+	chamelium_plug(port->id);
+	igt_assert(res = drmModeGetResources(data->drm_fd));
+	kmstest_unset_all_crtcs(data->drm_fd, res);
+
+	igt_display_init(&display, data->drm_fd);
+
+	/* Find the active connector */
+	for_each_connected_output(&display, output) {
+		drmModeConnector *connector = output->config.connector;
+
+		if (connector && connector->connector_type == port->type &&
+		    connector->connection == DRM_MODE_CONNECTED) {
+			connector_found = true;
+			break;
+		}
+	}
+	igt_assert(connector_found);
+
+	/* Setup the display */
+	igt_output_set_pipe(output, PIPE_A);
+	mode = igt_output_get_mode(output);
+	primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
+	igt_assert(primary);
+
+	fb_id = igt_create_pattern_fb(data->drm_fd,
+				      mode->hdisplay,
+				      mode->vdisplay,
+				      DRM_FORMAT_XRGB8888,
+				      LOCAL_DRM_FORMAT_MOD_NONE,
+				      &fb);
+	igt_assert(fb_id > 0);
+	igt_plane_set_fb(primary, &fb);
+
+	igt_display_commit(&display);
+
+	igt_assert(chamelium_port_wait_video_input_stable(port->id,
+							  HOTPLUG_TIMEOUT));
+
+	drmModeFreeResources(res);
+	igt_display_fini(&display);
+}
+
+static void
+test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
+{
+	reset_chamelium_state(data);
+	igt_watch_hotplug();
+
+	/* Disable the DDC on the connector and make sure we still get a
+	 * hotplug
+	 */
+	chamelium_port_set_ddc_state(port->id, false);
+	chamelium_plug(port->id);
+
+	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
+}
+
+static void
+cache_connector_info(data_t *data)
+{
+	drmModeRes *res = drmModeGetResources(data->drm_fd);
+	drmModeConnector *connector;
+	int i;
+
+	igt_assert(res);
+
+	data->connector_count = res->count_connectors;
+	data->connectors = calloc(sizeof(struct connector_info),
+				  res->count_connectors);
+	igt_assert(data->connectors);
+
+	for (i = 0; i < res->count_connectors; i++) {
+		connector = drmModeGetConnectorCurrent(data->drm_fd,
+						       res->connectors[i]);
+		igt_assert(connector);
+
+		data->connectors[i].id = connector->connector_id;
+		data->connectors[i].type = connector->connector_type;
+
+		drmModeFreeConnector(connector);
+	}
+
+	drmModeFreeResources(res);
+}
+
+#define for_each_port(p, port)                  \
+	for (p = 0, port = &chamelium_ports[p]; \
+	     p < chamelium_port_count;          \
+	     p++, port = &chamelium_ports[p])   \
+
+#define connector_subtest(name__, type__) \
+	igt_subtest(name__)               \
+		for_each_port(p, port)    \
+			if (port->type == DRM_MODE_CONNECTOR_ ## type__)
+
+#define define_common_connector_tests(type_str__, type__)                     \
+	connector_subtest(type_str__ "-hpd", type__)                          \
+		test_basic_hotplug(&data, port);                              \
+                                                                              \
+	connector_subtest(type_str__ "-edid-read", type__) {                  \
+		test_edid_read(&data, port, edid_id,                          \
+			       igt_kms_get_base_edid());                      \
+		test_edid_read(&data, port, alt_edid_id,                      \
+			       igt_kms_get_alt_edid());                       \
+	}                                                                     \
+                                                                              \
+	connector_subtest(type_str__ "-hpd-after-suspend", type__)            \
+		test_suspend_resume_hpd(&data, port,                          \
+					SUSPEND_STATE_MEM,                    \
+					SUSPEND_TEST_NONE);                   \
+                                                                              \
+	connector_subtest(type_str__ "-hpd-after-hibernate", type__)          \
+		test_suspend_resume_hpd(&data, port,                          \
+					SUSPEND_STATE_DISK,                   \
+					SUSPEND_TEST_DEVICES);                \
+                                                                              \
+	connector_subtest(type_str__ "-edid-change-during-suspend", type__)   \
+		test_suspend_resume_edid_change(&data, port,                  \
+						SUSPEND_STATE_MEM,            \
+						SUSPEND_TEST_NONE,            \
+						edid_id, alt_edid_id);        \
+                                                                              \
+	connector_subtest(type_str__ "-edid-change-during-hibernate", type__) \
+		test_suspend_resume_edid_change(&data, port,                  \
+						SUSPEND_STATE_DISK,           \
+						SUSPEND_TEST_DEVICES,         \
+						edid_id, alt_edid_id);        \
+                                                                              \
+	connector_subtest(type_str__ "-display", type__)                      \
+		test_display(&data, port);
+
+static data_t data;
+
+igt_main
+{
+	struct chamelium_port *port;
+	int edid_id, alt_edid_id, p;
+
+	igt_fixture {
+		igt_require_chamelium();
+		igt_skip_on_simulation();
+
+		chamelium_init();
+
+		edid_id = chamelium_new_edid(igt_kms_get_base_edid());
+		alt_edid_id = chamelium_new_edid(igt_kms_get_alt_edid());
+
+		data.drm_fd = drm_open_driver_master(DRIVER_INTEL);
+		cache_connector_info(&data);
+
+		/* So fbcon doesn't try to reprobe things itself */
+		kmstest_set_vt_graphics_mode();
+	}
+
+	igt_subtest_group {
+		igt_fixture {
+			require_connector_present(
+			    &data, DRM_MODE_CONNECTOR_DisplayPort);
+		}
+
+		define_common_connector_tests("dp", DisplayPort);
+	}
+
+	igt_subtest_group {
+		igt_fixture {
+			require_connector_present(
+			    &data, DRM_MODE_CONNECTOR_HDMIA);
+		}
+
+		define_common_connector_tests("hdmi", HDMIA);
+	}
+
+	igt_subtest_group {
+		igt_fixture {
+			require_connector_present(
+			    &data, DRM_MODE_CONNECTOR_VGA);
+		}
+
+		connector_subtest("vga-hpd", VGA)
+			test_basic_hotplug(&data, port);
+
+		connector_subtest("vga-edid-read", VGA) {
+			test_edid_read(&data, port, edid_id,
+				       igt_kms_get_base_edid());
+			test_edid_read(&data, port, alt_edid_id,
+				       igt_kms_get_alt_edid());
+		}
+
+		/* FIXME: Right now there isn't a way to do any sort of delayed
+		 * psuedo-hotplug with VGA, so testing detection after a
+		 * suspend/resume cycle isn't possible yet
+		 */
+
+		connector_subtest("vga-hpd-without-ddc", VGA)
+			test_hpd_without_ddc(&data, port);
+
+		connector_subtest("vga-display", VGA)
+			test_display(&data, port);
+	}
+}
-- 
2.7.4

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 3/4] igt_aux: Add some list helpers from wayland
  2016-11-08  0:05 ` [RFC i-g-t 3/4] igt_aux: Add some list helpers from wayland Lyude
@ 2016-11-08  9:37   ` Chris Wilson
  0 siblings, 0 replies; 16+ messages in thread
From: Chris Wilson @ 2016-11-08  9:37 UTC (permalink / raw)
  To: Lyude; +Cc: intel-gfx

On Mon, Nov 07, 2016 at 07:05:15PM -0500, Lyude wrote:
> Since we're going to be using lists for keeping track of EDIDs we've
> allocated on the chamelium, add some generic list helpers from the
> wayland project.

We are a little more familiar with the kernel naming convention.
-Chris

-- 
Chris Wilson, Intel Open Source Technology Centre
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 2/4] igt_aux: Add igt_set_autoresume_delay()
  2016-11-08  0:05 ` [RFC i-g-t 2/4] igt_aux: Add igt_set_autoresume_delay() Lyude
@ 2016-11-08  9:39   ` Chris Wilson
  0 siblings, 0 replies; 16+ messages in thread
From: Chris Wilson @ 2016-11-08  9:39 UTC (permalink / raw)
  To: Lyude; +Cc: intel-gfx

On Mon, Nov 07, 2016 at 07:05:14PM -0500, Lyude wrote:
> The default autoresume delay is about 5 seconds. It's possible on a
> system that's not very fast this might not be a long enough time, since
> an asynchronous hotplug event we scheduled on the chamelium that was
> intended to happen during suspend could happen before we actually manage
> to suspend. So, add a function that allows us to increase the autoresume
> time to ensure this never happens during suspend/resume tests with the
> chamelium.
> 
> Signed-off-by: Lyude <lyude@redhat.com>
> ---
>  lib/igt_aux.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
>  lib/igt_aux.h |  1 +
>  2 files changed, 47 insertions(+)
> 
> diff --git a/lib/igt_aux.c b/lib/igt_aux.c
> index 9754148..26d32fd 100644
> --- a/lib/igt_aux.c
> +++ b/lib/igt_aux.c
> @@ -812,6 +812,52 @@ void igt_system_suspend_autoresume(enum igt_suspend_state state,
>  	close(power_dir);
>  }
>  
> +static int original_autoresume_delay;
> +
> +static void igt_restore_autoresume_delay(int sig)
> +{
> +	int delay_fd;
> +	char delay_str[10];
> +
> +	igt_assert((delay_fd = open("/sys/module/suspend/parameters/pm_test_delay",
> +				    O_WRONLY)) >= 0);
> +
> +	snprintf(delay_str, sizeof(delay_str), "%d", original_autoresume_delay);
> +	igt_assert(write(delay_fd, delay_str, strlen(delay_str)));
> +
> +	close(delay_fd);
> +}
> +
> +/**
> + * igt_set_autoresume_delay:
> + * @delay_secs: The delay in seconds before resuming the system
> + *
> + * Sets how long we wait to resume the system after suspending it, using the
> + * suspend.pm_test_delay variable. On exit, the original delay value is
> + * restored.
> + */
> +void igt_set_autoresume_delay(int delay_secs)
> +{
> +	int delay_fd;
> +	char delay_str[10];
> +
> +	igt_skip_on_simulation();
> +
> +	igt_assert((delay_fd = open("/sys/module/suspend/parameters/pm_test_delay",
> +				    O_RDWR)) >= 0);

Are these test failures? No, then don't assert. Do it within your
igt_fixture() block and use igt_require to skip.
-Chris

-- 
Chris Wilson, Intel Open Source Technology Centre
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
  2016-11-08  0:05 [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium Lyude
                   ` (3 preceding siblings ...)
  2016-11-08  0:05 ` [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium Lyude
@ 2016-11-09 15:09 ` Tomeu Vizoso
  2016-11-11 17:53   ` Lyude Paul
  4 siblings, 1 reply; 16+ messages in thread
From: Tomeu Vizoso @ 2016-11-09 15:09 UTC (permalink / raw)
  To: Lyude; +Cc: Intel Graphics Development

Hi Lyude,

I think this looks very good.

On 8 November 2016 at 01:05, Lyude <lyude@redhat.com> wrote:
>
>  - While writing this patch series, I found that quite a few of the RPC calls
>    for chameleond don't work as expected. For instance, I have had absolutely
>    no luck getting CRCs from any of the display types that the chamelium
>    supports.

When I looked at this a few months ago, frame CRCs were working just
fine. I was using libsoup, so maybe there's some problem with the
unpacking of the checksum?

> This isn't a huge deal though, since we usually just use the
>    native CRC read back on the GPU anyway.

I'm not completely sure what you mean by that, but not all graphic
pipelines are able to provide frame CRCs so I think this Chamelium
work will be very useful when running tests that do check frame CRCs.

Regards,

Tomeu

>
>  - Among other things that are broken with the chameleon, video signal
>    detection for DisplayPort is one of them. After the first plug/unplug cycle,
>    the DisplayPort receiver gets stuck and gives the wrong results for
>    WaitForInputStable. Luckily I've already got a fix I'll be submitting to the
>    ChromeOS guys when I get around to setting up their homebrew git tools:
>
>         https://github.com/Lyude/chameleond/tree/wip/chameleon-fixes
>
>    For now, expect the dp-display tests to fail without those patches.
>
> Lyude (4):
>   igt_aux: Add igt_skip_without_suspend_support()
>   igt_aux: Add igt_set_autoresume_delay()
>   igt_aux: Add some list helpers from wayland
>   Add support for hotplug testing with the Chamelium
>
>  configure.ac           |  13 +
>  lib/Makefile.am        |  10 +-
>  lib/igt.h              |   1 +
>  lib/igt_aux.c          |  94 ++++++++
>  lib/igt_aux.h          |  41 ++++
>  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 ++++++++++++++++++++++++++++++++++++++++++
>  13 files changed, 1538 insertions(+), 5 deletions(-)
>  create mode 100644 lib/igt_chamelium.c
>  create mode 100644 lib/igt_chamelium.h
>  create mode 100644 tests/chamelium.c
>
> --
> 2.7.4
>
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium
  2016-11-08  0:05 ` [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium Lyude
@ 2016-11-09 15:18   ` Tomeu Vizoso
  2016-11-14  7:05   ` Daniel Vetter
  1 sibling, 0 replies; 16+ messages in thread
From: Tomeu Vizoso @ 2016-11-09 15:18 UTC (permalink / raw)
  To: Lyude; +Cc: Intel Graphics Development

On 8 November 2016 at 01:05, Lyude <lyude@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@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@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@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@redhat.com>
> + */
> +
> +#include "config.h"
> +#include "igt.h"
> +
> +#include <fcntl.h>
> +#include <string.h>
> +
> +struct connector_info {
> +       int id;
> +       unsigned int type;
> +};
> +
> +typedef struct {
> +       int drm_fd;
> +       struct connector_info *connectors;
> +       int connector_count;
> +} data_t;
> +
> +#define HOTPLUG_TIMEOUT 30 /* seconds */
> +
> +/*
> + * Since we can't get an exact mapping of which chamelium ports are connected
> + * to each of the DUT's ports, we have to figure out whether or not the status
> + * of a port on the chamelium has changed by counting the number of connectors
> + * with the connector type and status we want, and then comparing the values
> + * from before hotplugging and after
> + */
> +static void
> +reprobe_connectors(data_t *data, unsigned int type)
> +{
> +       drmModeConnector *connector;
> +       int i;
> +
> +       igt_debug("Reprobing %s connectors...\n",
> +                 kmstest_connector_type_str(type));
> +
> +       for (i = 0; i < data->connector_count; i++) {
> +               if (data->connectors[i].type != type)
> +                       continue;
> +
> +               connector = drmModeGetConnector(data->drm_fd,
> +                                               data->connectors[i].id);
> +               igt_assert(connector);
> +
> +               drmModeFreeConnector(connector);
> +       }
> +}
> +
> +static void
> +reset_chamelium_state(data_t *data)
> +{
> +       chamelium_reset();
> +       reprobe_connectors(data, DRM_MODE_CONNECTOR_DisplayPort);
> +       reprobe_connectors(data, DRM_MODE_CONNECTOR_HDMIA);
> +       reprobe_connectors(data, DRM_MODE_CONNECTOR_VGA);
> +}
> +
> +static int
> +connector_status_count(data_t *data, unsigned int type, unsigned int status)
> +{
> +       struct connector_info *info;
> +       drmModeConnector *connector;
> +       int count = 0;
> +
> +       for (int i = 0; i < data->connector_count; i++) {
> +               info = &data->connectors[i];
> +               if (info->type != type)
> +                       continue;
> +
> +               connector = drmModeGetConnectorCurrent(data->drm_fd, info->id);
> +               igt_assert(connector);
> +
> +               if (connector->connection == status)
> +                       count++;
> +
> +               drmModeFreeConnector(connector);
> +       }
> +
> +       return count;
> +}
> +
> +static void
> +require_connector_present(data_t *data, unsigned int type)
> +{
> +       int i;
> +       bool found = false;
> +
> +       for (i = 0; i < data->connector_count && !found; i++) {
> +               if (data->connectors[i].type == type)
> +                       found = true;
> +       }
> +
> +       igt_require_f(found, "No port of type %s was found on the system\n",
> +                     kmstest_connector_type_str(type));
> +
> +       for (i = 0, found = false; i < chamelium_port_count && !found; i++) {
> +               if (chamelium_ports[i].type == type)
> +                       found = true;
> +       }
> +
> +       igt_require_f(found, "No connected port of type %s was found on the chamelium\n",
> +                     kmstest_connector_type_str(type));
> +}
> +
> +static drmModeConnector *
> +find_connected(data_t *data, unsigned int type)
> +{
> +       drmModeConnector *connector;
> +       int i;
> +
> +       for (i = 0; i < data->connector_count; i++) {
> +               if (data->connectors[i].type != type)
> +                       continue;
> +
> +               connector = drmModeGetConnector(data->drm_fd,
> +                                               data->connectors[i].id);
> +               igt_assert(connector);
> +
> +               if (connector->connection == DRM_MODE_CONNECTED)
> +                       return connector;
> +
> +               drmModeFreeConnector(connector);
> +       }
> +
> +       return NULL;
> +}
> +
> +/*
> + * Skips the test if we find any connectors with a matching type connected.
> + * This is necessary when we need to identify which port on the machine is
> + * connected to which port on the chamelium, since any other ports that are
> + * connected to other displays could cause us to choose the wrong port.
> + *
> + * This also has the effect of reprobing all of the connected ports.
> + */
> +static void
> +skip_on_any_connected(data_t *data, unsigned int type)
> +{
> +       drmModeConnector *connector;
> +
> +       connector = find_connected(data, type);
> +       if (connector)
> +               drmModeFreeConnector(connector);
> +
> +       igt_skip_on(connector);
> +}
> +
> +static void
> +test_basic_hotplug(data_t *data, struct chamelium_port *port)
> +{
> +       int before, after;
> +       int i;
> +
> +       reset_chamelium_state(data);
> +       igt_watch_hotplug();
> +
> +       for (i = 0; i < 15; i++) {
> +               igt_flush_hotplugs();
> +
> +               /* Check if we get a sysfs hotplug event */
> +               before = connector_status_count(data, port->type,
> +                                               DRM_MODE_CONNECTED);
> +               chamelium_plug(port->id);
> +               igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +               /* Now we should have one additional port connected */
> +               reprobe_connectors(data, port->type);
> +               after = connector_status_count(data, port->type,
> +                                              DRM_MODE_CONNECTED);
> +               igt_assert_lt(before, after);
> +
> +               igt_flush_hotplugs();
> +
> +               /* Now check if we get a hotplug from disconnection */
> +               before = connector_status_count(data, port->type,
> +                                               DRM_MODE_DISCONNECTED);
> +               chamelium_unplug(port->id);
> +               igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +               /* And make sure we now have one more disconnected port */
> +               reprobe_connectors(data, port->type);
> +               after = connector_status_count(data, port->type,
> +                                              DRM_MODE_DISCONNECTED);
> +               igt_assert_lt(before, after);
> +
> +               /* Sleep so we don't accidentally cause an hpd storm */
> +               sleep(1);
> +       }
> +}
> +
> +static void
> +test_edid_read(data_t *data, struct chamelium_port *port,
> +              int edid_id, const unsigned char *edid)
> +{
> +       drmModeConnector *connector;
> +       drmModeObjectProperties *props;
> +       drmModePropertyBlobPtr edid_blob = NULL;
> +       bool edid_found = false;
> +       int i;
> +
> +       reset_chamelium_state(data);
> +       skip_on_any_connected(data, port->type);
> +
> +       chamelium_port_set_edid(port->id, edid_id);
> +       chamelium_plug(port->id);
> +       sleep(1);
> +       igt_assert(connector = find_connected(data, port->type));
> +
> +       props = drmModeObjectGetProperties(data->drm_fd,
> +                                          connector->connector_id,
> +                                          DRM_MODE_OBJECT_CONNECTOR);
> +       igt_assert(props);
> +
> +       /* Get the edid */
> +       for (i = 0; i < props->count_props && !edid_blob; i++) {
> +               drmModePropertyPtr prop =
> +                       drmModeGetProperty(data->drm_fd,
> +                                          props->props[i]);
> +
> +               igt_assert(prop);
> +
> +               if (strcmp(prop->name, "EDID") == 0) {
> +                       edid_blob = drmModeGetPropertyBlob(
> +                           data->drm_fd, props->prop_values[i]);
> +               }
> +
> +               drmModeFreeProperty(prop);
> +       }
> +
> +       /* And make sure it matches to what we expected */
> +       edid_found = memcmp(edid, edid_blob->data, EDID_LENGTH) == 0;
> +
> +       drmModeFreePropertyBlob(edid_blob);
> +       drmModeFreeObjectProperties(props);
> +       drmModeFreeConnector(connector);
> +
> +       igt_assert(edid_found);
> +}
> +
> +static void
> +test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> +                       enum igt_suspend_state state,
> +                       enum igt_suspend_test test)
> +{
> +       int before, after;
> +       int delay = 7;
> +
> +       igt_skip_without_suspend_support(state, test);
> +       reset_chamelium_state(data);
> +       igt_watch_hotplug();
> +
> +       igt_set_autoresume_delay(15);
> +
> +       /* Make sure we notice new connectors after resuming */
> +       before = connector_status_count(data, port->type, DRM_MODE_CONNECTED);
> +       sleep(1);
> +       igt_flush_hotplugs();
> +
> +       chamelium_async_hpd_pulse_start(port->id, false, delay);
> +       igt_system_suspend_autoresume(state, test);
> +       chamelium_async_hpd_pulse_finish();
> +
> +       igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +       reprobe_connectors(data, port->type);
> +       after = connector_status_count(data, port->type, DRM_MODE_CONNECTED);
> +       igt_assert_lt(before, after);
> +
> +       igt_flush_hotplugs();
> +
> +       /* Now make sure we notice disconnected connectors after resuming */
> +       before = connector_status_count(data, port->type, DRM_MODE_DISCONNECTED);
> +
> +       chamelium_async_hpd_pulse_start(port->id, true, delay);
> +       igt_system_suspend_autoresume(state, test);
> +       chamelium_async_hpd_pulse_finish();
> +
> +       igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +       reprobe_connectors(data, port->type);
> +       after = connector_status_count(data, port->type, DRM_MODE_DISCONNECTED);
> +       igt_assert_lt(before, after);
> +}
> +
> +static void
> +test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
> +                               enum igt_suspend_state state,
> +                               enum igt_suspend_test test,
> +                               int edid_id,
> +                               int alt_edid_id)
> +{
> +       igt_skip_without_suspend_support(state, test);
> +       reset_chamelium_state(data);
> +       igt_watch_hotplug();
> +
> +       /* First plug in the port */
> +       chamelium_port_set_edid(port->id, edid_id);
> +       chamelium_plug(port->id);
> +
> +       reprobe_connectors(data, port->type);
> +       sleep(1);
> +       igt_flush_hotplugs();
> +
> +       /*
> +        * Change the edid before we suspend. On resume, the machine should
> +        * notice the EDID change and fire a hotplug event.
> +        */
> +       chamelium_port_set_edid(port->id, alt_edid_id);
> +
> +       igt_system_suspend_autoresume(state, test);
> +       igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +}
> +
> +static void
> +test_display(data_t *data, struct chamelium_port *port)
> +{
> +       igt_display_t display;
> +       igt_output_t *output;
> +       igt_plane_t *primary;
> +       struct igt_fb fb;
> +       drmModeRes *res;
> +       drmModeModeInfo *mode;
> +       int connector_found = false, fb_id;
> +
> +       chamelium_plug(port->id);
> +       igt_assert(res = drmModeGetResources(data->drm_fd));
> +       kmstest_unset_all_crtcs(data->drm_fd, res);
> +
> +       igt_display_init(&display, data->drm_fd);
> +
> +       /* Find the active connector */
> +       for_each_connected_output(&display, output) {
> +               drmModeConnector *connector = output->config.connector;
> +
> +               if (connector && connector->connector_type == port->type &&
> +                   connector->connection == DRM_MODE_CONNECTED) {
> +                       connector_found = true;
> +                       break;
> +               }
> +       }
> +       igt_assert(connector_found);
> +
> +       /* Setup the display */
> +       igt_output_set_pipe(output, PIPE_A);
> +       mode = igt_output_get_mode(output);
> +       primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
> +       igt_assert(primary);
> +
> +       fb_id = igt_create_pattern_fb(data->drm_fd,
> +                                     mode->hdisplay,
> +                                     mode->vdisplay,
> +                                     DRM_FORMAT_XRGB8888,
> +                                     LOCAL_DRM_FORMAT_MOD_NONE,
> +                                     &fb);
> +       igt_assert(fb_id > 0);
> +       igt_plane_set_fb(primary, &fb);
> +
> +       igt_display_commit(&display);
> +
> +       igt_assert(chamelium_port_wait_video_input_stable(port->id,
> +                                                         HOTPLUG_TIMEOUT));
> +
> +       drmModeFreeResources(res);
> +       igt_display_fini(&display);
> +}
> +
> +static void
> +test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> +{
> +       reset_chamelium_state(data);
> +       igt_watch_hotplug();
> +
> +       /* Disable the DDC on the connector and make sure we still get a
> +        * hotplug
> +        */
> +       chamelium_port_set_ddc_state(port->id, false);
> +       chamelium_plug(port->id);
> +
> +       igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +}
> +
> +static void
> +cache_connector_info(data_t *data)
> +{
> +       drmModeRes *res = drmModeGetResources(data->drm_fd);
> +       drmModeConnector *connector;
> +       int i;
> +
> +       igt_assert(res);
> +
> +       data->connector_count = res->count_connectors;
> +       data->connectors = calloc(sizeof(struct connector_info),
> +                                 res->count_connectors);
> +       igt_assert(data->connectors);
> +
> +       for (i = 0; i < res->count_connectors; i++) {
> +               connector = drmModeGetConnectorCurrent(data->drm_fd,
> +                                                      res->connectors[i]);
> +               igt_assert(connector);
> +
> +               data->connectors[i].id = connector->connector_id;
> +               data->connectors[i].type = connector->connector_type;
> +
> +               drmModeFreeConnector(connector);
> +       }
> +
> +       drmModeFreeResources(res);
> +}
> +
> +#define for_each_port(p, port)                  \
> +       for (p = 0, port = &chamelium_ports[p]; \
> +            p < chamelium_port_count;          \
> +            p++, port = &chamelium_ports[p])   \
> +
> +#define connector_subtest(name__, type__) \
> +       igt_subtest(name__)               \
> +               for_each_port(p, port)    \
> +                       if (port->type == DRM_MODE_CONNECTOR_ ## type__)
> +
> +#define define_common_connector_tests(type_str__, type__)                     \
> +       connector_subtest(type_str__ "-hpd", type__)                          \
> +               test_basic_hotplug(&data, port);                              \
> +                                                                              \
> +       connector_subtest(type_str__ "-edid-read", type__) {                  \
> +               test_edid_read(&data, port, edid_id,                          \
> +                              igt_kms_get_base_edid());                      \
> +               test_edid_read(&data, port, alt_edid_id,                      \
> +                              igt_kms_get_alt_edid());                       \
> +       }                                                                     \
> +                                                                              \
> +       connector_subtest(type_str__ "-hpd-after-suspend", type__)            \
> +               test_suspend_resume_hpd(&data, port,                          \
> +                                       SUSPEND_STATE_MEM,                    \
> +                                       SUSPEND_TEST_NONE);                   \
> +                                                                              \
> +       connector_subtest(type_str__ "-hpd-after-hibernate", type__)          \
> +               test_suspend_resume_hpd(&data, port,                          \
> +                                       SUSPEND_STATE_DISK,                   \
> +                                       SUSPEND_TEST_DEVICES);                \
> +                                                                              \
> +       connector_subtest(type_str__ "-edid-change-during-suspend", type__)   \
> +               test_suspend_resume_edid_change(&data, port,                  \
> +                                               SUSPEND_STATE_MEM,            \
> +                                               SUSPEND_TEST_NONE,            \
> +                                               edid_id, alt_edid_id);        \
> +                                                                              \
> +       connector_subtest(type_str__ "-edid-change-during-hibernate", type__) \
> +               test_suspend_resume_edid_change(&data, port,                  \
> +                                               SUSPEND_STATE_DISK,           \
> +                                               SUSPEND_TEST_DEVICES,         \
> +                                               edid_id, alt_edid_id);        \
> +                                                                              \
> +       connector_subtest(type_str__ "-display", type__)                      \
> +               test_display(&data, port);
> +
> +static data_t data;
> +
> +igt_main
> +{
> +       struct chamelium_port *port;
> +       int edid_id, alt_edid_id, p;
> +
> +       igt_fixture {
> +               igt_require_chamelium();
> +               igt_skip_on_simulation();
> +
> +               chamelium_init();
> +
> +               edid_id = chamelium_new_edid(igt_kms_get_base_edid());
> +               alt_edid_id = chamelium_new_edid(igt_kms_get_alt_edid());
> +
> +               data.drm_fd = drm_open_driver_master(DRIVER_INTEL);
> +               cache_connector_info(&data);
> +
> +               /* So fbcon doesn't try to reprobe things itself */
> +               kmstest_set_vt_graphics_mode();
> +       }
> +
> +       igt_subtest_group {
> +               igt_fixture {
> +                       require_connector_present(
> +                           &data, DRM_MODE_CONNECTOR_DisplayPort);
> +               }
> +
> +               define_common_connector_tests("dp", DisplayPort);
> +       }
> +
> +       igt_subtest_group {
> +               igt_fixture {
> +                       require_connector_present(
> +                           &data, DRM_MODE_CONNECTOR_HDMIA);
> +               }
> +
> +               define_common_connector_tests("hdmi", HDMIA);
> +       }
> +
> +       igt_subtest_group {
> +               igt_fixture {
> +                       require_connector_present(
> +                           &data, DRM_MODE_CONNECTOR_VGA);
> +               }
> +
> +               connector_subtest("vga-hpd", VGA)
> +                       test_basic_hotplug(&data, port);
> +
> +               connector_subtest("vga-edid-read", VGA) {
> +                       test_edid_read(&data, port, edid_id,
> +                                      igt_kms_get_base_edid());
> +                       test_edid_read(&data, port, alt_edid_id,
> +                                      igt_kms_get_alt_edid());
> +               }
> +
> +               /* FIXME: Right now there isn't a way to do any sort of delayed
> +                * psuedo-hotplug with VGA, so testing detection after a
> +                * suspend/resume cycle isn't possible yet
> +                */
> +
> +               connector_subtest("vga-hpd-without-ddc", VGA)
> +                       test_hpd_without_ddc(&data, port);
> +
> +               connector_subtest("vga-display", VGA)
> +                       test_display(&data, port);
> +       }
> +}
> --
> 2.7.4
>
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
  2016-11-09 15:09 ` [RFC i-g-t 0/4] intel-gpu-tools: Add support for " Tomeu Vizoso
@ 2016-11-11 17:53   ` Lyude Paul
  2016-11-15 11:44     ` Tomeu Vizoso
  0 siblings, 1 reply; 16+ messages in thread
From: Lyude Paul @ 2016-11-11 17:53 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: Intel Graphics Development

Alright, quick question: should we be going with your branch then or
mine?

On Wed, 2016-11-09 at 16:09 +0100, Tomeu Vizoso wrote:
> Hi Lyude,
> 
> I think this looks very good.
> 
> On 8 November 2016 at 01:05, Lyude <lyude@redhat.com> wrote:
> > 
> > 
> >  - While writing this patch series, I found that quite a few of the
> > RPC calls
> >    for chameleond don't work as expected. For instance, I have had
> > absolutely
> >    no luck getting CRCs from any of the display types that the
> > chamelium
> >    supports.
> 
> When I looked at this a few months ago, frame CRCs were working just
> fine. I was using libsoup, so maybe there's some problem with the
> unpacking of the checksum?

I'm pretty sure it's on the chameleond side of things. Using the test
server application in chameleond's source shows the same issue. 
> 
> > 
> > This isn't a huge deal though, since we usually just use the
> >    native CRC read back on the GPU anyway.
> 
> I'm not completely sure what you mean by that, but not all graphic
> pipelines are able to provide frame CRCs so I think this Chamelium
> work will be very useful when running tests that do check frame CRCs.
I wasn't aware of that, thanks for letting me know

> 
> Regards,
> 
> Tomeu
> 
> > 
> > 
> >  - Among other things that are broken with the chameleon, video
> > signal
> >    detection for DisplayPort is one of them. After the first
> > plug/unplug cycle,
> >    the DisplayPort receiver gets stuck and gives the wrong results
> > for
> >    WaitForInputStable. Luckily I've already got a fix I'll be
> > submitting to the
> >    ChromeOS guys when I get around to setting up their homebrew git
> > tools:
> > 
> >         https://github.com/Lyude/chameleond/tree/wip/chameleon-fixe
> > s
> > 
> >    For now, expect the dp-display tests to fail without those
> > patches.
> > 
> > Lyude (4):
> >   igt_aux: Add igt_skip_without_suspend_support()
> >   igt_aux: Add igt_set_autoresume_delay()
> >   igt_aux: Add some list helpers from wayland
> >   Add support for hotplug testing with the Chamelium
> > 
> >  configure.ac           |  13 +
> >  lib/Makefile.am        |  10 +-
> >  lib/igt.h              |   1 +
> >  lib/igt_aux.c          |  94 ++++++++
> >  lib/igt_aux.h          |  41 ++++
> >  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
> > ++++++++++++++++++++++++++++++++++++++++++
> >  13 files changed, 1538 insertions(+), 5 deletions(-)
> >  create mode 100644 lib/igt_chamelium.c
> >  create mode 100644 lib/igt_chamelium.h
> >  create mode 100644 tests/chamelium.c
> > 
> > --
> > 2.7.4
> > 
> > _______________________________________________
> > Intel-gfx mailing list
> > Intel-gfx@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
-- 
Cheers,
	Lyude
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium
  2016-11-08  0:05 ` [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium Lyude
  2016-11-09 15:18   ` Tomeu Vizoso
@ 2016-11-14  7:05   ` Daniel Vetter
  2016-11-14 16:46     ` Lyude Paul
  1 sibling, 1 reply; 16+ messages in thread
From: Daniel Vetter @ 2016-11-14  7:05 UTC (permalink / raw)
  To: Lyude; +Cc: intel-gfx

On Mon, Nov 07, 2016 at 07:05:16PM -0500, Lyude wrote:
> For the purpose of testing things such as hotplugging and bad monitors,
> the ChromeOS team ended up designing a neat little device known as the
> Chamelium. More information on this can be found here:
> 
> 	https://www.chromium.org/chromium-os/testing/chamelium
> 
> This adds support for a couple of things to intel-gpu-tools:
>  - igt library functions for connecting to udev and monitoring it for
>    hotplug events, loosely based off of the unfinished hotplugging
>    implementation in testdisplay
>  - Library functions for controlling the chamelium in tests using
>    xmlrpc. A couple of RPC calls were ommitted here, mainly because they
>    didn't seem very useful for our needs or because they're just plain
>    broken
>  - A set of basic tests using the chamelium.
> 
> Because there's no surefire way that I know of where we can map which
> chamelium port belongs to which port on the system being tested (we
> could just use hotplugging, but then we'd be relying on something that
> might be broken on the machine and potentially give false positives for
> certain tests), most of the chamelium tests will figure out whether or
> not a connection happened by counting the number of connectors matching
> the status we're looking for before hotplugging with the chamelium, vs.
> after hotplugging it.
> 
> Tests which require that we know which port belongs to a certain port
> (such as ones where we actually perform a modeset) will unplug all of
> the chamelium ports, plug the desired port, then use the first DRM
> connector with the desired connector type that's marked as connected. In
> order to ensure we don't end up using the wrong connector, these tests
> will skip if they find any connectors with the desired type marked as
> connected before performing the hotplug on the chamelium.
> 
> Running these tests requires (of course) a working Chamelium, along with
> the RPC URL for the chamelium being specified in the environment
> variable CHAMELIUM_HOST. If no URL is specified, the tests will just
> skip on their own. As well, tests for connectors which are not actually
> present on the system or the chamelium will skip on their own as well.
> 
> Signed-off-by: Lyude <lyude@redhat.com>
> ---
>  configure.ac           |  13 +
>  lib/Makefile.am        |  10 +-
>  lib/igt.h              |   1 +
>  lib/igt_chamelium.c    | 628 +++++++++++++++++++++++++++++++++++++++++++++++++
>  lib/igt_chamelium.h    |  77 ++++++

Since you typed these nice gtkdocs, please also add it to the .xml in
docs/ and make sure it looks all good (./autogen.sh --enable-gtk-docs).

Wrt the api itself I think all we need is agreement from Tomeu that this
is the right thing for his chamelium use-cases, too. And Tomeu has commit
rights, so can push this stuff for you.
-Daniel


>  lib/igt_kms.c          | 107 +++++++++
>  lib/igt_kms.h          |  13 +-
>  scripts/run-tests.sh   |   4 +-
>  tests/Makefile.am      |   5 +-
>  tests/Makefile.sources |   1 +
>  tests/chamelium.c      | 549 ++++++++++++++++++++++++++++++++++++++++++
>  11 files changed, 1403 insertions(+), 5 deletions(-)
>  create mode 100644 lib/igt_chamelium.c
>  create mode 100644 lib/igt_chamelium.h
>  create mode 100644 tests/chamelium.c
> 
> diff --git a/configure.ac b/configure.ac
> index 735cfd5..88113b2 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -259,6 +259,18 @@ if test "x$with_libunwind" = xyes; then
>  			  AC_MSG_ERROR([libunwind not found. Use --without-libunwind to disable libunwind support.]))
>  fi
>  
> +# enable support for using the chamelium
> +AC_ARG_ENABLE(chamelium,
> +	      AS_HELP_STRING([--without-chamelium],
> +			     [Build tests without chamelium support]),
> +	      [], [with_chamelium=yes])
> +
> +AM_CONDITIONAL(HAVE_CHAMELIUM, [test "x$with_chamelium" = xyes])
> +if test "x$with_chamelium" = xyes; then
> +	AC_DEFINE(HAVE_CHAMELIUM, 1, [chamelium suport])
> +	PKG_CHECK_MODULES(XMLRPC, xmlrpc_client)
> +fi
> +
>  # enable debug symbols
>  AC_ARG_ENABLE(debug,
>  	      AS_HELP_STRING([--disable-debug],
> @@ -356,6 +368,7 @@ echo "       Assembler          : ${enable_assembler}"
>  echo "       Debugger           : ${enable_debugger}"
>  echo "       Overlay            : X: ${enable_overlay_xlib}, Xv: ${enable_overlay_xvlib}"
>  echo "       x86-specific tools : ${build_x86}"
> +echo "       Chamelium support  : ${with_chamelium}"
>  echo ""
>  echo " • API-Documentation      : ${enable_gtk_doc}"
>  echo " • Fail on warnings       : ${enable_werror}"
> diff --git a/lib/Makefile.am b/lib/Makefile.am
> index 4c0893d..aeac43a 100644
> --- a/lib/Makefile.am
> +++ b/lib/Makefile.am
> @@ -22,8 +22,14 @@ if !HAVE_LIBDRM_INTEL
>          stubs/drm/intel_bufmgr.h
>  endif
>  
> +if HAVE_CHAMELIUM
> +    libintel_tools_la_SOURCES +=	\
> +	igt_chamelium.c			\
> +	igt_chamelium.h
> +endif
> +
>  AM_CPPFLAGS = -I$(top_srcdir)
> -AM_CFLAGS = $(CWARNFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS) $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) \
> +AM_CFLAGS = $(CWARNFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS) $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) $(XMLRPC_CFLAGS) $(UDEV_CFLAGS) \
>  	    -DIGT_SRCDIR=\""$(abs_top_srcdir)/tests"\" \
>  	    -DIGT_DATADIR=\""$(pkgdatadir)"\" \
>  	    -DIGT_LOG_DOMAIN=\""$(subst _,-,$*)"\" \
> @@ -38,5 +44,7 @@ libintel_tools_la_LIBADD = \
>  	$(LIBUDEV_LIBS) \
>  	$(LIBUNWIND_LIBS) \
>  	$(TIMER_LIBS) \
> +	$(XMLRPC_LIBS) \
> +	$(UDEV_LIBS) \
>  	-lm
>  
> diff --git a/lib/igt.h b/lib/igt.h
> index d751f24..0ea03e4 100644
> --- a/lib/igt.h
> +++ b/lib/igt.h
> @@ -30,6 +30,7 @@
>  #include "igt_aux.h"
>  #include "igt_core.h"
>  #include "igt_core.h"
> +#include "igt_chamelium.h"
>  #include "igt_debugfs.h"
>  #include "igt_draw.h"
>  #include "igt_fb.h"
> diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
> new file mode 100644
> index 0000000..a281ef6
> --- /dev/null
> +++ b/lib/igt_chamelium.c
> @@ -0,0 +1,628 @@
> +/*
> + * Copyright © 2016 Red Hat Inc.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> + * IN THE SOFTWARE.
> + *
> + * Authors:
> + *  Lyude Paul <lyude@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@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@redhat.com>
> + */
> +
> +#include "config.h"
> +#include "igt.h"
> +
> +#include <fcntl.h>
> +#include <string.h>
> +
> +struct connector_info {
> +	int id;
> +	unsigned int type;
> +};
> +
> +typedef struct {
> +	int drm_fd;
> +	struct connector_info *connectors;
> +	int connector_count;
> +} data_t;
> +
> +#define HOTPLUG_TIMEOUT 30 /* seconds */
> +
> +/*
> + * Since we can't get an exact mapping of which chamelium ports are connected
> + * to each of the DUT's ports, we have to figure out whether or not the status
> + * of a port on the chamelium has changed by counting the number of connectors
> + * with the connector type and status we want, and then comparing the values
> + * from before hotplugging and after
> + */
> +static void
> +reprobe_connectors(data_t *data, unsigned int type)
> +{
> +	drmModeConnector *connector;
> +	int i;
> +
> +	igt_debug("Reprobing %s connectors...\n",
> +		  kmstest_connector_type_str(type));
> +
> +	for (i = 0; i < data->connector_count; i++) {
> +		if (data->connectors[i].type != type)
> +			continue;
> +
> +		connector = drmModeGetConnector(data->drm_fd,
> +						data->connectors[i].id);
> +		igt_assert(connector);
> +
> +		drmModeFreeConnector(connector);
> +	}
> +}
> +
> +static void
> +reset_chamelium_state(data_t *data)
> +{
> +	chamelium_reset();
> +	reprobe_connectors(data, DRM_MODE_CONNECTOR_DisplayPort);
> +	reprobe_connectors(data, DRM_MODE_CONNECTOR_HDMIA);
> +	reprobe_connectors(data, DRM_MODE_CONNECTOR_VGA);
> +}
> +
> +static int
> +connector_status_count(data_t *data, unsigned int type, unsigned int status)
> +{
> +	struct connector_info *info;
> +	drmModeConnector *connector;
> +	int count = 0;
> +
> +	for (int i = 0; i < data->connector_count; i++) {
> +		info = &data->connectors[i];
> +		if (info->type != type)
> +			continue;
> +
> +		connector = drmModeGetConnectorCurrent(data->drm_fd, info->id);
> +		igt_assert(connector);
> +
> +		if (connector->connection == status)
> +			count++;
> +
> +		drmModeFreeConnector(connector);
> +	}
> +
> +	return count;
> +}
> +
> +static void
> +require_connector_present(data_t *data, unsigned int type)
> +{
> +	int i;
> +	bool found = false;
> +
> +	for (i = 0; i < data->connector_count && !found; i++) {
> +		if (data->connectors[i].type == type)
> +			found = true;
> +	}
> +
> +	igt_require_f(found, "No port of type %s was found on the system\n",
> +		      kmstest_connector_type_str(type));
> +
> +	for (i = 0, found = false; i < chamelium_port_count && !found; i++) {
> +		if (chamelium_ports[i].type == type)
> +			found = true;
> +	}
> +
> +	igt_require_f(found, "No connected port of type %s was found on the chamelium\n",
> +		      kmstest_connector_type_str(type));
> +}
> +
> +static drmModeConnector *
> +find_connected(data_t *data, unsigned int type)
> +{
> +	drmModeConnector *connector;
> +	int i;
> +
> +	for (i = 0; i < data->connector_count; i++) {
> +		if (data->connectors[i].type != type)
> +			continue;
> +
> +		connector = drmModeGetConnector(data->drm_fd,
> +						data->connectors[i].id);
> +		igt_assert(connector);
> +
> +		if (connector->connection == DRM_MODE_CONNECTED)
> +			return connector;
> +
> +		drmModeFreeConnector(connector);
> +	}
> +
> +	return NULL;
> +}
> +
> +/*
> + * Skips the test if we find any connectors with a matching type connected.
> + * This is necessary when we need to identify which port on the machine is
> + * connected to which port on the chamelium, since any other ports that are
> + * connected to other displays could cause us to choose the wrong port.
> + *
> + * This also has the effect of reprobing all of the connected ports.
> + */
> +static void
> +skip_on_any_connected(data_t *data, unsigned int type)
> +{
> +	drmModeConnector *connector;
> +
> +	connector = find_connected(data, type);
> +	if (connector)
> +		drmModeFreeConnector(connector);
> +
> +	igt_skip_on(connector);
> +}
> +
> +static void
> +test_basic_hotplug(data_t *data, struct chamelium_port *port)
> +{
> +	int before, after;
> +	int i;
> +
> +	reset_chamelium_state(data);
> +	igt_watch_hotplug();
> +
> +	for (i = 0; i < 15; i++) {
> +		igt_flush_hotplugs();
> +
> +		/* Check if we get a sysfs hotplug event */
> +		before = connector_status_count(data, port->type,
> +						DRM_MODE_CONNECTED);
> +		chamelium_plug(port->id);
> +		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +		/* Now we should have one additional port connected */
> +		reprobe_connectors(data, port->type);
> +		after = connector_status_count(data, port->type,
> +					       DRM_MODE_CONNECTED);
> +		igt_assert_lt(before, after);
> +
> +		igt_flush_hotplugs();
> +
> +		/* Now check if we get a hotplug from disconnection */
> +		before = connector_status_count(data, port->type,
> +						DRM_MODE_DISCONNECTED);
> +		chamelium_unplug(port->id);
> +		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +		/* And make sure we now have one more disconnected port */
> +		reprobe_connectors(data, port->type);
> +		after = connector_status_count(data, port->type,
> +					       DRM_MODE_DISCONNECTED);
> +		igt_assert_lt(before, after);
> +
> +		/* Sleep so we don't accidentally cause an hpd storm */
> +		sleep(1);
> +	}
> +}
> +
> +static void
> +test_edid_read(data_t *data, struct chamelium_port *port,
> +	       int edid_id, const unsigned char *edid)
> +{
> +	drmModeConnector *connector;
> +	drmModeObjectProperties *props;
> +	drmModePropertyBlobPtr edid_blob = NULL;
> +	bool edid_found = false;
> +	int i;
> +
> +	reset_chamelium_state(data);
> +	skip_on_any_connected(data, port->type);
> +
> +	chamelium_port_set_edid(port->id, edid_id);
> +	chamelium_plug(port->id);
> +	sleep(1);
> +	igt_assert(connector = find_connected(data, port->type));
> +
> +	props = drmModeObjectGetProperties(data->drm_fd,
> +					   connector->connector_id,
> +					   DRM_MODE_OBJECT_CONNECTOR);
> +	igt_assert(props);
> +
> +	/* Get the edid */
> +	for (i = 0; i < props->count_props && !edid_blob; i++) {
> +		drmModePropertyPtr prop =
> +			drmModeGetProperty(data->drm_fd,
> +					   props->props[i]);
> +
> +		igt_assert(prop);
> +
> +		if (strcmp(prop->name, "EDID") == 0) {
> +			edid_blob = drmModeGetPropertyBlob(
> +			    data->drm_fd, props->prop_values[i]);
> +		}
> +
> +		drmModeFreeProperty(prop);
> +	}
> +
> +	/* And make sure it matches to what we expected */
> +	edid_found = memcmp(edid, edid_blob->data, EDID_LENGTH) == 0;
> +
> +	drmModeFreePropertyBlob(edid_blob);
> +	drmModeFreeObjectProperties(props);
> +	drmModeFreeConnector(connector);
> +
> +	igt_assert(edid_found);
> +}
> +
> +static void
> +test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> +			enum igt_suspend_state state,
> +			enum igt_suspend_test test)
> +{
> +	int before, after;
> +	int delay = 7;
> +
> +	igt_skip_without_suspend_support(state, test);
> +	reset_chamelium_state(data);
> +	igt_watch_hotplug();
> +
> +	igt_set_autoresume_delay(15);
> +
> +	/* Make sure we notice new connectors after resuming */
> +	before = connector_status_count(data, port->type, DRM_MODE_CONNECTED);
> +	sleep(1);
> +	igt_flush_hotplugs();
> +
> +	chamelium_async_hpd_pulse_start(port->id, false, delay);
> +	igt_system_suspend_autoresume(state, test);
> +	chamelium_async_hpd_pulse_finish();
> +
> +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +	reprobe_connectors(data, port->type);
> +	after = connector_status_count(data, port->type, DRM_MODE_CONNECTED);
> +	igt_assert_lt(before, after);
> +
> +	igt_flush_hotplugs();
> +
> +	/* Now make sure we notice disconnected connectors after resuming */
> +	before = connector_status_count(data, port->type, DRM_MODE_DISCONNECTED);
> +
> +	chamelium_async_hpd_pulse_start(port->id, true, delay);
> +	igt_system_suspend_autoresume(state, test);
> +	chamelium_async_hpd_pulse_finish();
> +
> +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +
> +	reprobe_connectors(data, port->type);
> +	after = connector_status_count(data, port->type, DRM_MODE_DISCONNECTED);
> +	igt_assert_lt(before, after);
> +}
> +
> +static void
> +test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
> +				enum igt_suspend_state state,
> +				enum igt_suspend_test test,
> +				int edid_id,
> +				int alt_edid_id)
> +{
> +	igt_skip_without_suspend_support(state, test);
> +	reset_chamelium_state(data);
> +	igt_watch_hotplug();
> +
> +	/* First plug in the port */
> +	chamelium_port_set_edid(port->id, edid_id);
> +	chamelium_plug(port->id);
> +
> +	reprobe_connectors(data, port->type);
> +	sleep(1);
> +	igt_flush_hotplugs();
> +
> +	/*
> +	 * Change the edid before we suspend. On resume, the machine should
> +	 * notice the EDID change and fire a hotplug event.
> +	 */
> +	chamelium_port_set_edid(port->id, alt_edid_id);
> +
> +	igt_system_suspend_autoresume(state, test);
> +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +}
> +
> +static void
> +test_display(data_t *data, struct chamelium_port *port)
> +{
> +	igt_display_t display;
> +	igt_output_t *output;
> +	igt_plane_t *primary;
> +	struct igt_fb fb;
> +	drmModeRes *res;
> +	drmModeModeInfo *mode;
> +	int connector_found = false, fb_id;
> +
> +	chamelium_plug(port->id);
> +	igt_assert(res = drmModeGetResources(data->drm_fd));
> +	kmstest_unset_all_crtcs(data->drm_fd, res);
> +
> +	igt_display_init(&display, data->drm_fd);
> +
> +	/* Find the active connector */
> +	for_each_connected_output(&display, output) {
> +		drmModeConnector *connector = output->config.connector;
> +
> +		if (connector && connector->connector_type == port->type &&
> +		    connector->connection == DRM_MODE_CONNECTED) {
> +			connector_found = true;
> +			break;
> +		}
> +	}
> +	igt_assert(connector_found);
> +
> +	/* Setup the display */
> +	igt_output_set_pipe(output, PIPE_A);
> +	mode = igt_output_get_mode(output);
> +	primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
> +	igt_assert(primary);
> +
> +	fb_id = igt_create_pattern_fb(data->drm_fd,
> +				      mode->hdisplay,
> +				      mode->vdisplay,
> +				      DRM_FORMAT_XRGB8888,
> +				      LOCAL_DRM_FORMAT_MOD_NONE,
> +				      &fb);
> +	igt_assert(fb_id > 0);
> +	igt_plane_set_fb(primary, &fb);
> +
> +	igt_display_commit(&display);
> +
> +	igt_assert(chamelium_port_wait_video_input_stable(port->id,
> +							  HOTPLUG_TIMEOUT));
> +
> +	drmModeFreeResources(res);
> +	igt_display_fini(&display);
> +}
> +
> +static void
> +test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> +{
> +	reset_chamelium_state(data);
> +	igt_watch_hotplug();
> +
> +	/* Disable the DDC on the connector and make sure we still get a
> +	 * hotplug
> +	 */
> +	chamelium_port_set_ddc_state(port->id, false);
> +	chamelium_plug(port->id);
> +
> +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> +}
> +
> +static void
> +cache_connector_info(data_t *data)
> +{
> +	drmModeRes *res = drmModeGetResources(data->drm_fd);
> +	drmModeConnector *connector;
> +	int i;
> +
> +	igt_assert(res);
> +
> +	data->connector_count = res->count_connectors;
> +	data->connectors = calloc(sizeof(struct connector_info),
> +				  res->count_connectors);
> +	igt_assert(data->connectors);
> +
> +	for (i = 0; i < res->count_connectors; i++) {
> +		connector = drmModeGetConnectorCurrent(data->drm_fd,
> +						       res->connectors[i]);
> +		igt_assert(connector);
> +
> +		data->connectors[i].id = connector->connector_id;
> +		data->connectors[i].type = connector->connector_type;
> +
> +		drmModeFreeConnector(connector);
> +	}
> +
> +	drmModeFreeResources(res);
> +}
> +
> +#define for_each_port(p, port)                  \
> +	for (p = 0, port = &chamelium_ports[p]; \
> +	     p < chamelium_port_count;          \
> +	     p++, port = &chamelium_ports[p])   \
> +
> +#define connector_subtest(name__, type__) \
> +	igt_subtest(name__)               \
> +		for_each_port(p, port)    \
> +			if (port->type == DRM_MODE_CONNECTOR_ ## type__)
> +
> +#define define_common_connector_tests(type_str__, type__)                     \
> +	connector_subtest(type_str__ "-hpd", type__)                          \
> +		test_basic_hotplug(&data, port);                              \
> +                                                                              \
> +	connector_subtest(type_str__ "-edid-read", type__) {                  \
> +		test_edid_read(&data, port, edid_id,                          \
> +			       igt_kms_get_base_edid());                      \
> +		test_edid_read(&data, port, alt_edid_id,                      \
> +			       igt_kms_get_alt_edid());                       \
> +	}                                                                     \
> +                                                                              \
> +	connector_subtest(type_str__ "-hpd-after-suspend", type__)            \
> +		test_suspend_resume_hpd(&data, port,                          \
> +					SUSPEND_STATE_MEM,                    \
> +					SUSPEND_TEST_NONE);                   \
> +                                                                              \
> +	connector_subtest(type_str__ "-hpd-after-hibernate", type__)          \
> +		test_suspend_resume_hpd(&data, port,                          \
> +					SUSPEND_STATE_DISK,                   \
> +					SUSPEND_TEST_DEVICES);                \
> +                                                                              \
> +	connector_subtest(type_str__ "-edid-change-during-suspend", type__)   \
> +		test_suspend_resume_edid_change(&data, port,                  \
> +						SUSPEND_STATE_MEM,            \
> +						SUSPEND_TEST_NONE,            \
> +						edid_id, alt_edid_id);        \
> +                                                                              \
> +	connector_subtest(type_str__ "-edid-change-during-hibernate", type__) \
> +		test_suspend_resume_edid_change(&data, port,                  \
> +						SUSPEND_STATE_DISK,           \
> +						SUSPEND_TEST_DEVICES,         \
> +						edid_id, alt_edid_id);        \
> +                                                                              \
> +	connector_subtest(type_str__ "-display", type__)                      \
> +		test_display(&data, port);
> +
> +static data_t data;
> +
> +igt_main
> +{
> +	struct chamelium_port *port;
> +	int edid_id, alt_edid_id, p;
> +
> +	igt_fixture {
> +		igt_require_chamelium();
> +		igt_skip_on_simulation();
> +
> +		chamelium_init();
> +
> +		edid_id = chamelium_new_edid(igt_kms_get_base_edid());
> +		alt_edid_id = chamelium_new_edid(igt_kms_get_alt_edid());
> +
> +		data.drm_fd = drm_open_driver_master(DRIVER_INTEL);
> +		cache_connector_info(&data);
> +
> +		/* So fbcon doesn't try to reprobe things itself */
> +		kmstest_set_vt_graphics_mode();
> +	}
> +
> +	igt_subtest_group {
> +		igt_fixture {
> +			require_connector_present(
> +			    &data, DRM_MODE_CONNECTOR_DisplayPort);
> +		}
> +
> +		define_common_connector_tests("dp", DisplayPort);
> +	}
> +
> +	igt_subtest_group {
> +		igt_fixture {
> +			require_connector_present(
> +			    &data, DRM_MODE_CONNECTOR_HDMIA);
> +		}
> +
> +		define_common_connector_tests("hdmi", HDMIA);
> +	}
> +
> +	igt_subtest_group {
> +		igt_fixture {
> +			require_connector_present(
> +			    &data, DRM_MODE_CONNECTOR_VGA);
> +		}
> +
> +		connector_subtest("vga-hpd", VGA)
> +			test_basic_hotplug(&data, port);
> +
> +		connector_subtest("vga-edid-read", VGA) {
> +			test_edid_read(&data, port, edid_id,
> +				       igt_kms_get_base_edid());
> +			test_edid_read(&data, port, alt_edid_id,
> +				       igt_kms_get_alt_edid());
> +		}
> +
> +		/* FIXME: Right now there isn't a way to do any sort of delayed
> +		 * psuedo-hotplug with VGA, so testing detection after a
> +		 * suspend/resume cycle isn't possible yet
> +		 */
> +
> +		connector_subtest("vga-hpd-without-ddc", VGA)
> +			test_hpd_without_ddc(&data, port);
> +
> +		connector_subtest("vga-display", VGA)
> +			test_display(&data, port);
> +	}
> +}
> -- 
> 2.7.4
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium
  2016-11-14  7:05   ` Daniel Vetter
@ 2016-11-14 16:46     ` Lyude Paul
  0 siblings, 0 replies; 16+ messages in thread
From: Lyude Paul @ 2016-11-14 16:46 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: intel-gfx

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

On Mon, 2016-11-14 at 08:05 +0100, Daniel Vetter wrote:
> On Mon, Nov 07, 2016 at 07:05:16PM -0500, Lyude wrote:
> > 
> > For the purpose of testing things such as hotplugging and bad
> > monitors,
> > the ChromeOS team ended up designing a neat little device known as
> > the
> > Chamelium. More information on this can be found here:
> > 
> > 	https://www.chromium.org/chromium-os/testing/chamelium
> > 
> > This adds support for a couple of things to intel-gpu-tools:
> >  - igt library functions for connecting to udev and monitoring it
> > for
> >    hotplug events, loosely based off of the unfinished hotplugging
> >    implementation in testdisplay
> >  - Library functions for controlling the chamelium in tests using
> >    xmlrpc. A couple of RPC calls were ommitted here, mainly because
> > they
> >    didn't seem very useful for our needs or because they're just
> > plain
> >    broken
> >  - A set of basic tests using the chamelium.
> > 
> > Because there's no surefire way that I know of where we can map
> > which
> > chamelium port belongs to which port on the system being tested (we
> > could just use hotplugging, but then we'd be relying on something
> > that
> > might be broken on the machine and potentially give false positives
> > for
> > certain tests), most of the chamelium tests will figure out whether
> > or
> > not a connection happened by counting the number of connectors
> > matching
> > the status we're looking for before hotplugging with the chamelium,
> > vs.
> > after hotplugging it.
> > 
> > Tests which require that we know which port belongs to a certain
> > port
> > (such as ones where we actually perform a modeset) will unplug all
> > of
> > the chamelium ports, plug the desired port, then use the first DRM
> > connector with the desired connector type that's marked as
> > connected. In
> > order to ensure we don't end up using the wrong connector, these
> > tests
> > will skip if they find any connectors with the desired type marked
> > as
> > connected before performing the hotplug on the chamelium.
> > 
> > Running these tests requires (of course) a working Chamelium, along
> > with
> > the RPC URL for the chamelium being specified in the environment
> > variable CHAMELIUM_HOST. If no URL is specified, the tests will
> > just
> > skip on their own. As well, tests for connectors which are not
> > actually
> > present on the system or the chamelium will skip on their own as
> > well.
> > 
> > Signed-off-by: Lyude <lyude@redhat.com>
> > ---
> >  configure.ac           |  13 +
> >  lib/Makefile.am        |  10 +-
> >  lib/igt.h              |   1 +
> >  lib/igt_chamelium.c    | 628
> > +++++++++++++++++++++++++++++++++++++++++++++++++
> >  lib/igt_chamelium.h    |  77 ++++++
> 
> Since you typed these nice gtkdocs, please also add it to the .xml in
> docs/ and make sure it looks all good (./autogen.sh --enable-gtk-
> docs).
> 
> Wrt the api itself I think all we need is agreement from Tomeu that
> this
> is the right thing for his chamelium use-cases, too. And Tomeu has
> commit
> rights, so can push this stuff for you.
> -Daniel
> 
> 
> > 
> >  lib/igt_kms.c          | 107 +++++++++
> >  lib/igt_kms.h          |  13 +-
> >  scripts/run-tests.sh   |   4 +-
> >  tests/Makefile.am      |   5 +-
> >  tests/Makefile.sources |   1 +
> >  tests/chamelium.c      | 549
> > ++++++++++++++++++++++++++++++++++++++++++
> >  11 files changed, 1403 insertions(+), 5 deletions(-)
> >  create mode 100644 lib/igt_chamelium.c
> >  create mode 100644 lib/igt_chamelium.h
> >  create mode 100644 tests/chamelium.c
> > 
> > diff --git a/configure.ac b/configure.ac
> > index 735cfd5..88113b2 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -259,6 +259,18 @@ if test "x$with_libunwind" = xyes; then
> >  			  AC_MSG_ERROR([libunwind not found. Use
> > --without-libunwind to disable libunwind support.]))
> >  fi
> >  
> > +# enable support for using the chamelium
> > +AC_ARG_ENABLE(chamelium,
> > +	      AS_HELP_STRING([--without-chamelium],
> > +			     [Build tests without chamelium
> > support]),
> > +	      [], [with_chamelium=yes])
> > +
> > +AM_CONDITIONAL(HAVE_CHAMELIUM, [test "x$with_chamelium" = xyes])
> > +if test "x$with_chamelium" = xyes; then
> > +	AC_DEFINE(HAVE_CHAMELIUM, 1, [chamelium suport])
> > +	PKG_CHECK_MODULES(XMLRPC, xmlrpc_client)
> > +fi
> > +
> >  # enable debug symbols
> >  AC_ARG_ENABLE(debug,
> >  	      AS_HELP_STRING([--disable-debug],
> > @@ -356,6 +368,7 @@ echo "       Assembler          :
> > ${enable_assembler}"
> >  echo "       Debugger           : ${enable_debugger}"
> >  echo "       Overlay            : X: ${enable_overlay_xlib}, Xv:
> > ${enable_overlay_xvlib}"
> >  echo "       x86-specific tools : ${build_x86}"
> > +echo "       Chamelium support  : ${with_chamelium}"
> >  echo ""
> >  echo " • API-Documentation      : ${enable_gtk_doc}"
> >  echo " • Fail on warnings       : ${enable_werror}"
> > diff --git a/lib/Makefile.am b/lib/Makefile.am
> > index 4c0893d..aeac43a 100644
> > --- a/lib/Makefile.am
> > +++ b/lib/Makefile.am
> > @@ -22,8 +22,14 @@ if !HAVE_LIBDRM_INTEL
> >          stubs/drm/intel_bufmgr.h
> >  endif
> >  
> > +if HAVE_CHAMELIUM
> > +    libintel_tools_la_SOURCES +=	\
> > +	igt_chamelium.c			\
> > +	igt_chamelium.h
> > +endif
> > +
> >  AM_CPPFLAGS = -I$(top_srcdir)
> > -AM_CFLAGS = $(CWARNFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS)
> > $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) \
> > +AM_CFLAGS = $(CWARNFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS)
> > $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) $(XMLRPC_CFLAGS) $(UDEV_CFLAGS)
> > \
> >  	    -DIGT_SRCDIR=\""$(abs_top_srcdir)/tests"\" \
> >  	    -DIGT_DATADIR=\""$(pkgdatadir)"\" \
> >  	    -DIGT_LOG_DOMAIN=\""$(subst _,-,$*)"\" \
> > @@ -38,5 +44,7 @@ libintel_tools_la_LIBADD = \
> >  	$(LIBUDEV_LIBS) \
> >  	$(LIBUNWIND_LIBS) \
> >  	$(TIMER_LIBS) \
> > +	$(XMLRPC_LIBS) \
> > +	$(UDEV_LIBS) \
> >  	-lm
> >  
> > diff --git a/lib/igt.h b/lib/igt.h
> > index d751f24..0ea03e4 100644
> > --- a/lib/igt.h
> > +++ b/lib/igt.h
> > @@ -30,6 +30,7 @@
> >  #include "igt_aux.h"
> >  #include "igt_core.h"
> >  #include "igt_core.h"
> > +#include "igt_chamelium.h"
> >  #include "igt_debugfs.h"
> >  #include "igt_draw.h"
> >  #include "igt_fb.h"
> > diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
> > new file mode 100644
> > index 0000000..a281ef6
> > --- /dev/null
> > +++ b/lib/igt_chamelium.c
> > @@ -0,0 +1,628 @@
> > +/*
> > + * Copyright © 2016 Red Hat Inc.
> > + *
> > + * Permission is hereby granted, free of charge, to any person
> > obtaining a
> > + * copy of this software and associated documentation files (the
> > "Software"),
> > + * to deal in the Software without restriction, including without
> > limitation
> > + * the rights to use, copy, modify, merge, publish, distribute,
> > sublicense,
> > + * and/or sell copies of the Software, and to permit persons to
> > whom the
> > + * Software is furnished to do so, subject to the following
> > conditions:
> > + *
> > + * The above copyright notice and this permission notice
> > (including the next
> > + * paragraph) shall be included in all copies or substantial
> > portions of the
> > + * Software.
> > + *
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> > EXPRESS OR
> > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> > MERCHANTABILITY,
> > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO
> > EVENT SHALL
> > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
> > DAMAGES OR OTHER
> > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> > ARISING
> > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> > OTHER DEALINGS
> > + * IN THE SOFTWARE.
> > + *
> > + * Authors:
> > + *  Lyude Paul <lyude@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@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@redhat.com>
> > + */
> > +
> > +#include "config.h"
> > +#include "igt.h"
> > +
> > +#include <fcntl.h>
> > +#include <string.h>
> > +
> > +struct connector_info {
> > +	int id;
> > +	unsigned int type;
> > +};
> > +
> > +typedef struct {
> > +	int drm_fd;
> > +	struct connector_info *connectors;
> > +	int connector_count;
> > +} data_t;
> > +
> > +#define HOTPLUG_TIMEOUT 30 /* seconds */
> > +
> > +/*
> > + * Since we can't get an exact mapping of which chamelium ports
> > are connected
> > + * to each of the DUT's ports, we have to figure out whether or
> > not the status
> > + * of a port on the chamelium has changed by counting the number
> > of connectors
> > + * with the connector type and status we want, and then comparing
> > the values
> > + * from before hotplugging and after
> > + */
> > +static void
> > +reprobe_connectors(data_t *data, unsigned int type)
> > +{
> > +	drmModeConnector *connector;
> > +	int i;
> > +
> > +	igt_debug("Reprobing %s connectors...\n",
> > +		  kmstest_connector_type_str(type));
> > +
> > +	for (i = 0; i < data->connector_count; i++) {
> > +		if (data->connectors[i].type != type)
> > +			continue;
> > +
> > +		connector = drmModeGetConnector(data->drm_fd,
> > +						data-
> > >connectors[i].id);
> > +		igt_assert(connector);
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +}
> > +
> > +static void
> > +reset_chamelium_state(data_t *data)
> > +{
> > +	chamelium_reset();
> > +	reprobe_connectors(data, DRM_MODE_CONNECTOR_DisplayPort);
> > +	reprobe_connectors(data, DRM_MODE_CONNECTOR_HDMIA);
> > +	reprobe_connectors(data, DRM_MODE_CONNECTOR_VGA);
> > +}
> > +
> > +static int
> > +connector_status_count(data_t *data, unsigned int type, unsigned
> > int status)
> > +{
> > +	struct connector_info *info;
> > +	drmModeConnector *connector;
> > +	int count = 0;
> > +
> > +	for (int i = 0; i < data->connector_count; i++) {
> > +		info = &data->connectors[i];
> > +		if (info->type != type)
> > +			continue;
> > +
> > +		connector = drmModeGetConnectorCurrent(data-
> > >drm_fd, info->id);
> > +		igt_assert(connector);
> > +
> > +		if (connector->connection == status)
> > +			count++;
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +
> > +	return count;
> > +}
> > +
> > +static void
> > +require_connector_present(data_t *data, unsigned int type)
> > +{
> > +	int i;
> > +	bool found = false;
> > +
> > +	for (i = 0; i < data->connector_count && !found; i++) {
> > +		if (data->connectors[i].type == type)
> > +			found = true;
> > +	}
> > +
> > +	igt_require_f(found, "No port of type %s was found on the
> > system\n",
> > +		      kmstest_connector_type_str(type));
> > +
> > +	for (i = 0, found = false; i < chamelium_port_count &&
> > !found; i++) {
> > +		if (chamelium_ports[i].type == type)
> > +			found = true;
> > +	}
> > +
> > +	igt_require_f(found, "No connected port of type %s was
> > found on the chamelium\n",
> > +		      kmstest_connector_type_str(type));
> > +}
> > +
> > +static drmModeConnector *
> > +find_connected(data_t *data, unsigned int type)
> > +{
> > +	drmModeConnector *connector;
> > +	int i;
> > +
> > +	for (i = 0; i < data->connector_count; i++) {
> > +		if (data->connectors[i].type != type)
> > +			continue;
> > +
> > +		connector = drmModeGetConnector(data->drm_fd,
> > +						data-
> > >connectors[i].id);
> > +		igt_assert(connector);
> > +
> > +		if (connector->connection == DRM_MODE_CONNECTED)
> > +			return connector;
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +/*
> > + * Skips the test if we find any connectors with a matching type
> > connected.
> > + * This is necessary when we need to identify which port on the
> > machine is
> > + * connected to which port on the chamelium, since any other ports
> > that are
> > + * connected to other displays could cause us to choose the wrong
> > port.
> > + *
> > + * This also has the effect of reprobing all of the connected
> > ports.
> > + */
> > +static void
> > +skip_on_any_connected(data_t *data, unsigned int type)
> > +{
> > +	drmModeConnector *connector;
> > +
> > +	connector = find_connected(data, type);
> > +	if (connector)
> > +		drmModeFreeConnector(connector);
> > +
> > +	igt_skip_on(connector);
> > +}
> > +
> > +static void
> > +test_basic_hotplug(data_t *data, struct chamelium_port *port)
> > +{
> > +	int before, after;
> > +	int i;
> > +
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	for (i = 0; i < 15; i++) {
> > +		igt_flush_hotplugs();
> > +
> > +		/* Check if we get a sysfs hotplug event */
> > +		before = connector_status_count(data, port->type,
> > +						DRM_MODE_CONNECTED
> > );
> > +		chamelium_plug(port->id);
> > +		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +		/* Now we should have one additional port
> > connected */
> > +		reprobe_connectors(data, port->type);
> > +		after = connector_status_count(data, port->type,
> > +					       DRM_MODE_CONNECTED)
> > ;
> > +		igt_assert_lt(before, after);
> > +
> > +		igt_flush_hotplugs();
> > +
> > +		/* Now check if we get a hotplug from
> > disconnection */
> > +		before = connector_status_count(data, port->type,
> > +						DRM_MODE_DISCONNEC
> > TED);
> > +		chamelium_unplug(port->id);
> > +		igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +		/* And make sure we now have one more disconnected
> > port */
> > +		reprobe_connectors(data, port->type);
> > +		after = connector_status_count(data, port->type,
> > +					       DRM_MODE_DISCONNECT
> > ED);
> > +		igt_assert_lt(before, after);
> > +
> > +		/* Sleep so we don't accidentally cause an hpd
> > storm */
> > +		sleep(1);
> > +	}
> > +}
> > +
> > +static void
> > +test_edid_read(data_t *data, struct chamelium_port *port,
> > +	       int edid_id, const unsigned char *edid)
> > +{
> > +	drmModeConnector *connector;
> > +	drmModeObjectProperties *props;
> > +	drmModePropertyBlobPtr edid_blob = NULL;
> > +	bool edid_found = false;
> > +	int i;
> > +
> > +	reset_chamelium_state(data);
> > +	skip_on_any_connected(data, port->type);
> > +
> > +	chamelium_port_set_edid(port->id, edid_id);
> > +	chamelium_plug(port->id);
> > +	sleep(1);
> > +	igt_assert(connector = find_connected(data, port->type));
> > +
> > +	props = drmModeObjectGetProperties(data->drm_fd,
> > +					   connector-
> > >connector_id,
> > +					   DRM_MODE_OBJECT_CONNECT
> > OR);
> > +	igt_assert(props);
> > +
> > +	/* Get the edid */
> > +	for (i = 0; i < props->count_props && !edid_blob; i++) {
> > +		drmModePropertyPtr prop =
> > +			drmModeGetProperty(data->drm_fd,
> > +					   props->props[i]);
> > +
> > +		igt_assert(prop);
> > +
> > +		if (strcmp(prop->name, "EDID") == 0) {
> > +			edid_blob = drmModeGetPropertyBlob(
> > +			    data->drm_fd, props->prop_values[i]);
> > +		}
> > +
> > +		drmModeFreeProperty(prop);
> > +	}
> > +
> > +	/* And make sure it matches to what we expected */
> > +	edid_found = memcmp(edid, edid_blob->data, EDID_LENGTH) ==
> > 0;
> > +
> > +	drmModeFreePropertyBlob(edid_blob);
> > +	drmModeFreeObjectProperties(props);
> > +	drmModeFreeConnector(connector);
> > +
> > +	igt_assert(edid_found);
> > +}
> > +
> > +static void
> > +test_suspend_resume_hpd(data_t *data, struct chamelium_port *port,
> > +			enum igt_suspend_state state,
> > +			enum igt_suspend_test test)
> > +{
> > +	int before, after;
> > +	int delay = 7;
> > +
> > +	igt_skip_without_suspend_support(state, test);
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	igt_set_autoresume_delay(15);
> > +
> > +	/* Make sure we notice new connectors after resuming */
> > +	before = connector_status_count(data, port->type,
> > DRM_MODE_CONNECTED);
> > +	sleep(1);
> > +	igt_flush_hotplugs();
> > +
> > +	chamelium_async_hpd_pulse_start(port->id, false, delay);
> > +	igt_system_suspend_autoresume(state, test);
> > +	chamelium_async_hpd_pulse_finish();
> > +
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +	reprobe_connectors(data, port->type);
> > +	after = connector_status_count(data, port->type,
> > DRM_MODE_CONNECTED);
> > +	igt_assert_lt(before, after);
> > +
> > +	igt_flush_hotplugs();
> > +
> > +	/* Now make sure we notice disconnected connectors after
> > resuming */
> > +	before = connector_status_count(data, port->type,
> > DRM_MODE_DISCONNECTED);
> > +
> > +	chamelium_async_hpd_pulse_start(port->id, true, delay);
> > +	igt_system_suspend_autoresume(state, test);
> > +	chamelium_async_hpd_pulse_finish();
> > +
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +
> > +	reprobe_connectors(data, port->type);
> > +	after = connector_status_count(data, port->type,
> > DRM_MODE_DISCONNECTED);
> > +	igt_assert_lt(before, after);
> > +}
> > +
> > +static void
> > +test_suspend_resume_edid_change(data_t *data, struct
> > chamelium_port *port,
> > +				enum igt_suspend_state state,
> > +				enum igt_suspend_test test,
> > +				int edid_id,
> > +				int alt_edid_id)
> > +{
> > +	igt_skip_without_suspend_support(state, test);
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	/* First plug in the port */
> > +	chamelium_port_set_edid(port->id, edid_id);
> > +	chamelium_plug(port->id);
> > +
> > +	reprobe_connectors(data, port->type);
> > +	sleep(1);
> > +	igt_flush_hotplugs();
> > +
> > +	/*
> > +	 * Change the edid before we suspend. On resume, the
> > machine should
> > +	 * notice the EDID change and fire a hotplug event.
> > +	 */
> > +	chamelium_port_set_edid(port->id, alt_edid_id);
> > +
> > +	igt_system_suspend_autoresume(state, test);
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +}
> > +
> > +static void
> > +test_display(data_t *data, struct chamelium_port *port)
> > +{
> > +	igt_display_t display;
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +	struct igt_fb fb;
> > +	drmModeRes *res;
> > +	drmModeModeInfo *mode;
> > +	int connector_found = false, fb_id;
> > +
> > +	chamelium_plug(port->id);
> > +	igt_assert(res = drmModeGetResources(data->drm_fd));
> > +	kmstest_unset_all_crtcs(data->drm_fd, res);
> > +
> > +	igt_display_init(&display, data->drm_fd);
> > +
> > +	/* Find the active connector */
> > +	for_each_connected_output(&display, output) {
> > +		drmModeConnector *connector = output-
> > >config.connector;
> > +
> > +		if (connector && connector->connector_type ==
> > port->type &&
> > +		    connector->connection == DRM_MODE_CONNECTED) {
> > +			connector_found = true;
> > +			break;
> > +		}
> > +	}
> > +	igt_assert(connector_found);
> > +
> > +	/* Setup the display */
> > +	igt_output_set_pipe(output, PIPE_A);
> > +	mode = igt_output_get_mode(output);
> > +	primary = igt_output_get_plane(output, IGT_PLANE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	fb_id = igt_create_pattern_fb(data->drm_fd,
> > +				      mode->hdisplay,
> > +				      mode->vdisplay,
> > +				      DRM_FORMAT_XRGB8888,
> > +				      LOCAL_DRM_FORMAT_MOD_NONE,
> > +				      &fb);
> > +	igt_assert(fb_id > 0);
> > +	igt_plane_set_fb(primary, &fb);
> > +
> > +	igt_display_commit(&display);
> > +
> > +	igt_assert(chamelium_port_wait_video_input_stable(port-
> > >id,
> > +							  HOTPLUG_
> > TIMEOUT));
> > +
> > +	drmModeFreeResources(res);
> > +	igt_display_fini(&display);
> > +}
> > +
> > +static void
> > +test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> > +{
> > +	reset_chamelium_state(data);
> > +	igt_watch_hotplug();
> > +
> > +	/* Disable the DDC on the connector and make sure we still
> > get a
> > +	 * hotplug
> > +	 */
> > +	chamelium_port_set_ddc_state(port->id, false);
> > +	chamelium_plug(port->id);
> > +
> > +	igt_assert(igt_hotplug_detected(HOTPLUG_TIMEOUT));
> > +}
> > +
> > +static void
> > +cache_connector_info(data_t *data)
> > +{
> > +	drmModeRes *res = drmModeGetResources(data->drm_fd);
> > +	drmModeConnector *connector;
> > +	int i;
> > +
> > +	igt_assert(res);
> > +
> > +	data->connector_count = res->count_connectors;
> > +	data->connectors = calloc(sizeof(struct connector_info),
> > +				  res->count_connectors);
> > +	igt_assert(data->connectors);
> > +
> > +	for (i = 0; i < res->count_connectors; i++) {
> > +		connector = drmModeGetConnectorCurrent(data-
> > >drm_fd,
> > +						       res-
> > >connectors[i]);
> > +		igt_assert(connector);
> > +
> > +		data->connectors[i].id = connector->connector_id;
> > +		data->connectors[i].type = connector-
> > >connector_type;
> > +
> > +		drmModeFreeConnector(connector);
> > +	}
> > +
> > +	drmModeFreeResources(res);
> > +}
> > +
> > +#define for_each_port(p, port)                  \
> > +	for (p = 0, port = &chamelium_ports[p]; \
> > +	     p < chamelium_port_count;          \
> > +	     p++, port = &chamelium_ports[p])   \
> > +
> > +#define connector_subtest(name__, type__) \
> > +	igt_subtest(name__)               \
> > +		for_each_port(p, port)    \
> > +			if (port->type == DRM_MODE_CONNECTOR_ ##
> > type__)
> > +
> > +#define define_common_connector_tests(type_str__,
> > type__)                     \
> > +	connector_subtest(type_str__ "-hpd",
> > type__)                          \
> > +		test_basic_hotplug(&data,
> > port);                              \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-edid-read", type__)
> > {                  \
> > +		test_edid_read(&data, port,
> > edid_id,                          \
> > +			       igt_kms_get_base_edid());          
> >             \
> > +		test_edid_read(&data, port,
> > alt_edid_id,                      \
> > +			       igt_kms_get_alt_edid());           
> >             \
> > +	}                                                         
> >             \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-hpd-after-suspend",
> > type__)            \
> > +		test_suspend_resume_hpd(&data,
> > port,                          \
> > +					SUSPEND_STATE_MEM,        
> >             \
> > +					SUSPEND_TEST_NONE);       
> >             \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-hpd-after-hibernate",
> > type__)          \
> > +		test_suspend_resume_hpd(&data,
> > port,                          \
> > +					SUSPEND_STATE_DISK,       
> >             \
> > +					SUSPEND_TEST_DEVICES);    
> >             \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-edid-change-during-
> > suspend", type__)   \
> > +		test_suspend_resume_edid_change(&data,
> > port,                  \
> > +						SUSPEND_STATE_MEM,
> >             \
> > +						SUSPEND_TEST_NONE,
> >             \
> > +						edid_id,
> > alt_edid_id);        \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-edid-change-during-
> > hibernate", type__) \
> > +		test_suspend_resume_edid_change(&data,
> > port,                  \
> > +						SUSPEND_STATE_DISK
> > ,           \
> > +						SUSPEND_TEST_DEVIC
> > ES,         \
> > +						edid_id,
> > alt_edid_id);        \
> > +                                                                  
> >             \
> > +	connector_subtest(type_str__ "-display",
> > type__)                      \
> > +		test_display(&data, port);
> > +
> > +static data_t data;
> > +
> > +igt_main
> > +{
> > +	struct chamelium_port *port;
> > +	int edid_id, alt_edid_id, p;
> > +
> > +	igt_fixture {
> > +		igt_require_chamelium();
> > +		igt_skip_on_simulation();
> > +
> > +		chamelium_init();
> > +
> > +		edid_id =
> > chamelium_new_edid(igt_kms_get_base_edid());
> > +		alt_edid_id =
> > chamelium_new_edid(igt_kms_get_alt_edid());
> > +
> > +		data.drm_fd =
> > drm_open_driver_master(DRIVER_INTEL);
> > +		cache_connector_info(&data);
> > +
> > +		/* So fbcon doesn't try to reprobe things itself
> > */
> > +		kmstest_set_vt_graphics_mode();
> > +	}
> > +
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			require_connector_present(
> > +			    &data,
> > DRM_MODE_CONNECTOR_DisplayPort);
> > +		}
> > +
> > +		define_common_connector_tests("dp", DisplayPort);
> > +	}
> > +
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			require_connector_present(
> > +			    &data, DRM_MODE_CONNECTOR_HDMIA);
> > +		}
> > +
> > +		define_common_connector_tests("hdmi", HDMIA);
> > +	}
> > +
> > +	igt_subtest_group {
> > +		igt_fixture {
> > +			require_connector_present(
> > +			    &data, DRM_MODE_CONNECTOR_VGA);
> > +		}
> > +
> > +		connector_subtest("vga-hpd", VGA)
> > +			test_basic_hotplug(&data, port);
> > +
> > +		connector_subtest("vga-edid-read", VGA) {
> > +			test_edid_read(&data, port, edid_id,
> > +				       igt_kms_get_base_edid());
> > +			test_edid_read(&data, port, alt_edid_id,
> > +				       igt_kms_get_alt_edid());
> > +		}
> > +
> > +		/* FIXME: Right now there isn't a way to do any
> > sort of delayed
> > +		 * psuedo-hotplug with VGA, so testing detection
> > after a
> > +		 * suspend/resume cycle isn't possible yet
> > +		 */
> > +
> > +		connector_subtest("vga-hpd-without-ddc", VGA)
> > +			test_hpd_without_ddc(&data, port);
> > +
> > +		connector_subtest("vga-display", VGA)
> > +			test_display(&data, port);
> > +	}
> > +}
> > -- 
> > 2.7.4
> > 
> > _______________________________________________
> > Intel-gfx mailing list
> > Intel-gfx@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
> 
-- 
Cheers,
	Lyude
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
  2016-11-11 17:53   ` Lyude Paul
@ 2016-11-15 11:44     ` Tomeu Vizoso
  2016-11-15 21:44       ` Lyude Paul
  0 siblings, 1 reply; 16+ messages in thread
From: Tomeu Vizoso @ 2016-11-15 11:44 UTC (permalink / raw)
  To: Lyude Paul; +Cc: Intel Graphics Development

On 11 November 2016 at 18:53, Lyude Paul <lyude@redhat.com> wrote:
> Alright, quick question: should we be going with your branch then or
> mine?

I'm not going to be able to work on this in the short term, so I think
it's up to you.

Wonder if there are more opinions regarding xmlrpc vs. libsoup. I
liked it mostly because we already depend on glib.

> On Wed, 2016-11-09 at 16:09 +0100, Tomeu Vizoso wrote:
>> Hi Lyude,
>>
>> I think this looks very good.
>>
>> On 8 November 2016 at 01:05, Lyude <lyude@redhat.com> wrote:
>> >
>> >
>> >  - While writing this patch series, I found that quite a few of the
>> > RPC calls
>> >    for chameleond don't work as expected. For instance, I have had
>> > absolutely
>> >    no luck getting CRCs from any of the display types that the
>> > chamelium
>> >    supports.
>>
>> When I looked at this a few months ago, frame CRCs were working just
>> fine. I was using libsoup, so maybe there's some problem with the
>> unpacking of the checksum?
>
> I'm pretty sure it's on the chameleond side of things. Using the test
> server application in chameleond's source shows the same issue.

And what's the problem? You always get CRCs with a value of zero? I
only tried with HDMI, but IIRC I got to a point where
kms_universal_plane passed.

Regards,

Tomeu

>> >
>> > This isn't a huge deal though, since we usually just use the
>> >    native CRC read back on the GPU anyway.
>>
>> I'm not completely sure what you mean by that, but not all graphic
>> pipelines are able to provide frame CRCs so I think this Chamelium
>> work will be very useful when running tests that do check frame CRCs.
> I wasn't aware of that, thanks for letting me know
>
>>
>> Regards,
>>
>> Tomeu
>>
>> >
>> >
>> >  - Among other things that are broken with the chameleon, video
>> > signal
>> >    detection for DisplayPort is one of them. After the first
>> > plug/unplug cycle,
>> >    the DisplayPort receiver gets stuck and gives the wrong results
>> > for
>> >    WaitForInputStable. Luckily I've already got a fix I'll be
>> > submitting to the
>> >    ChromeOS guys when I get around to setting up their homebrew git
>> > tools:
>> >
>> >         https://github.com/Lyude/chameleond/tree/wip/chameleon-fixe
>> > s
>> >
>> >    For now, expect the dp-display tests to fail without those
>> > patches.
>> >
>> > Lyude (4):
>> >   igt_aux: Add igt_skip_without_suspend_support()
>> >   igt_aux: Add igt_set_autoresume_delay()
>> >   igt_aux: Add some list helpers from wayland
>> >   Add support for hotplug testing with the Chamelium
>> >
>> >  configure.ac           |  13 +
>> >  lib/Makefile.am        |  10 +-
>> >  lib/igt.h              |   1 +
>> >  lib/igt_aux.c          |  94 ++++++++
>> >  lib/igt_aux.h          |  41 ++++
>> >  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
>> > ++++++++++++++++++++++++++++++++++++++++++
>> >  13 files changed, 1538 insertions(+), 5 deletions(-)
>> >  create mode 100644 lib/igt_chamelium.c
>> >  create mode 100644 lib/igt_chamelium.h
>> >  create mode 100644 tests/chamelium.c
>> >
>> > --
>> > 2.7.4
>> >
>> > _______________________________________________
>> > Intel-gfx mailing list
>> > Intel-gfx@lists.freedesktop.org
>> > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
> --
> Cheers,
>         Lyude
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
  2016-11-15 11:44     ` Tomeu Vizoso
@ 2016-11-15 21:44       ` Lyude Paul
  2016-11-16 11:52         ` Tomeu Vizoso
  0 siblings, 1 reply; 16+ messages in thread
From: Lyude Paul @ 2016-11-15 21:44 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: Intel Graphics Development

I'm fine with libsoup as well, I'll check it out and probably move all
of the code over to using that instead.

On Tue, 2016-11-15 at 12:44 +0100, Tomeu Vizoso wrote:
> On 11 November 2016 at 18:53, Lyude Paul <lyude@redhat.com> wrote:
> > 
> > Alright, quick question: should we be going with your branch then
> > or
> > mine?
> 
> I'm not going to be able to work on this in the short term, so I
> think
> it's up to you.
> 
> Wonder if there are more opinions regarding xmlrpc vs. libsoup. I
> liked it mostly because we already depend on glib.
> 
> > 
> > On Wed, 2016-11-09 at 16:09 +0100, Tomeu Vizoso wrote:
> > > 
> > > Hi Lyude,
> > > 
> > > I think this looks very good.
> > > 
> > > On 8 November 2016 at 01:05, Lyude <lyude@redhat.com> wrote:
> > > > 
> > > > 
> > > > 
> > > >  - While writing this patch series, I found that quite a few of
> > > > the
> > > > RPC calls
> > > >    for chameleond don't work as expected. For instance, I have
> > > > had
> > > > absolutely
> > > >    no luck getting CRCs from any of the display types that the
> > > > chamelium
> > > >    supports.
> > > 
> > > When I looked at this a few months ago, frame CRCs were working
> > > just
> > > fine. I was using libsoup, so maybe there's some problem with the
> > > unpacking of the checksum?
> > 
> > I'm pretty sure it's on the chameleond side of things. Using the
> > test
> > server application in chameleond's source shows the same issue.
> 
> And what's the problem? You always get CRCs with a value of zero? I
> only tried with HDMI, but IIRC I got to a point where
> kms_universal_plane passed.
The issue is I don't get CRCs, period :(. It looks like somewhere down
the line chameleond is throwing a big fit whenever I try to get them:

Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> p.Plug(1)
>>> p.ComputePixelChecksum(1, 0, 0, 1920, 1080)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1243, in __call__
    return self.__send(self.__name, args)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1602, in __request
    verbose=self.__verbose
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1283, in request
    return self.single_request(host, handler, request_body, verbose)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1316, in
single_request
    return self.parse_response(response)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1493, in
parse_response
    return u.close()
  File "/usr/lib64/python2.7/xmlrpclib.py", line 800, in close
    raise Fault(**self._stack[0])
Fault: <Fault 1: "<type 'exceptions.OSError'>:[Errno 8] Exec format
error">
>>> p.ComputePixelChecksum(1, 0, 0, 1920, 1080)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1243, in __call__
    return self.__send(self.__name, args)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1602, in __request
    verbose=self.__verbose
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1283, in request
    return self.single_request(host, handler, request_body, verbose)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1316, in
single_request
    return self.parse_response(response)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1493, in
parse_response
    return u.close()
  File "/usr/lib64/python2.7/xmlrpclib.py", line 800, in close
    raise Fault(**self._stack[0])
Fault: <Fault 1: "<type 'exceptions.OSError'>:[Errno 8] Exec format
error">

Haven't had much luck with any of the other ports either

> 
> Regards,
> 
> Tomeu
> 
> > 
> > > 
> > > > 
> > > > 
> > > > This isn't a huge deal though, since we usually just use the
> > > >    native CRC read back on the GPU anyway.
> > > 
> > > I'm not completely sure what you mean by that, but not all
> > > graphic
> > > pipelines are able to provide frame CRCs so I think this
> > > Chamelium
> > > work will be very useful when running tests that do check frame
> > > CRCs.
> > I wasn't aware of that, thanks for letting me know
> > 
> > > 
> > > 
> > > Regards,
> > > 
> > > Tomeu
> > > 
> > > > 
> > > > 
> > > > 
> > > >  - Among other things that are broken with the chameleon, video
> > > > signal
> > > >    detection for DisplayPort is one of them. After the first
> > > > plug/unplug cycle,
> > > >    the DisplayPort receiver gets stuck and gives the wrong
> > > > results
> > > > for
> > > >    WaitForInputStable. Luckily I've already got a fix I'll be
> > > > submitting to the
> > > >    ChromeOS guys when I get around to setting up their homebrew
> > > > git
> > > > tools:
> > > > 
> > > >         https://github.com/Lyude/chameleond/tree/wip/chameleon-
> > > > fixe
> > > > s
> > > > 
> > > >    For now, expect the dp-display tests to fail without those
> > > > patches.
> > > > 
> > > > Lyude (4):
> > > >   igt_aux: Add igt_skip_without_suspend_support()
> > > >   igt_aux: Add igt_set_autoresume_delay()
> > > >   igt_aux: Add some list helpers from wayland
> > > >   Add support for hotplug testing with the Chamelium
> > > > 
> > > >  configure.ac           |  13 +
> > > >  lib/Makefile.am        |  10 +-
> > > >  lib/igt.h              |   1 +
> > > >  lib/igt_aux.c          |  94 ++++++++
> > > >  lib/igt_aux.h          |  41 ++++
> > > >  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
> > > > ++++++++++++++++++++++++++++++++++++++++++
> > > >  13 files changed, 1538 insertions(+), 5 deletions(-)
> > > >  create mode 100644 lib/igt_chamelium.c
> > > >  create mode 100644 lib/igt_chamelium.h
> > > >  create mode 100644 tests/chamelium.c
> > > > 
> > > > --
> > > > 2.7.4
> > > > 
> > > > _______________________________________________
> > > > Intel-gfx mailing list
> > > > Intel-gfx@lists.freedesktop.org
> > > > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
> > --
> > Cheers,
> >         Lyude
-- 
Cheers,
	Lyude
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
  2016-11-15 21:44       ` Lyude Paul
@ 2016-11-16 11:52         ` Tomeu Vizoso
  2016-11-28 20:10           ` Lyude Paul
  0 siblings, 1 reply; 16+ messages in thread
From: Tomeu Vizoso @ 2016-11-16 11:52 UTC (permalink / raw)
  To: Lyude Paul; +Cc: Intel Graphics Development

On 15 November 2016 at 22:44, Lyude Paul <lyude@redhat.com> wrote:
> I'm fine with libsoup as well, I'll check it out and probably move all
> of the code over to using that instead.

Cool.

> On Tue, 2016-11-15 at 12:44 +0100, Tomeu Vizoso wrote:
>> On 11 November 2016 at 18:53, Lyude Paul <lyude@redhat.com> wrote:
>> > > >  - While writing this patch series, I found that quite a few of
>> > > > the
>> > > > RPC calls
>> > > >    for chameleond don't work as expected. For instance, I have
>> > > > had
>> > > > absolutely
>> > > >    no luck getting CRCs from any of the display types that the
>> > > > chamelium
>> > > >    supports.
>> > >
>> > > When I looked at this a few months ago, frame CRCs were working
>> > > just
>> > > fine. I was using libsoup, so maybe there's some problem with the
>> > > unpacking of the checksum?
>> >
>> > I'm pretty sure it's on the chameleond side of things. Using the
>> > test
>> > server application in chameleond's source shows the same issue.
>>
>> And what's the problem? You always get CRCs with a value of zero? I
>> only tried with HDMI, but IIRC I got to a point where
>> kms_universal_plane passed.
> The issue is I don't get CRCs, period :(. It looks like somewhere down
> the line chameleond is throwing a big fit whenever I try to get them:

Have no good ideas right now, and it will pass some time before I can
look at it.

I would say to just go ahead with what matters to you right now, and I
will look at it when I rebase my branch on top of yours, which will be
once IGT gains support for the generic frame CRC ABI.

Regards,

Tomeu

> Type "help", "copyright", "credits" or "license" for more information.
> (InteractiveConsole)
>>>> p.Plug(1)
>>>> p.ComputePixelChecksum(1, 0, 0, 1920, 1080)
> Traceback (most recent call last):
>   File "<console>", line 1, in <module>
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1243, in __call__
>     return self.__send(self.__name, args)
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1602, in __request
>     verbose=self.__verbose
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1283, in request
>     return self.single_request(host, handler, request_body, verbose)
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1316, in
> single_request
>     return self.parse_response(response)
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1493, in
> parse_response
>     return u.close()
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 800, in close
>     raise Fault(**self._stack[0])
> Fault: <Fault 1: "<type 'exceptions.OSError'>:[Errno 8] Exec format
> error">
>>>> p.ComputePixelChecksum(1, 0, 0, 1920, 1080)
> Traceback (most recent call last):
>   File "<console>", line 1, in <module>
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1243, in __call__
>     return self.__send(self.__name, args)
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1602, in __request
>     verbose=self.__verbose
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1283, in request
>     return self.single_request(host, handler, request_body, verbose)
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1316, in
> single_request
>     return self.parse_response(response)
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 1493, in
> parse_response
>     return u.close()
>   File "/usr/lib64/python2.7/xmlrpclib.py", line 800, in close
>     raise Fault(**self._stack[0])
> Fault: <Fault 1: "<type 'exceptions.OSError'>:[Errno 8] Exec format
> error">
>
> Haven't had much luck with any of the other ports either
>
>>
>> Regards,
>>
>> Tomeu
>>
>> >
>> > >
>> > > >
>> > > >
>> > > > This isn't a huge deal though, since we usually just use the
>> > > >    native CRC read back on the GPU anyway.
>> > >
>> > > I'm not completely sure what you mean by that, but not all
>> > > graphic
>> > > pipelines are able to provide frame CRCs so I think this
>> > > Chamelium
>> > > work will be very useful when running tests that do check frame
>> > > CRCs.
>> > I wasn't aware of that, thanks for letting me know
>> >
>> > >
>> > >
>> > > Regards,
>> > >
>> > > Tomeu
>> > >
>> > > >
>> > > >
>> > > >
>> > > >  - Among other things that are broken with the chameleon, video
>> > > > signal
>> > > >    detection for DisplayPort is one of them. After the first
>> > > > plug/unplug cycle,
>> > > >    the DisplayPort receiver gets stuck and gives the wrong
>> > > > results
>> > > > for
>> > > >    WaitForInputStable. Luckily I've already got a fix I'll be
>> > > > submitting to the
>> > > >    ChromeOS guys when I get around to setting up their homebrew
>> > > > git
>> > > > tools:
>> > > >
>> > > >         https://github.com/Lyude/chameleond/tree/wip/chameleon-
>> > > > fixe
>> > > > s
>> > > >
>> > > >    For now, expect the dp-display tests to fail without those
>> > > > patches.
>> > > >
>> > > > Lyude (4):
>> > > >   igt_aux: Add igt_skip_without_suspend_support()
>> > > >   igt_aux: Add igt_set_autoresume_delay()
>> > > >   igt_aux: Add some list helpers from wayland
>> > > >   Add support for hotplug testing with the Chamelium
>> > > >
>> > > >  configure.ac           |  13 +
>> > > >  lib/Makefile.am        |  10 +-
>> > > >  lib/igt.h              |   1 +
>> > > >  lib/igt_aux.c          |  94 ++++++++
>> > > >  lib/igt_aux.h          |  41 ++++
>> > > >  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
>> > > > ++++++++++++++++++++++++++++++++++++++++++
>> > > >  13 files changed, 1538 insertions(+), 5 deletions(-)
>> > > >  create mode 100644 lib/igt_chamelium.c
>> > > >  create mode 100644 lib/igt_chamelium.h
>> > > >  create mode 100644 tests/chamelium.c
>> > > >
>> > > > --
>> > > > 2.7.4
>> > > >
>> > > > _______________________________________________
>> > > > Intel-gfx mailing list
>> > > > Intel-gfx@lists.freedesktop.org
>> > > > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
>> > --
>> > Cheers,
>> >         Lyude
> --
> Cheers,
>         Lyude
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium
  2016-11-16 11:52         ` Tomeu Vizoso
@ 2016-11-28 20:10           ` Lyude Paul
  0 siblings, 0 replies; 16+ messages in thread
From: Lyude Paul @ 2016-11-28 20:10 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: Intel Graphics Development

As a note: I finally managed to figure out what the problem was with
the CRC readback. Turns out when I compiled the chamelium source code,
I completely forgot to point it to an arm compiler so the chamelium
kept trying to run x86_64 binaries…

On Wed, 2016-11-16 at 12:52 +0100, Tomeu Vizoso wrote:
> On 15 November 2016 at 22:44, Lyude Paul <lyude@redhat.com> wrote:
> > I'm fine with libsoup as well, I'll check it out and probably move
> > all
> > of the code over to using that instead.
> 
> Cool.
> 
> > On Tue, 2016-11-15 at 12:44 +0100, Tomeu Vizoso wrote:
> > > On 11 November 2016 at 18:53, Lyude Paul <lyude@redhat.com>
> > > wrote:
> > > > > >  - While writing this patch series, I found that quite a
> > > > > > few of
> > > > > > the
> > > > > > RPC calls
> > > > > >    for chameleond don't work as expected. For instance, I
> > > > > > have
> > > > > > had
> > > > > > absolutely
> > > > > >    no luck getting CRCs from any of the display types that
> > > > > > the
> > > > > > chamelium
> > > > > >    supports.
> > > > > 
> > > > > When I looked at this a few months ago, frame CRCs were
> > > > > working
> > > > > just
> > > > > fine. I was using libsoup, so maybe there's some problem with
> > > > > the
> > > > > unpacking of the checksum?
> > > > 
> > > > I'm pretty sure it's on the chameleond side of things. Using
> > > > the
> > > > test
> > > > server application in chameleond's source shows the same issue.
> > > 
> > > And what's the problem? You always get CRCs with a value of zero?
> > > I
> > > only tried with HDMI, but IIRC I got to a point where
> > > kms_universal_plane passed.
> > 
> > The issue is I don't get CRCs, period :(. It looks like somewhere
> > down
> > the line chameleond is throwing a big fit whenever I try to get
> > them:
> 
> Have no good ideas right now, and it will pass some time before I can
> look at it.
> 
> I would say to just go ahead with what matters to you right now, and
> I
> will look at it when I rebase my branch on top of yours, which will
> be
> once IGT gains support for the generic frame CRC ABI.
> 
> Regards,
> 
> Tomeu
> 
> > Type "help", "copyright", "credits" or "license" for more
> > information.
> > (InteractiveConsole)
> > > > > p.Plug(1)
> > > > > p.ComputePixelChecksum(1, 0, 0, 1920, 1080)
> > 
> > Traceback (most recent call last):
> >   File "<console>", line 1, in <module>
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1243, in __call__
> >     return self.__send(self.__name, args)
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1602, in __request
> >     verbose=self.__verbose
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1283, in request
> >     return self.single_request(host, handler, request_body,
> > verbose)
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1316, in
> > single_request
> >     return self.parse_response(response)
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1493, in
> > parse_response
> >     return u.close()
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 800, in close
> >     raise Fault(**self._stack[0])
> > Fault: <Fault 1: "<type 'exceptions.OSError'>:[Errno 8] Exec format
> > error">
> > > > > p.ComputePixelChecksum(1, 0, 0, 1920, 1080)
> > 
> > Traceback (most recent call last):
> >   File "<console>", line 1, in <module>
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1243, in __call__
> >     return self.__send(self.__name, args)
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1602, in __request
> >     verbose=self.__verbose
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1283, in request
> >     return self.single_request(host, handler, request_body,
> > verbose)
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1316, in
> > single_request
> >     return self.parse_response(response)
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 1493, in
> > parse_response
> >     return u.close()
> >   File "/usr/lib64/python2.7/xmlrpclib.py", line 800, in close
> >     raise Fault(**self._stack[0])
> > Fault: <Fault 1: "<type 'exceptions.OSError'>:[Errno 8] Exec format
> > error">
> > 
> > Haven't had much luck with any of the other ports either
> > 
> > > 
> > > Regards,
> > > 
> > > Tomeu
> > > 
> > > > 
> > > > > 
> > > > > > 
> > > > > > 
> > > > > > This isn't a huge deal though, since we usually just use
> > > > > > the
> > > > > >    native CRC read back on the GPU anyway.
> > > > > 
> > > > > I'm not completely sure what you mean by that, but not all
> > > > > graphic
> > > > > pipelines are able to provide frame CRCs so I think this
> > > > > Chamelium
> > > > > work will be very useful when running tests that do check
> > > > > frame
> > > > > CRCs.
> > > > 
> > > > I wasn't aware of that, thanks for letting me know
> > > > 
> > > > > 
> > > > > 
> > > > > Regards,
> > > > > 
> > > > > Tomeu
> > > > > 
> > > > > > 
> > > > > > 
> > > > > > 
> > > > > >  - Among other things that are broken with the chameleon,
> > > > > > video
> > > > > > signal
> > > > > >    detection for DisplayPort is one of them. After the
> > > > > > first
> > > > > > plug/unplug cycle,
> > > > > >    the DisplayPort receiver gets stuck and gives the wrong
> > > > > > results
> > > > > > for
> > > > > >    WaitForInputStable. Luckily I've already got a fix I'll
> > > > > > be
> > > > > > submitting to the
> > > > > >    ChromeOS guys when I get around to setting up their
> > > > > > homebrew
> > > > > > git
> > > > > > tools:
> > > > > > 
> > > > > >         https://github.com/Lyude/chameleond/tree/wip/chamel
> > > > > > eon-
> > > > > > fixe
> > > > > > s
> > > > > > 
> > > > > >    For now, expect the dp-display tests to fail without
> > > > > > those
> > > > > > patches.
> > > > > > 
> > > > > > Lyude (4):
> > > > > >   igt_aux: Add igt_skip_without_suspend_support()
> > > > > >   igt_aux: Add igt_set_autoresume_delay()
> > > > > >   igt_aux: Add some list helpers from wayland
> > > > > >   Add support for hotplug testing with the Chamelium
> > > > > > 
> > > > > >  configure.ac           |  13 +
> > > > > >  lib/Makefile.am        |  10 +-
> > > > > >  lib/igt.h              |   1 +
> > > > > >  lib/igt_aux.c          |  94 ++++++++
> > > > > >  lib/igt_aux.h          |  41 ++++
> > > > > >  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
> > > > > > ++++++++++++++++++++++++++++++++++++++++++
> > > > > >  13 files changed, 1538 insertions(+), 5 deletions(-)
> > > > > >  create mode 100644 lib/igt_chamelium.c
> > > > > >  create mode 100644 lib/igt_chamelium.h
> > > > > >  create mode 100644 tests/chamelium.c
> > > > > > 
> > > > > > --
> > > > > > 2.7.4
> > > > > > 
> > > > > > _______________________________________________
> > > > > > Intel-gfx mailing list
> > > > > > Intel-gfx@lists.freedesktop.org
> > > > > > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
> > > > 
> > > > --
> > > > Cheers,
> > > >         Lyude
> > 
> > --
> > Cheers,
> >         Lyude
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2016-11-28 20:10 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-11-08  0:05 [RFC i-g-t 0/4] intel-gpu-tools: Add support for the Chamelium Lyude
2016-11-08  0:05 ` [RFC i-g-t 1/4] igt_aux: Add igt_skip_without_suspend_support() Lyude
2016-11-08  0:05 ` [RFC i-g-t 2/4] igt_aux: Add igt_set_autoresume_delay() Lyude
2016-11-08  9:39   ` Chris Wilson
2016-11-08  0:05 ` [RFC i-g-t 3/4] igt_aux: Add some list helpers from wayland Lyude
2016-11-08  9:37   ` Chris Wilson
2016-11-08  0:05 ` [RFC i-g-t 4/4] Add support for hotplug testing with the Chamelium Lyude
2016-11-09 15:18   ` Tomeu Vizoso
2016-11-14  7:05   ` Daniel Vetter
2016-11-14 16:46     ` Lyude Paul
2016-11-09 15:09 ` [RFC i-g-t 0/4] intel-gpu-tools: Add support for " Tomeu Vizoso
2016-11-11 17:53   ` Lyude Paul
2016-11-15 11:44     ` Tomeu Vizoso
2016-11-15 21:44       ` Lyude Paul
2016-11-16 11:52         ` Tomeu Vizoso
2016-11-28 20:10           ` Lyude Paul

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.