/*
 * 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 <stdbool.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <xmlrpc-c/base.h>
#include <xmlrpc-c/client.h>
#include <pthread.h>
#include <glib.h>
#include <pixman.h>
#include <cairo.h>

#include "igt_chamelium.h"
#include "igt_core.h"
#include "igt_aux.h"
#include "igt_edid.h"
#include "igt_frame.h"
#include "igt_list.h"
#include "igt_kms.h"
#include "igt_pipe_crc.h"
#include "igt_rc.h"

/**
 * SECTION:igt_chamelium
 * @short_description: Library for using the Chamelium into igt tests
 * @title: Chamelium
 * @include: igt_chamelium.h
 *
 * This library contains helpers for using Chameliums in IGT tests. This allows
 * for tests to simulate more difficult tasks to automate such as display
 * hotplugging, faulty display behaviors, etc.
 *
 * More information on the Chamelium can be found
 * [on the ChromeOS project page](https://www.chromium.org/chromium-os/testing/chamelium).
 *
 * In order to run tests using the Chamelium, a valid configuration file must be
 * present. It must contain Chamelium-specific keys as shown with the following
 * example:
 *
 * |[<!-- language="plain" -->
 *	[Chamelium]
 *	URL=http://chameleon:9992 # The URL used for connecting to the Chamelium's RPC server
 *
 *	# The rest of the sections are used for defining connector mappings.
 *	# This is required so any tests using the Chamelium know which connector
 *	# on the test machine should be connected to each Chamelium port.
 *	#
 *	# In the event that any of these mappings are specified incorrectly,
 *	# any hotplugging tests for the incorrect connector mapping will fail.
 *
 *	[Chamelium:DP-1] # The name of the DRM connector
 *	ChameliumPortID=1 # The ID of the port on the Chamelium this connector is attached to
 *
 *	[Chamelium:HDMI-A-1]
 *	ChameliumPortID=3
 * ]|
 *
 */

/*
 * We cannot expect chamelium_plug() to take effect imediately.
 *
 * Especially with modern, more complex hardware where we may have LSPcons and
 * USB controllers in the way.
 */
#define CHAMELIUM_HOTPLUG_DETECTION_DELAY 10

struct chamelium_port {
	unsigned int type;
	int id;
	int connector_id;
	char *name;
	bool adapter_allowed;
	char *connector_path;
	bool is_mapped;
};

struct chamelium_frame_dump {
	unsigned char *bgr;
	size_t size;
	int width;
	int height;
	struct chamelium_port *port;
};

struct chamelium_fb_crc_async_data {
	cairo_surface_t *fb_surface;

	pthread_t thread_id;
	igt_crc_t *ret;
};

struct chamelium {
	xmlrpc_env env;
	xmlrpc_client *client;
	char *url;

	/* Indicates the last port to have been used for capturing video */
	struct chamelium_port *capturing_port;

	int drm_fd;

	struct igt_list_head edids;
	struct chamelium_port ports[CHAMELIUM_MAX_PORTS];
	int port_count;
};

bool igt_chamelium_allow_fsm_handling = true;

static struct chamelium *cleanup_instance;

static void chamelium_do_calculate_fb_crc(cairo_surface_t *fb_surface,
					  igt_crc_t *out);

/**
 * chamelium_get_ports:
 * @chamelium: The Chamelium instance to use
 * @count: Where to store the number of ports
 *
 * Retrieves all of the ports currently configured for use with this chamelium
 *
 * Returns: an array containing a pointer to each configured chamelium port
 */
struct chamelium_port **chamelium_get_ports(struct chamelium *chamelium,
					    int *count)
{
	int i;
	struct chamelium_port **ret =
		calloc(chamelium->port_count, sizeof(void*));

	*count = chamelium->port_count;
	for (i = 0; i < chamelium->port_count; i++)
		ret[i] = &chamelium->ports[i];

	return ret;
}

/**
 * chamelium_port_get_type:
 * @port: The chamelium port to retrieve the type from
 *
 * Retrieves the DRM connector type of the physical port on the Chamelium. It
 * should be noted that this type may differ from the type provided by the
 * driver.
 *
 * Returns: the DRM connector type of the physical Chamelium port
 */
unsigned int chamelium_port_get_type(const struct chamelium_port *port) {
	return port->type;
}

/**
 * chamelium_port_get_name:
 * @port: The chamelium port to retrieve the name of
 *
 * Gets the name of the DRM connector corresponding to the given Chamelium
 * port.
 *
 * Returns: the name of the DRM connector
 */
const char *chamelium_port_get_name(struct chamelium_port *port)
{
	return port->name;
}

/**
 * chamelium_port_get_connector:
 * @chamelium: The Chamelium instance to use
 * @port: The chamelium port to retrieve the DRM connector for
 * @reprobe: Whether or not to reprobe the DRM connector
 *
 * Get a drmModeConnector object for the given Chamelium port, and optionally
 * reprobe the port in the process
 *
 * Returns: a drmModeConnector object corresponding to the given port
 */
drmModeConnector *chamelium_port_get_connector(struct chamelium *chamelium,
					       struct chamelium_port *port,
					       bool reprobe)
{
	typedef drmModeConnectorPtr (*getConnectorPtr)(int fd,
						       uint32_t connector_id);

	drmModeRes *res = NULL;
	int i;

	bool is_mst_port = !!port->connector_path;
	getConnectorPtr getConnector = reprobe ? &drmModeGetConnector :
						 &drmModeGetConnectorCurrent;
	int drm_fd = chamelium->drm_fd;
	drmModeConnector *connector = getConnector(drm_fd, port->connector_id);

	/* If the port isn't MST, then the connector ID should be consistent to grab the connector. */
	if (!is_mst_port) {
		return connector;
	}

	/* If the port is MST, then we need to find the connector ID from the path. */

	/* In case the connector ID is still valid, do a quick check if we're have the connector we expect. 
	 * Otherwise, read the new resources and find the new connector we're looking for. */
	if (connector) {
		if (connector->connection == DRM_MODE_CONNECTED) {
			drmModePropertyBlobPtr path_blob =
				kmstest_get_path_blob(drm_fd,
						      connector->connector_id);
			if (path_blob) {
				bool is_correct_connector =
					strcmp(port->connector_path,
					       path_blob->data) == 0;
				drmModeFreePropertyBlob(path_blob);
				if (is_correct_connector)
					return connector;
			}
		}

		drmModeFreeConnector(connector);
		connector = NULL;
	}

	res = drmModeGetResources(drm_fd);
	for (i = 0; i < res->count_connectors; i++) {
		drmModePropertyBlobPtr path_blob = NULL;

		connector = getConnector(drm_fd, res->connectors[i]);
		/* Check if the connector is not disconnected and in zombie mode. */
		if (!connector)
			continue;
		/* Check if the connector is MST. */
		path_blob =
			kmstest_get_path_blob(drm_fd, connector->connector_id);
		if (!path_blob)
			continue;

		if (strcmp(path_blob->data, port->connector_path) == 0) {
			char connector_name[50];
			/* At finding the connector, update its metadata. */
			port->connector_id = connector->connector_id;

			snprintf(connector_name, 50, "%s-%u",
				 kmstest_connector_type_str(
					 connector->connector_type),
				 connector->connector_type_id);
			port->name = strdup(connector_name);

			goto out;
		}

		drmModeFreePropertyBlob(path_blob);
		drmModeFreeConnector(connector);
		connector = NULL;
	}

out:
	drmModeFreeResources(res);
	return connector;
}

/**
 * chamelium_require_connector_present
 * @ports: All connected ports
 * @type: Required port type
 * @port_count: Total port count
 * @count: The required number of port count
 *
 * Check there are required ports connected of given type
 */
void
chamelium_require_connector_present(struct chamelium_port **ports,
				    unsigned int type,
				    int port_count,
				    int count)
{
	int i;
	int found = 0;

	for (i = 0; i < port_count; i++) {
		if (chamelium_port_get_type(ports[i]) == type)
			found++;
	}

	igt_require_f(found >= count,
		      "port of type %s found %d and required %d\n",
		      kmstest_connector_type_str(type), found, count);
}

/**
 * chamelium_reprobe_connector
 * @display: A pointer to an #igt_display_t structure
 * @chamelium: The Chamelium instance to use
 * @port: Chamelium port to reprobe
 *
 *  Reprobe the given connector and fetch current status
 *
 *  Returns: drmModeConnection
 */
drmModeConnection
chamelium_reprobe_connector(igt_display_t *display,
			    struct chamelium *chamelium,
			    struct chamelium_port *port)
{
	drmModeConnector *connector;
	drmModeConnection connection_status;
	igt_output_t *output;
	bool is_mst_port = !!port->connector_path;

	igt_debug("Reprobing %s...\n", chamelium_port_get_name(port));

	if (is_mst_port)
	{
		int drm_fd = display->drm_fd;
		drmModeRes *res = drmModeGetResources(drm_fd);
		bool is_connector_found = false;
		int i;

		for (i = 0; i < res->count_connectors; ++i)
		{
			char connector_name[50];
			uint64_t path_blob_id;
			drmModePropertyBlobPtr path_blob = NULL;

			connector = drmModeGetConnector(drm_fd, res->connectors[i]);
			/* If the connector is now unplugged, the spawned connectors will no
			   longer be available but can still be counted as part of
			   res->count_connectors */
			if (!connector)
			{
				drmModeFreeConnector(connector);
				connector = NULL;
				continue;
			}

			/* If the post is MST, it must have a path property - skip if not */
			if (!kmstest_get_property(drm_fd, connector->connector_id,
									  DRM_MODE_OBJECT_CONNECTOR, "PATH", NULL,
									  &path_blob_id, NULL))
			{
				drmModeFreeConnector(connector);
				connector = NULL;
				continue;
			}

			igt_assert(path_blob =
						   drmModeGetPropertyBlob(drm_fd, path_blob_id));
			/* With MST, the only thing that is guaranteed to persist between
			   plugs and unplugs is the PATH property. Verify that it matches
			   what we previously saved. */
			if (strcmp(path_blob->data, port->connector_path) == 0)
			{
				is_connector_found = true;
				/* Update name and connector ID they can change between plugs and
				   unplugs */
				port->connector_id = connector->connector_id;
				snprintf(connector_name, 50, "%s-%u",
						 kmstest_connector_type_str(connector->connector_type),
						 connector->connector_type_id);
				port->name = strdup(connector_name);

				drmModeFreePropertyBlob(path_blob);
				break;
			}

			drmModeFreePropertyBlob(path_blob);
			drmModeFreeConnector(connector);
			connector = NULL;
		}
		drmModeFreeResources(res);

		if (!is_connector_found)
		{
			igt_assert(!connector);
			return DRM_MODE_DISCONNECTED;
		}
	}
	else
	{
		connector = chamelium_port_get_connector(chamelium, port, true);
	}

	igt_assert(connector);
	connection_status = connector->connection;

	/* If we still have a connector, let's make sure that igt_display and
	   the port are up to date too */
	output = igt_output_from_connector(display, connector);
	output->force_reprobe = true;
	igt_output_refresh(output);

	drmModeFreeConnector(connector);
	return connection_status;
}

/**
 * chamelium_wait_for_conn_status_change
 * @display: A pointer to an #igt_display_t structure
 * @chamelium: The Chamelium instance to use
 * @port: Chamelium port to check connector status update
 * @status: Enum which describes connector states
 *
 * Wait for the connector to change the status
 */
void
chamelium_wait_for_conn_status_change(igt_display_t *display,
			     struct chamelium *chamelium,
			     struct chamelium_port *port,
			     drmModeConnection status)
{
	igt_debug("Waiting for %s to get %s...\n",
			  chamelium_port_get_name(port),
			  kmstest_connector_status_str(status));

	/*
	 * Rely on simple reprobing so we don't fail tests that don't require
	 * that hpd events work in the event that hpd doesn't work on the system
	 */
	igt_until_timeout(CHAMELIUM_HOTPLUG_TIMEOUT) {
		if (chamelium_reprobe_connector(display,
						chamelium, port) == status)
			return;

		usleep(50000);
	}

	igt_assert_f(false, "Timed out waiting for %s to get %s\n",
				 chamelium_port_get_name(port),
				 kmstest_connector_status_str(status));
}

/**
 * chamelium_reset_state
 *
 * @chamelium: The Chamelium instance to use
 * @port: Chamelium port to reset
 * @ports: All connected ports
 * @port_count: Count of connected ports
 *
 * Reset chamelium ports
 */
void
chamelium_reset_state(igt_display_t *display,
		      struct chamelium *chamelium,
		      struct chamelium_port *port,
		      struct chamelium_port **ports,
		      int port_count)
{
	int p;

	chamelium_reset(chamelium);

	if (port) {
		chamelium_wait_for_conn_status_change(display, chamelium,
						      port,
						      DRM_MODE_DISCONNECTED);
	} else {
		for (p = 0; p < port_count; p++) {
			port = ports[p];
			chamelium_wait_for_conn_status_change(display, chamelium,
							      port, DRM_MODE_DISCONNECTED);
		}
	}
}

/**
 * chamelium_destroy_frame_dump:
 * @dump: The frame dump to destroy
 *
 * Destroys the given frame dump and frees all of the resources associated with
 * it.
 */
void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
{
	free(dump->bgr);
	free(dump);
}

void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file)
{
	free(audio_file->path);
	free(audio_file);
}

struct fsm_monitor_args {
	struct chamelium *chamelium;
	struct chamelium_port *port;
	struct udev_monitor *mon;
};

static bool wait_for_connected_state(int drm_fd,
				     int *connectors, int connector_count)
{
	igt_assert(connector_count > 0);

	igt_until_timeout(CHAMELIUM_HOTPLUG_DETECTION_DELAY) {
		bool connected;

		for (int i = 0; i < connector_count; i++) {
			drmModeConnector *connector =
				drmModeGetConnector(drm_fd, connectors[i]);

			if (!connector) {
				connected = false;
				break;
			}

			connected = connector->connection == DRM_MODE_CONNECTED;

			drmModeFreeConnector(connector);

			if (!connected)
				break;
		}

		if (connected)
			return true;

		usleep(50000);
	}

	return false;
}

/*
 * Whenever resolutions or other factors change with the display output, the
 * Chamelium's display receivers need to be fully reset in order to perform any
 * frame-capturing related tasks. This requires cutting off the display then
 * turning it back on, and is indicated by the Chamelium sending hotplug events
 */
static void *chamelium_fsm_mon(void *data)
{
	struct fsm_monitor_args *args = data;
	drmModeConnector *connector;
	int drm_fd = args->chamelium->drm_fd;

	/*
	 * Wait for the chamelium to try unplugging the connector, otherwise
	 * the thread calling chamelium_rpc will kill us
	 */
	igt_hotplug_detected(args->mon, 60);

	/*
	 * Just in case the RPC call being executed returns before we complete
	 * the FSM modesetting sequence, so we don't leave the display in a bad
	 * state.
	 */
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

	igt_debug("Chamelium needs FSM, handling\n");
	connector = chamelium_port_get_connector(args->chamelium, args->port,
						 false);
	kmstest_set_connector_dpms(drm_fd, connector, DRM_MODE_DPMS_OFF);
	wait_for_connected_state(drm_fd, &args->port->connector_id, 1);

	kmstest_set_connector_dpms(drm_fd, connector, DRM_MODE_DPMS_ON);

	drmModeFreeConnector(connector);
	return NULL;
}

#define _RECEIVER_RESPONSIVE_AFTER_RESET_SECONDS 10

static xmlrpc_value *__chamelium_rpc_va(struct chamelium *chamelium,
					struct chamelium_port *fsm_port,
					const char *method_name,
					const char *format_str,
					va_list va_args)
{
	xmlrpc_value *res = NULL;
	struct fsm_monitor_args monitor_args;
	pthread_t fsm_thread_id;

	/* Unfortunately xmlrpc_client's event loop helpers are rather useless
	 * for implementing any sort of event loop, since they provide no way
	 * to poll for events other then the RPC response. This means in order
	 * to handle the chamelium attempting FSM, we have to fork into another
	 * thread and have that handle hotplugging displays
	 */
	if (fsm_port && igt_chamelium_allow_fsm_handling) {
		monitor_args.chamelium = chamelium;
		monitor_args.port = fsm_port;
		monitor_args.mon = igt_watch_uevents();
		pthread_create(&fsm_thread_id, NULL, chamelium_fsm_mon,
			       &monitor_args);
	}

	igt_until_timeout(_RECEIVER_RESPONSIVE_AFTER_RESET_SECONDS) {
		/* Cleanup the last error, if any */
		if (chamelium->env.fault_occurred) {
			xmlrpc_env_clean(&chamelium->env);
			xmlrpc_env_init(&chamelium->env);
		}

		xmlrpc_client_call2f_va(&chamelium->env, chamelium->client,
					chamelium->url, method_name, format_str, &res,
					va_args);

		if (!chamelium->env.fault_occurred)
			break;

		if (NULL == strstr(chamelium->env.fault_string, "I2C"))
			break;

		/* i2c error, let's try to retry */
	}

	if (fsm_port && igt_chamelium_allow_fsm_handling) {
		pthread_cancel(fsm_thread_id);
		pthread_join(fsm_thread_id, NULL);
		igt_cleanup_uevents(monitor_args.mon);
	}

	return res;
}

static xmlrpc_value *__chamelium_rpc(struct chamelium *chamelium,
				     struct chamelium_port *fsm_port,
				     const char *method_name,
				     const char *format_str,
				     ...)
{
	xmlrpc_value *res;
	va_list va_args;

	va_start(va_args, format_str);
	res = __chamelium_rpc_va(chamelium, fsm_port, method_name,
				 format_str, va_args);
	va_end(va_args);

	return res;
}

static xmlrpc_value *chamelium_rpc(struct chamelium *chamelium,
				   struct chamelium_port *fsm_port,
				   const char *method_name,
				   const char *format_str,
				   ...)
{
	xmlrpc_value *res;
	va_list va_args;
	int fsm_trials_left = 5;

	if (strcmp(method_name, "CaptureVideo") == 0
	    || strcmp(method_name, "StartCapturingVideo") == 0) {
		while (fsm_trials_left) {
			va_start(va_args, format_str);
			res = __chamelium_rpc_va(chamelium, fsm_port,
						 method_name, format_str,
						 va_args);
			va_end(va_args);

			if (!chamelium->env.fault_occurred)
				break;

			igt_debug("DP FSM failed retrying, tries left %d\n", fsm_trials_left);
			--fsm_trials_left;
		}
	} else {
		va_start(va_args, format_str);
		res = __chamelium_rpc_va(chamelium, fsm_port, method_name,
					 format_str, va_args);
		va_end(va_args);
	}
	igt_assert_f(!chamelium->env.fault_occurred,
		     "Chamelium RPC call[%s] failed: %s\n", method_name,
		     chamelium->env.fault_string);

	return res;
}

static bool __chamelium_is_reachable(struct chamelium *chamelium)
{
	xmlrpc_value *res;

	/* GetSupportedInputs does not require a port and is harmless */
	res = __chamelium_rpc(chamelium, NULL, "GetSupportedInputs", "()");

	if (res != NULL)
		xmlrpc_DECREF(res);

	if (chamelium->env.fault_occurred) {
		igt_debug("Chamelium RPC call failed: %s\n",
			  chamelium->env.fault_string);

		xmlrpc_env_clean(&chamelium->env);
		xmlrpc_env_init(&chamelium->env);

		return false;
	}

	return true;
}

/**
 * chamelium_wait_reachable:
 * @chamelium: The Chamelium instance to use
 * @timeout: Time (in seconds) to wait for chamelium to be reachable
 *
 * Returns: %true if the Chamelium is reachable, %false otherwise.
 */
bool chamelium_wait_reachable(struct chamelium *chamelium, int timeout)
{
	return igt_wait(__chamelium_is_reachable(chamelium),
			timeout * 1000, 100);
}

/**
 * chamelium_assert_reachable:
 * @chamelium: The Chamelium instance to use
 * @timeout: Time (in seconds) to wait for chamelium to be reachable
 *
 * Asserts that the chamelium is reachable.
 */
void chamelium_assert_reachable(struct chamelium *chamelium, int timeout)
{
	bool chamelium_online = chamelium_wait_reachable(chamelium, timeout);
	igt_assert_f(chamelium_online,
		     "Couldn't connect to Chamelium for %ds", timeout);
}

/**
 * chamelium_plug:
 * @chamelium: The Chamelium instance to use
 * @port: The port on the chamelium to plug
 *
 * Simulate a display connector being plugged into the system using the
 * chamelium.
 */
void chamelium_plug(struct chamelium *chamelium, struct chamelium_port *port)
{
	igt_debug("Plugging %s (Chamelium port ID %d)\n", port->name, port->id);
	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Plug", "(i)", port->id));
}

/**
 * chamelium_unplug:
 * @chamelium: The Chamelium instance to use
 * @port: The port on the chamelium to unplug
 *
 * Simulate a display connector being unplugged from the system using the
 * chamelium.
 */
void chamelium_unplug(struct chamelium *chamelium, struct chamelium_port *port)
{
	igt_debug("Unplugging port %s\n", port->name);
	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Unplug", "(i)",
				    port->id));
}

/**
 * chamelium_is_plugged:
 * @chamelium: The Chamelium instance to use
 * @port: 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(struct chamelium *chamelium,
			  struct chamelium_port *port)
{
	xmlrpc_value *res;
	xmlrpc_bool is_plugged;

	res = chamelium_rpc(chamelium, NULL, "IsPlugged", "(i)", port->id);

	xmlrpc_read_bool(&chamelium->env, res, &is_plugged);
	xmlrpc_DECREF(res);

	return is_plugged;
}

/**
 * chamelium_port_wait_video_input_stable:
 * @chamelium: The Chamelium instance to use
 * @port: 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(struct chamelium *chamelium,
					    struct chamelium_port *port,
					    int timeout_secs)
{
	xmlrpc_value *res;
	xmlrpc_bool is_on;

	igt_debug("Waiting for video input to stabalize on %s\n", port->name);

	res = chamelium_rpc(chamelium, port, "WaitVideoInputStable", "(ii)",
			    port->id, timeout_secs);

	xmlrpc_read_bool(&chamelium->env, res, &is_on);
	xmlrpc_DECREF(res);

	return is_on;
}

/**
 * chamelium_fire_hpd_pulses:
 * @chamelium: The Chamelium instance to use
 * @port: The port to fire the HPD 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(struct chamelium *chamelium,
			       struct chamelium_port *port,
			       int width_msec, int count)
{
	xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env);
	xmlrpc_value *width = xmlrpc_int_new(&chamelium->env, width_msec);
	int i;

	igt_debug("Firing %d HPD pulses with width of %d msec on %s\n",
		  count, width_msec, port->name);

	for (i = 0; i < count; i++)
		xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);

	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "FireMixedHpdPulses",
				    "(iA)", port->id, pulse_widths));

	xmlrpc_DECREF(width);
	xmlrpc_DECREF(pulse_widths);
}

/**
 * chamelium_fire_mixed_hpd_pulses:
 * @chamelium: The Chamelium instance to use
 * @port: The port to fire the HPD 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(struct chamelium *chamelium,
				     struct chamelium_port *port, ...)
{
	va_list args;
	xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env), *width;
	int arg;

	igt_debug("Firing mixed HPD pulses on %s\n", port->name);

	va_start(args, port);
	for (arg = va_arg(args, int); arg; arg = va_arg(args, int)) {
		width = xmlrpc_int_new(&chamelium->env, arg);
		xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
		xmlrpc_DECREF(width);
	}
	va_end(args);

	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "FireMixedHpdPulses",
				    "(iA)", port->id, pulse_widths));

	xmlrpc_DECREF(pulse_widths);
}

/**
 * chamelium_schedule_hpd_toggle:
 * @chamelium: The Chamelium instance to use
 * @port: The port to fire the HPD pulses on
 * @delay_ms: Delay in milli-second before the toggle takes place
 * @rising_edge: Whether the toggle should be a rising edge or a falling edge
 *
 * Instructs the chamelium to schedule an hpd toggle (either a rising edge or
 * a falling edge, depending on @rising_edg) after @delay_ms have passed.
 * This is useful for testing things such as hpd after a suspend/resume cycle.
 */
void chamelium_schedule_hpd_toggle(struct chamelium *chamelium,
				   struct chamelium_port *port, int delay_ms,
				   bool rising_edge)
{
	igt_debug("Scheduling HPD toggle on %s in %d ms\n", port->name,
		  delay_ms);

	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "ScheduleHpdToggle",
				    "(iii)", port->id, delay_ms, rising_edge));
}

static int chamelium_upload_edid(struct chamelium *chamelium,
				 const struct edid *edid)
{
	xmlrpc_value *res;
	int edid_id;

	res = chamelium_rpc(chamelium, NULL, "CreateEdid", "(6)",
			    edid, edid_get_size(edid));
	xmlrpc_read_int(&chamelium->env, res, &edid_id);
	xmlrpc_DECREF(res);

	return edid_id;
}

static void chamelium_destroy_edid(struct chamelium *chamelium, int edid_id)
{
	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "DestroyEdid", "(i)",
				    edid_id));
}

/**
 * chamelium_new_edid:
 * @chamelium: The Chamelium instance to use
 * @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.
 *
 * Callers shouldn't assume that the raw EDID they provide is uploaded as-is to
 * the Chamelium. The EDID may be mutated (e.g. a serial number can be appended
 * to be able to uniquely identify the EDID). To retrieve the exact EDID that
 * will be applied to a particular port, use #chamelium_edid_get_raw.
 *
 * Returns: An opaque pointer to the Chamelium EDID
 */
struct chamelium_edid *chamelium_new_edid(struct chamelium *chamelium,
					  const struct edid *edid)
{
	struct chamelium_edid *chamelium_edid;
	size_t edid_size = edid_get_size(edid);

	chamelium_edid = calloc(1, sizeof(struct chamelium_edid));
	chamelium_edid->chamelium = chamelium;
	chamelium_edid->base = malloc(edid_size);
	memcpy(chamelium_edid->base, edid, edid_size);
	igt_list_add(&chamelium_edid->link, &chamelium->edids);

	return chamelium_edid;
}

/**
 * chamelium_port_tag_edid: tag the EDID with the provided Chamelium port.
 */
static void chamelium_port_tag_edid(struct chamelium_port *port,
				    struct edid *edid)
{
	uint32_t *serial;

	/* Product code: Chamelium */
	edid->prod_code[0] = 'C';
	edid->prod_code[1] = 'H';

	/* Serial: Chamelium port ID */
	serial = (uint32_t *) &edid->serial;
	*serial = port->id;

	edid_update_checksum(edid);
}

/**
 * chamelium_edid_get_raw: get the raw EDID
 * @edid: the Chamelium EDID
 * @port: the Chamelium port
 *
 * The EDID provided to #chamelium_new_edid may be mutated for identification
 * purposes. This function allows to retrieve the exact EDID that will be set
 * for a given port.
 *
 * The returned raw EDID is only valid until the next call to this function.
 */
const struct edid *chamelium_edid_get_raw(struct chamelium_edid *edid,
					  struct chamelium_port *port)
{
	size_t port_index = port - edid->chamelium->ports;
	size_t edid_size;

	if (!edid->raw[port_index]) {
		edid_size = edid_get_size(edid->base);
		edid->raw[port_index] = malloc(edid_size);
		memcpy(edid->raw[port_index], edid->base, edid_size);
		chamelium_port_tag_edid(port, edid->raw[port_index]);
	}

	return edid->raw[port_index];
}

/**
 * chamelium_edid_get_editable_raw: get the raw EDID which can be edited later.
 * @edid: the Chamelium EDID
 * @port: the Chamelium port
 *
 * The EDID provided to #chamelium_new_edid may be mutated for identification
 * purposes. This function allows to retrieve the exact EDID that will be set
 * for a given port.
 *
 * The returned raw EDID is only valid until the next call to this function.
 */
struct edid *chamelium_edid_get_editable_raw(struct chamelium_edid *edid,
					  struct chamelium_port *port)
{
	size_t port_index = port - edid->chamelium->ports;
	size_t edid_size;

	if (!edid->raw[port_index]) {
		edid_size = edid_get_size(edid->base);
		edid->raw[port_index] = malloc(edid_size);
		memcpy(edid->raw[port_index], edid->base, edid_size);
		chamelium_port_tag_edid(port, edid->raw[port_index]);
	}

	return edid->raw[port_index];
}

/**
 * chamelium_port_set_edid:
 * @chamelium: The Chamelium instance to use
 * @port: The port on the Chamelium to set the EDID on
 * @edid: The Chamelium EDID to set or NULL to use the default Chamelium EDID
 *
 * 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.
 *
 * To create an EDID, see #chamelium_new_edid.
 */
void chamelium_port_set_edid(struct chamelium *chamelium,
			     struct chamelium_port *port,
			     struct chamelium_edid *edid)
{
	int edid_id;
	size_t port_index;
	const struct edid *raw_edid;

	if (edid) {
		port_index = port - chamelium->ports;
		edid_id = edid->ids[port_index];
		if (edid_id == 0) {
			raw_edid = chamelium_edid_get_raw(edid, port);
			edid_id = chamelium_upload_edid(chamelium, raw_edid);
			edid->ids[port_index] = edid_id;
		}
	} else {
		edid_id = 0;
	}

	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "ApplyEdid", "(ii)",
				    port->id, edid_id));
}

/**
 * chamelium_port_set_tiled_edid:
 * @chamelium: The Chamelium instance to use
 * @port: The port on the Chamelium to set the EDID on
 * @edid: The Chamelium EDID to set or NULL to use the default Chamelium EDID
 *
 * Sets unique serial for tiled edid.
 * 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.
 *
 * To create an EDID, see #chamelium_new_edid.
 */
void chamelium_port_set_tiled_edid(struct chamelium *chamelium,
			     struct chamelium_port *port,
			     struct chamelium_edid *edid)
{
	int edid_id;
	size_t port_index;
	struct edid *raw_edid;

	if (edid) {
		port_index = port - chamelium->ports;
		edid_id = edid->ids[port_index];
		if (edid_id == 0) {
			raw_edid = chamelium_edid_get_editable_raw(edid, port);
			raw_edid->serial[0] = 0x02;
			base_edid_update_checksum(raw_edid);
			edid_id = chamelium_upload_edid(chamelium, raw_edid);
			edid->ids[port_index] = edid_id;
		}
	} else {
		edid_id = 0;
	}
	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "ApplyEdid", "(ii)",
				    port->id, edid_id));
}

/**
 * chamelium_port_set_ddc_state:
 * @chamelium: The Chamelium instance to use
 * @port: The port to change the DDC state on
 * @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(struct chamelium *chamelium,
				  struct chamelium_port *port,
				  bool enabled)
{
	igt_debug("%sabling DDC bus on %s\n",
		  enabled ? "En" : "Dis", port->name);

	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "SetDdcState", "(ib)",
				    port->id, enabled));
}

/**
 * chamelium_port_get_ddc_state:
 * @chamelium: The Chamelium instance to use
 * @port: The port on the Chamelium 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(struct chamelium *chamelium,
				  struct chamelium_port *port)
{
	xmlrpc_value *res;
	xmlrpc_bool enabled;

	res = chamelium_rpc(chamelium, NULL, "IsDdcEnabled", "(i)", port->id);
	xmlrpc_read_bool(&chamelium->env, res, &enabled);

	xmlrpc_DECREF(res);
	return enabled;
}

/**
 * chamelium_port_get_resolution:
 * @chamelium: The Chamelium instance to use
 * @port: The port on the Chamelium 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(struct chamelium *chamelium,
				   struct chamelium_port *port,
				   int *x, int *y)
{
	xmlrpc_value *res, *res_x, *res_y;

	res = chamelium_rpc(chamelium, port, "DetectResolution", "(i)",
			    port->id);

	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_x);
	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_y);
	xmlrpc_read_int(&chamelium->env, res_x, x);
	xmlrpc_read_int(&chamelium->env, res_y, y);

	xmlrpc_DECREF(res_x);
	xmlrpc_DECREF(res_y);
	xmlrpc_DECREF(res);
}

/** chamelium_supports_method: checks if the Chamelium board supports a method.
 *
 * Note: this actually tries to call the method.
 *
 * See https://crbug.com/977995 for a discussion about a better solution.
 */
static bool chamelium_supports_method(struct chamelium *chamelium,
				      const char *name)
{
	xmlrpc_value *res;

	res = __chamelium_rpc(chamelium, NULL, name, "()");
	if (res)
		xmlrpc_DECREF(res);

	/* XML-RPC has a special code for unsupported methods
	 * (XMLRPC_NO_SUCH_METHOD_ERROR) however the Chamelium implementation
	 * doesn't return it. */
	return (!chamelium->env.fault_occurred ||
		strstr(chamelium->env.fault_string, "not supported") == NULL);
}

bool chamelium_supports_get_video_params(struct chamelium *chamelium)
{
	return chamelium_supports_method(chamelium, "GetVideoParams");
}

static void read_int_from_xml_struct(struct chamelium *chamelium,
				     xmlrpc_value *struct_val, const char *key,
				     int *dst)
{
	xmlrpc_value *val = NULL;

	xmlrpc_struct_find_value(&chamelium->env, struct_val, key, &val);
	if (val) {
		xmlrpc_read_int(&chamelium->env, val, dst);
		xmlrpc_DECREF(val);
	} else
		*dst = -1;
}

static void video_params_from_xml(struct chamelium *chamelium,
				  xmlrpc_value *res,
				  struct chamelium_video_params *params)
{
	xmlrpc_value *val = NULL;

	xmlrpc_struct_find_value(&chamelium->env, res, "clock", &val);
	if (val) {
		xmlrpc_read_double(&chamelium->env, val, &params->clock);
		xmlrpc_DECREF(val);
	} else
		params->clock = NAN;

	read_int_from_xml_struct(chamelium, res, "htotal", &params->htotal);
	read_int_from_xml_struct(chamelium, res, "hactive", &params->hactive);
	read_int_from_xml_struct(chamelium, res, "hsync_offset",
				 &params->hsync_offset);
	read_int_from_xml_struct(chamelium, res, "hsync_width",
				 &params->hsync_width);
	read_int_from_xml_struct(chamelium, res, "hsync_polarity",
				 &params->hsync_polarity);
	read_int_from_xml_struct(chamelium, res, "vtotal", &params->vtotal);
	read_int_from_xml_struct(chamelium, res, "vactive", &params->vactive);
	read_int_from_xml_struct(chamelium, res, "vsync_offset",
				 &params->vsync_offset);
	read_int_from_xml_struct(chamelium, res, "vsync_width",
				 &params->vsync_width);
	read_int_from_xml_struct(chamelium, res, "vsync_polarity",
				 &params->vsync_polarity);
}

void chamelium_port_get_video_params(struct chamelium *chamelium,
				     struct chamelium_port *port,
				     struct chamelium_video_params *params)
{
	xmlrpc_value *res;

	res = chamelium_rpc(chamelium, NULL, "GetVideoParams", "(i)", port->id);
	video_params_from_xml(chamelium, res, params);

	xmlrpc_DECREF(res);
}

static void chamelium_get_captured_resolution(struct chamelium *chamelium,
					      int *w, int *h)
{
	xmlrpc_value *res, *res_w, *res_h;

	res = chamelium_rpc(chamelium, NULL, "GetCapturedResolution", "()");

	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_w);
	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_h);
	xmlrpc_read_int(&chamelium->env, res_w, w);
	xmlrpc_read_int(&chamelium->env, res_h, h);

	xmlrpc_DECREF(res_w);
	xmlrpc_DECREF(res_h);
	xmlrpc_DECREF(res);
}

static struct chamelium_frame_dump *frame_from_xml(struct chamelium *chamelium,
						   xmlrpc_value *frame_xml)
{
	struct chamelium_frame_dump *ret = malloc(sizeof(*ret));

	chamelium_get_captured_resolution(chamelium, &ret->width, &ret->height);
	ret->port = chamelium->capturing_port;
	xmlrpc_read_base64(&chamelium->env, frame_xml, &ret->size,
			   (void*)&ret->bgr);

	return ret;
}

/**
 * chamelium_port_dump_pixels:
 * @chamelium: The Chamelium instance to use
 * @port: The port to perform the video capture on
 * @x: The X coordinate to crop the screen capture to
 * @y: The Y coordinate to crop the screen capture to
 * @w: The width of the area to crop the screen capture to, or 0 for the whole
 * screen
 * @h: The height of the area to crop the screen capture to, or 0 for the whole
 * screen
 *
 * Captures the currently displayed image on the given chamelium port,
 * optionally cropped to a given region. In situations where pre-calculating
 * CRCs may not be reliable, this can be used as an alternative for figuring
 * out whether or not the correct images are being displayed on the screen.
 *
 * The frame dump data returned by this function should be freed when the
 * caller is done with it using #chamelium_destroy_frame_dump.
 *
 * As an important note: some of the EDIDs provided by the Chamelium cause
 * certain GPU drivers to default to using limited color ranges. This can cause
 * video captures from the Chamelium to provide different images then expected
 * due to the difference in color ranges (framebuffer uses full color range,
 * but the video output doesn't), and as a result lead to CRC mismatches. To
 * workaround this, the caller should force the connector to use full color
 * ranges by using #kmstest_set_connector_broadcast_rgb before setting up the
 * display.
 *
 * Returns: a chamelium_frame_dump struct
 */
struct chamelium_frame_dump *chamelium_port_dump_pixels(struct chamelium *chamelium,
							struct chamelium_port *port,
							int x, int y,
							int w, int h)
{
	xmlrpc_value *res;
	struct chamelium_frame_dump *frame;

	res = chamelium_rpc(chamelium, port, "DumpPixels",
			    (w && h) ? "(iiiii)" : "(innnn)",
			    port->id, x, y, w, h);
	chamelium->capturing_port = port;

	frame = frame_from_xml(chamelium, res);
	xmlrpc_DECREF(res);

	return frame;
}

static void crc_from_xml(struct chamelium *chamelium,
			 xmlrpc_value *xml_crc, igt_crc_t *out)
{
	xmlrpc_value *res;
	int i;

	out->n_words = xmlrpc_array_size(&chamelium->env, xml_crc);
	for (i = 0; i < out->n_words; i++) {
		xmlrpc_array_read_item(&chamelium->env, xml_crc, i, &res);
		xmlrpc_read_int(&chamelium->env, res, (int*)&out->crc[i]);
		xmlrpc_DECREF(res);
	}
}

/**
 * chamelium_get_crc_for_area:
 * @chamelium: The Chamelium instance to use
 * @port: The port to perform the CRC checking on
 * @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, or %0 for the whole display
 * @h: The height of the area to fetch the CRC from, or %0 for the whole display
 *
 * 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.
 *
 * As an important note: some of the EDIDs provided by the Chamelium cause
 * certain GPU drivers to default to using limited color ranges. This can cause
 * video captures from the Chamelium to provide different images then expected
 * due to the difference in color ranges (framebuffer uses full color range,
 * but the video output doesn't), and as a result lead to CRC mismatches. To
 * workaround this, the caller should force the connector to use full color
 * ranges by using #kmstest_set_connector_broadcast_rgb before setting up the
 * display.
 *
 * After the caller is finished with the EDID returned by this function, the
 * caller should manually free the resources associated with it.
 *
 * Returns: The CRC read back from the chamelium
 */
igt_crc_t *chamelium_get_crc_for_area(struct chamelium *chamelium,
				      struct chamelium_port *port,
				      int x, int y, int w, int h)
{
	xmlrpc_value *res;
	igt_crc_t *ret = malloc(sizeof(igt_crc_t));

	res = chamelium_rpc(chamelium, port, "ComputePixelChecksum",
			    (w && h) ? "(iiiii)" : "(innnn)",
			    port->id, x, y, w, h);
	chamelium->capturing_port = port;

	crc_from_xml(chamelium, res, ret);
	xmlrpc_DECREF(res);

	return ret;
}

/**
 * chamelium_start_capture:
 * @chamelium: The Chamelium instance to use
 * @port: The port to perform the video capture on
 * @x: The X coordinate to crop the video to
 * @y: The Y coordinate to crop the video to
 * @w: The width of the cropped video, or %0 for the whole display
 * @h: The height of the cropped video, or %0 for the whole display
 *
 * Starts capturing video frames on the given Chamelium port. Once the user is
 * finished capturing frames, they should call #chamelium_stop_capture.
 *
 * A blocking, one-shot version of this function is available: see
 * #chamelium_capture
 *
 * As an important note: some of the EDIDs provided by the Chamelium cause
 * certain GPU drivers to default to using limited color ranges. This can cause
 * video captures from the Chamelium to provide different images then expected
 * due to the difference in color ranges (framebuffer uses full color range,
 * but the video output doesn't), and as a result lead to CRC and frame dump
 * comparison mismatches. To workaround this, the caller should force the
 * connector to use full color ranges by using
 * #kmstest_set_connector_broadcast_rgb before setting up the display.
 */
void chamelium_start_capture(struct chamelium *chamelium,
			     struct chamelium_port *port, int x, int y, int w, int h)
{
	xmlrpc_DECREF(chamelium_rpc(chamelium, port, "StartCapturingVideo",
				    (w && h) ? "(iiiii)" : "(innnn)",
				    port->id, x, y, w, h));
	chamelium->capturing_port = port;
}

/**
 * chamelium_stop_capture:
 * @chamelium: The Chamelium instance to use
 * @frame_count: The number of frames to wait to capture, or %0 to stop
 * immediately
 *
 * Finishes capturing video frames on the given Chamelium port. If @frame_count
 * is specified, this call will block until the given number of frames have been
 * captured.
 */
void chamelium_stop_capture(struct chamelium *chamelium, int frame_count)
{
	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "StopCapturingVideo",
				    "(i)", frame_count));
}

/**
 * chamelium_capture:
 * @chamelium: The Chamelium instance to use
 * @port: The port to perform the video capture on
 * @x: The X coordinate to crop the video to
 * @y: The Y coordinate to crop the video to
 * @w: The width of the cropped video, or %0 for the whole display
 * @h: The height of the cropped video, or %0 for the whole display
 * @frame_count: The number of frames to capture
 *
 * Captures the given number of frames on the chamelium. This is equivalent to
 * calling #chamelium_start_capture immediately followed by
 * #chamelium_stop_capture. The caller is blocked until all of the frames have
 * been captured.
 *
 * As an important note: some of the EDIDs provided by the Chamelium cause
 * certain GPU drivers to default to using limited color ranges. This can cause
 * video captures from the Chamelium to provide different images then expected
 * due to the difference in color ranges (framebuffer uses full color range,
 * but the video output doesn't), and as a result lead to CRC and frame dump
 * comparison mismatches. To workaround this, the caller should force the
 * connector to use full color ranges by using
 * #kmstest_set_connector_broadcast_rgb before setting up the display.
 */
void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
		       int x, int y, int w, int h, int frame_count)
{
	xmlrpc_DECREF(chamelium_rpc(chamelium, port, "CaptureVideo",
				    (w && h) ? "(iiiiii)" : "(iinnnn)",
				    port->id, frame_count, x, y, w, h));
	chamelium->capturing_port = port;
}

/**
 * chamelium_read_captured_crcs:
 * @chamelium: The Chamelium instance to use
 * @frame_count: Where to store the number of CRCs we read in
 *
 * Reads all of the CRCs that have been captured thus far from the Chamelium.
 *
 * Returns: An array of @frame_count length containing all of the CRCs we read
 */
igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
					int *frame_count)
{
	igt_crc_t *ret;
	xmlrpc_value *res, *elem;
	int i;

	res = chamelium_rpc(chamelium, NULL, "GetCapturedChecksums", "(in)", 0);

	*frame_count = xmlrpc_array_size(&chamelium->env, res);
	ret = calloc(*frame_count, sizeof(igt_crc_t));

	for (i = 0; i < *frame_count; i++) {
		xmlrpc_array_read_item(&chamelium->env, res, i, &elem);

		crc_from_xml(chamelium, elem, &ret[i]);
		ret[i].frame = i;

		xmlrpc_DECREF(elem);
	}

	xmlrpc_DECREF(res);

	return ret;
}

/**
 * chamelium_port_read_captured_frame:
 *
 * @chamelium: The Chamelium instance to use
 * @index: The index of the captured frame we want to get
 *
 * Retrieves a single video frame captured during the last video capture on the
 * Chamelium. This data should be freed using #chamelium_destroy_frame_data
 *
 * Returns: a chamelium_frame_dump struct
 */
struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
							   unsigned int index)
{
	xmlrpc_value *res;
	struct chamelium_frame_dump *frame;

	res = chamelium_rpc(chamelium, NULL, "ReadCapturedFrame", "(i)", index);
	frame = frame_from_xml(chamelium, res);
	xmlrpc_DECREF(res);

	return frame;
}

/**
 * chamelium_get_captured_frame_count:
 * @chamelium: The Chamelium instance to use
 *
 * Gets the number of frames that were captured during the last video capture.
 *
 * Returns: the number of frames the Chamelium captured during the last video
 * capture.
 */
int chamelium_get_captured_frame_count(struct chamelium *chamelium)
{
	xmlrpc_value *res;
	int ret;

	res = chamelium_rpc(chamelium, NULL, "GetCapturedFrameCount", "()");
	xmlrpc_read_int(&chamelium->env, res, &ret);

	xmlrpc_DECREF(res);
	return ret;
}

bool chamelium_supports_get_last_infoframe(struct chamelium *chamelium)
{
	return chamelium_supports_method(chamelium, "GetLastInfoFrame");
}

static const char *
chamelium_infoframe_type_str(enum chamelium_infoframe_type type)
{
	switch (type) {
	case CHAMELIUM_INFOFRAME_AVI:
		return "avi";
	case CHAMELIUM_INFOFRAME_AUDIO:
		return "audio";
	case CHAMELIUM_INFOFRAME_MPEG:
		return "mpeg";
	case CHAMELIUM_INFOFRAME_VENDOR:
		return "vendor";
	}
	assert(0); /* unreachable */
}

struct chamelium_infoframe *
chamelium_get_last_infoframe(struct chamelium *chamelium,
			     struct chamelium_port *port,
			     enum chamelium_infoframe_type type)
{
	xmlrpc_value *res, *res_version, *res_payload;
	struct chamelium_infoframe *infoframe;
	const unsigned char *payload;

	res = chamelium_rpc(chamelium, NULL, "GetLastInfoFrame", "(is)",
			    port->id, chamelium_infoframe_type_str(type));
	xmlrpc_struct_find_value(&chamelium->env, res, "version", &res_version);
	xmlrpc_struct_find_value(&chamelium->env, res, "payload", &res_payload);
	infoframe = calloc(1, sizeof(*infoframe));
	xmlrpc_read_int(&chamelium->env, res_version, &infoframe->version);
	xmlrpc_read_base64(&chamelium->env, res_payload,
			   &infoframe->payload_size, &payload);
	/* xmlrpc-c's docs say payload is actually not constant */
	infoframe->payload = (uint8_t *) payload;
	xmlrpc_DECREF(res_version);
	xmlrpc_DECREF(res_payload);
	xmlrpc_DECREF(res);

	if (infoframe->payload_size == 0) {
		chamelium_infoframe_destroy(infoframe);
		return NULL;
	}
	return infoframe;
}

void chamelium_infoframe_destroy(struct chamelium_infoframe *infoframe)
{
	free(infoframe->payload);
	free(infoframe);
}

bool chamelium_has_audio_support(struct chamelium *chamelium,
				 struct chamelium_port *port)
{
	xmlrpc_value *res;
	xmlrpc_bool has_support;

	if (!chamelium_supports_method(chamelium, "GetAudioFormat")) {
		igt_debug("The Chamelium device doesn't support GetAudioFormat\n");
		return false;
	}

	res = chamelium_rpc(chamelium, port, "HasAudioSupport", "(i)", port->id);
	xmlrpc_read_bool(&chamelium->env, res, &has_support);
	xmlrpc_DECREF(res);

	return has_support;
}

/**
 * chamelium_get_audio_channel_mapping:
 * @chamelium: the Chamelium instance
 * @port: the audio port
 * @mapping: will be filled with the channel mapping
 *
 * Obtains the channel mapping for an audio port.
 *
 * Audio channels are not guaranteed not to be swapped. Users can use the
 * channel mapping to match an input channel to a capture channel.
 *
 * The mapping contains one element per capture channel. Each element indicates
 * which input channel the capture channel is mapped to. As a special case, -1
 * means that the channel isn't mapped.
 */
void chamelium_get_audio_channel_mapping(struct chamelium *chamelium,
					 struct chamelium_port *port,
					 int mapping[static CHAMELIUM_MAX_AUDIO_CHANNELS])
{
	xmlrpc_value *res, *res_channel;
	int res_len, i;

	res = chamelium_rpc(chamelium, port, "GetAudioChannelMapping", "(i)",
			    port->id);
	res_len = xmlrpc_array_size(&chamelium->env, res);
	igt_assert(res_len == CHAMELIUM_MAX_AUDIO_CHANNELS);
	for (i = 0; i < res_len; i++) {
		xmlrpc_array_read_item(&chamelium->env, res, i, &res_channel);
		xmlrpc_read_int(&chamelium->env, res_channel, &mapping[i]);
		xmlrpc_DECREF(res_channel);
	}
	xmlrpc_DECREF(res);
}

static void audio_format_from_xml(struct chamelium *chamelium,
				  xmlrpc_value *res, int *rate, int *channels)
{
	xmlrpc_value *res_type, *res_rate, *res_sample_format, *res_channel;
	char *type, *sample_format;

	xmlrpc_struct_find_value(&chamelium->env, res, "file_type", &res_type);
	xmlrpc_struct_find_value(&chamelium->env, res, "rate", &res_rate);
	xmlrpc_struct_find_value(&chamelium->env, res, "sample_format", &res_sample_format);
	xmlrpc_struct_find_value(&chamelium->env, res, "channel", &res_channel);

	xmlrpc_read_string(&chamelium->env, res_type, (const char **) &type);
	igt_assert(strcmp(type, "raw") == 0);
	free(type);

	xmlrpc_read_string(&chamelium->env, res_sample_format, (const char **) &sample_format);
	igt_assert(strcmp(sample_format, "S32_LE") == 0);
	free(sample_format);

	if (rate)
		xmlrpc_read_int(&chamelium->env, res_rate, rate);
	if (channels) {
		xmlrpc_read_int(&chamelium->env, res_channel, channels);
		igt_assert(*channels <= CHAMELIUM_MAX_AUDIO_CHANNELS);
	}

	xmlrpc_DECREF(res_channel);
	xmlrpc_DECREF(res_sample_format);
	xmlrpc_DECREF(res_rate);
	xmlrpc_DECREF(res_type);
}

/**
 * chamelium_get_audio_format:
 * @chamelium: the Chamelium instance
 * @port: the audio port
 * @rate: if non-NULL, will be set to the sample rate in Hz
 * @channels: if non-NULL, will be set to the number of channels
 *
 * Obtains the audio format of the captured data. Users should start sending an
 * audio signal to the Chamelium device prior to calling this function.
 *
 * The captured data is guaranteed to be in the S32_LE format.
 */
void chamelium_get_audio_format(struct chamelium *chamelium,
				struct chamelium_port *port,
				int *rate, int *channels)
{
	xmlrpc_value *res;

	res = chamelium_rpc(chamelium, port, "GetAudioFormat", "(i)",
			    port->id);
	audio_format_from_xml(chamelium, res, rate, channels);
	xmlrpc_DECREF(res);
}

/**
 * chamelium_start_capturing_audio:
 * @chamelium: the Chamelium instance
 * @port: the port to capture audio from (it must support audio)
 * @save_to_file: whether the captured audio data should be saved to a file on
 * the Chamelium device
 *
 * Starts capturing audio from a Chamelium port. To stop the capture, use
 * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
 * stream server or enable @save_to_file (the latter is mainly useful for
 * debugging purposes).
 *
 * It isn't possible to capture audio from multiple ports at the same time.
 */
void chamelium_start_capturing_audio(struct chamelium *chamelium,
				    struct chamelium_port *port,
				    bool save_to_file)
{
	xmlrpc_value *res;

	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
			    port->id, save_to_file);
	xmlrpc_DECREF(res);
}

/**
 * chamelium_stop_capturing_audio:
 * @chamelium: the Chamelium instance
 * @port: the port from which audio is being captured
 *
 * Stops capturing audio from a Chamelium port. If
 * #chamelium_start_capturing_audio has been called with @save_to_file enabled,
 * this function will return a #chamelium_audio_file struct containing details
 * about the audio file. Once the caller is done with the struct, they should
 * release it with #chamelium_destroy_audio_file.
 */
struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
							    struct chamelium_port *port)
{
	xmlrpc_value *res, *res_path, *res_props;
	struct chamelium_audio_file *file = NULL;
	char *path;

	res = chamelium_rpc(chamelium, NULL, "StopCapturingAudio", "(i)",
			    port->id);
	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_path);
	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_props);

	xmlrpc_read_string(&chamelium->env, res_path, (const char **) &path);

	if (strlen(path) > 0) {
		file = calloc(1, sizeof(*file));
		file->path = path;

		audio_format_from_xml(chamelium, res_props,
				      &file->rate, &file->channels);
	} else {
		free(path);
	}

	xmlrpc_DECREF(res_props);
	xmlrpc_DECREF(res_path);
	xmlrpc_DECREF(res);

	return file;
}

static pixman_image_t *convert_frame_format(pixman_image_t *src,
					    int format)
{
	pixman_image_t *converted;
	int w = pixman_image_get_width(src), h = pixman_image_get_height(src);

	converted = pixman_image_create_bits(format, w, h, NULL,
					     PIXMAN_FORMAT_BPP(format) / 8 * w);
	pixman_image_composite(PIXMAN_OP_ADD, src, NULL, converted,
			       0, 0, 0, 0, 0, 0, w, h);

	return converted;
}

static cairo_surface_t *convert_frame_dump_argb32(const struct chamelium_frame_dump *dump)
{
	cairo_surface_t *dump_surface;
	pixman_image_t *image_bgr;
	pixman_image_t *image_argb;
	int w = dump->width, h = dump->height;
	uint32_t *bits_bgr = (uint32_t *) dump->bgr;
	unsigned char *bits_argb;
	unsigned char *bits_target;
	int size;

	image_bgr = pixman_image_create_bits(
	    PIXMAN_b8g8r8, w, h, bits_bgr,
	    PIXMAN_FORMAT_BPP(PIXMAN_b8g8r8) / 8 * w);
	image_argb = convert_frame_format(image_bgr, PIXMAN_x8r8g8b8);
	pixman_image_unref(image_bgr);

	bits_argb = (unsigned char *) pixman_image_get_data(image_argb);

	dump_surface = cairo_image_surface_create(
	    CAIRO_FORMAT_ARGB32, w, h);

	bits_target = cairo_image_surface_get_data(dump_surface);
	size = cairo_image_surface_get_stride(dump_surface) * h;
	memcpy(bits_target, bits_argb, size);
	cairo_surface_mark_dirty(dump_surface);

	pixman_image_unref(image_argb);

	return dump_surface;
}

static void compared_frames_dump(cairo_surface_t *reference,
				 cairo_surface_t *capture,
				 igt_crc_t *reference_crc,
				 igt_crc_t *capture_crc)
{
	char *reference_suffix;
	char *capture_suffix;
	igt_crc_t local_reference_crc;
	igt_crc_t local_capture_crc;

	igt_assert(reference && capture);

	if (!reference_crc) {
		chamelium_do_calculate_fb_crc(reference, &local_reference_crc);
		reference_crc = &local_reference_crc;
	}

	if (!capture_crc) {
		chamelium_do_calculate_fb_crc(reference, &local_capture_crc);
		capture_crc = &local_capture_crc;
	}

	reference_suffix = igt_crc_to_string_extended(reference_crc, '-', 2);
	capture_suffix = igt_crc_to_string_extended(capture_crc, '-', 2);

	/* Write reference and capture frames to png. */
	igt_write_compared_frames_to_png(reference, capture, reference_suffix,
					 capture_suffix);

	free(reference_suffix);
	free(capture_suffix);
}

/**
 * chamelium_assert_frame_eq:
 * @chamelium: The chamelium instance the frame dump belongs to
 * @dump: The chamelium frame dump to check
 * @fb: The framebuffer to check against
 *
 * Asserts that the image contained in the chamelium frame dump is identical to
 * the given framebuffer. Useful for scenarios where pre-calculating CRCs might
 * not be ideal.
 */
void chamelium_assert_frame_eq(const struct chamelium *chamelium,
			       const struct chamelium_frame_dump *dump,
			       struct igt_fb *fb)
{
	cairo_surface_t *fb_surface;
	pixman_image_t *reference_src, *reference_bgr;
	int w = dump->width, h = dump->height;
	bool eq;

	/* Get the cairo surface for the framebuffer */
	fb_surface = igt_get_cairo_surface(chamelium->drm_fd, fb);

	/*
	 * Convert the reference image into the same format as the chamelium
	 * image
	 */
	reference_src = pixman_image_create_bits(
	    PIXMAN_x8r8g8b8, w, h,
	    (void*)cairo_image_surface_get_data(fb_surface),
	    cairo_image_surface_get_stride(fb_surface));
	reference_bgr = convert_frame_format(reference_src, PIXMAN_b8g8r8);
	pixman_image_unref(reference_src);

	/* Now do the actual comparison */
	eq = memcmp(dump->bgr, pixman_image_get_data(reference_bgr),
		    dump->size) == 0;

	pixman_image_unref(reference_bgr);

	igt_fail_on_f(!eq,
		      "Chamelium frame dump didn't match reference image\n");
}

/**
 * chamelium_assert_crc_eq_or_dump:
 * @chamelium: The chamelium instance the frame dump belongs to
 * @reference_crc: The CRC for the reference frame
 * @capture_crc: The CRC for the captured frame
 * @fb: pointer to an #igt_fb structure
 *
 * Asserts that the CRC provided for both the reference and the captured frame
 * are identical. If they are not, this grabs the captured frame and saves it
 * along with the reference to a png file.
 */
void chamelium_assert_crc_eq_or_dump(struct chamelium *chamelium,
				     igt_crc_t *reference_crc,
				     igt_crc_t *capture_crc, struct igt_fb *fb,
				     int index)
{
	struct chamelium_frame_dump *frame;
	cairo_surface_t *reference;
	cairo_surface_t *capture;
	bool eq;

	igt_debug("Reference CRC: %s\n", igt_crc_to_string(reference_crc));
	igt_debug("Captured CRC: %s\n", igt_crc_to_string(capture_crc));

	eq = igt_check_crc_equal(reference_crc, capture_crc);
	if (!eq && igt_frame_dump_is_enabled()) {
		/* Convert the reference framebuffer to cairo. */
		reference = igt_get_cairo_surface(chamelium->drm_fd, fb);

		/* Grab the captured frame from the Chamelium. */
		frame = chamelium_read_captured_frame(chamelium, index);
		igt_assert(frame);

		/* Convert the captured frame to cairo. */
		capture = convert_frame_dump_argb32(frame);
		igt_assert(capture);

		compared_frames_dump(reference, capture, reference_crc,
				     capture_crc);

		cairo_surface_destroy(reference);
		cairo_surface_destroy(capture);
		chamelium_destroy_frame_dump(frame);
	}

	igt_assert(eq);
}

/**
 * chamelium_assert_frame_match_or_dump:
 * @chamelium: The chamelium instance the frame dump belongs to
 * @frame: The chamelium frame dump to match
 * @fb: pointer to an #igt_fb structure
 * @check: the type of frame matching check to use
 *
 * Asserts that the provided captured frame matches the reference frame from
 * the framebuffer. If they do not, this saves the reference and captured frames
 * to a png file.
 */
void chamelium_assert_frame_match_or_dump(struct chamelium *chamelium,
					  struct chamelium_port *port,
					  const struct chamelium_frame_dump *frame,
					  struct igt_fb *fb,
					  enum chamelium_check check)
{
	igt_assert(chamelium_frame_match_or_dump(chamelium, port,
						 frame, fb, check));
}

/**
 * chamelium_frame_match_or_dump:
 * @chamelium: The chamelium instance the frame dump belongs to
 * @frame: The chamelium frame dump to match
 * @fb: pointer to an #igt_fb structure
 * @check: the type of frame matching check to use
 *
 * Returns bool that the provided captured frame matches the reference
 * frame from the framebuffer. If they do not, this saves the reference
 * and captured frames to a png file.
 */
bool chamelium_frame_match_or_dump(struct chamelium *chamelium,
				   struct chamelium_port *port,
				   const struct chamelium_frame_dump *frame,
				   struct igt_fb *fb,
				   enum chamelium_check check)
{
	cairo_surface_t *reference;
	cairo_surface_t *capture;
	igt_crc_t *reference_crc;
	igt_crc_t *capture_crc;
	bool match;

	/* Grab the reference frame from framebuffer */
	reference = igt_get_cairo_surface(chamelium->drm_fd, fb);

	/* Grab the captured frame from chamelium */
	capture = convert_frame_dump_argb32(frame);

	switch (check) {
	case CHAMELIUM_CHECK_ANALOG:
		match = igt_check_analog_frame_match(reference, capture);
		break;
	case CHAMELIUM_CHECK_CHECKERBOARD:
		match = igt_check_checkerboard_frame_match(reference, capture);
		break;
	default:
		igt_assert(false);
	}

	if (!match && igt_frame_dump_is_enabled()) {
		reference_crc = malloc(sizeof(igt_crc_t));
		igt_assert(reference_crc);

		/* Calculate the reference frame CRC. */
		chamelium_do_calculate_fb_crc(reference, reference_crc);

		/* Get the captured frame CRC from the Chamelium. */
		capture_crc = chamelium_get_crc_for_area(chamelium, port, 0, 0,
							 0, 0);
		igt_assert(capture_crc);

		compared_frames_dump(reference, capture, reference_crc,
				     capture_crc);

		free(reference_crc);
		free(capture_crc);
	}

	cairo_surface_destroy(reference);
	cairo_surface_destroy(capture);

	return match;
}

/**
 * chamelium_analog_frame_crop:
 * @chamelium: The Chamelium instance to use
 * @dump: The chamelium frame dump to crop
 * @width: The cropped frame width
 * @height: The cropped frame height
 *
 * Detects the corners of a chamelium frame and crops it to the requested
 * width/height. This is useful for VGA frame dumps that also contain the
 * pixels dumped during the blanking intervals.
 *
 * The detection is done on a brightness-threshold-basis, that is adapted
 * to the reference frame used by i-g-t. It may not be as relevant for other
 * frames.
 */
void chamelium_crop_analog_frame(struct chamelium_frame_dump *dump, int width,
				 int height)
{
	unsigned char *bgr;
	unsigned char *p;
	unsigned char *q;
	int top, left;
	int x, y, xx, yy;
	int score;

	if (dump->width == width && dump->height == height)
		return;

	/* Start with the most bottom-right position. */
	top = dump->height - height;
	left = dump->width - width;

	igt_assert(top >= 0 && left >= 0);

	igt_debug("Cropping analog frame from %dx%d to %dx%d\n", dump->width,
		  dump->height, width, height);

	/* Detect the top-left corner of the frame. */
	for (x = 0; x < dump->width; x++) {
		for (y = 0; y < dump->height; y++) {
			p = &dump->bgr[(x + y * dump->width) * 3];

			/* Detect significantly bright pixels. */
			if (p[0] < 50 && p[1] < 50 && p[2] < 50)
				continue;

			/*
			 * Make sure close-by pixels are also significantly
			 * bright.
			 */
			score = 0;
			for (xx = x; xx < x + 10; xx++) {
				for (yy = y; yy < y + 10; yy++) {
					p = &dump->bgr[(xx + yy * dump->width) * 3];

					if (p[0] > 50 && p[1] > 50 && p[2] > 50)
						score++;
				}
			}

			/* Not enough pixels are significantly bright. */
			if (score < 25)
				continue;

			if (x < left)
				left = x;

			if (y < top)
				top = y;

			if (left == x || top == y)
				continue;
		}
	}

	igt_debug("Detected analog frame edges at %dx%d\n", left, top);

	/* Crop the frame given the detected top-left corner. */
	bgr = malloc(width * height * 3);

	for (y = 0; y < height; y++) {
		p = &dump->bgr[(left + (top + y) * dump->width) * 3];
		q = &bgr[(y * width) * 3];
		memcpy(q, p, width * 3);
	}

	free(dump->bgr);
	dump->width = width;
	dump->height = height;
	dump->bgr = bgr;
}

/**
 * chamelium_get_frame_limit:
 * @chamelium: The Chamelium instance to use
 * @port: The port to check the frame limit on
 * @w: The width of the area to get the capture frame limit for, or %0 for the
 * whole display
 * @h: The height of the area to get the capture frame limit for, or %0 for the
 * whole display
 *
 * Gets the max number of frames we can capture with the Chamelium for the given
 * resolution.
 *
 * Returns: The number of the max number of frames we can capture
 */
int chamelium_get_frame_limit(struct chamelium *chamelium,
			      struct chamelium_port *port,
			      int w, int h)
{
	xmlrpc_value *res;
	int ret;

	if (!w && !h)
		chamelium_port_get_resolution(chamelium, port, &w, &h);

	res = chamelium_rpc(chamelium, port, "GetMaxFrameLimit", "(iii)",
			    port->id, w, h);

	xmlrpc_read_int(&chamelium->env, res, &ret);
	xmlrpc_DECREF(res);

	return ret;
}

static uint32_t chamelium_xrgb_hash16(const unsigned char *buffer, int width,
				      int height, int k, int m)
{
	unsigned char r, g, b;
	uint64_t sum = 0;
	uint64_t count = 0;
	uint64_t value;
	uint32_t hash;
	int index;
	int i;

	for (i=0; i < width * height; i++) {
		if ((i % m) != k)
			continue;

		index = i * 4;

		r = buffer[index + 2];
		g = buffer[index + 1];
		b = buffer[index + 0];

		value = r | (g << 8) | (b << 16);
		sum += ++count * value;
	}

	hash = ((sum >> 0) ^ (sum >> 16) ^ (sum >> 32) ^ (sum >> 48)) & 0xffff;

	return hash;
}

static void chamelium_do_calculate_fb_crc(cairo_surface_t *fb_surface,
					  igt_crc_t *out)
{
	unsigned char *buffer;
	int n = 4;
	int w, h;
	int i, j;

	buffer = cairo_image_surface_get_data(fb_surface);
	w = cairo_image_surface_get_width(fb_surface);
	h = cairo_image_surface_get_height(fb_surface);

	for (i = 0; i < n; i++) {
		j = n - i - 1;
		out->crc[i] = chamelium_xrgb_hash16(buffer, w, h, j, n);
	}

	out->n_words = n;
}

/**
 * chamelium_calculate_fb_crc:
 * @fd: The drm file descriptor
 * @fb: The framebuffer to calculate the CRC for
 *
 * Calculates the CRC for the provided framebuffer, using the Chamelium's CRC
 * algorithm. This calculates the CRC in a synchronous fashion.
 *
 * Returns: The calculated CRC
 */
igt_crc_t *chamelium_calculate_fb_crc(int fd, struct igt_fb *fb)
{
	igt_crc_t *ret = calloc(1, sizeof(igt_crc_t));
	cairo_surface_t *fb_surface;

	/* Get the cairo surface for the framebuffer */
	fb_surface = igt_get_cairo_surface(fd, fb);

	chamelium_do_calculate_fb_crc(fb_surface, ret);

	cairo_surface_destroy(fb_surface);

	return ret;
}

static void *chamelium_calculate_fb_crc_async_work(void *data)
{
	struct chamelium_fb_crc_async_data *fb_crc;

	fb_crc = (struct chamelium_fb_crc_async_data *) data;

	chamelium_do_calculate_fb_crc(fb_crc->fb_surface, fb_crc->ret);

	return NULL;
}

/**
 * chamelium_calculate_fb_crc_launch:
 * @fd: The drm file descriptor
 * @fb: The framebuffer to calculate the CRC for
 *
 * Launches the CRC calculation for the provided framebuffer, using the
 * Chamelium's CRC algorithm. This calculates the CRC in an asynchronous
 * fashion.
 *
 * The returned structure should be passed to a subsequent call to
 * chamelium_calculate_fb_crc_result. It should not be freed.
 *
 * Returns: An intermediate structure for the CRC calculation work.
 */
struct chamelium_fb_crc_async_data *chamelium_calculate_fb_crc_async_start(int fd,
									   struct igt_fb *fb)
{
	struct chamelium_fb_crc_async_data *fb_crc;

	fb_crc = calloc(1, sizeof(struct chamelium_fb_crc_async_data));
	fb_crc->ret = calloc(1, sizeof(igt_crc_t));

	/* Get the cairo surface for the framebuffer */
	fb_crc->fb_surface = igt_get_cairo_surface(fd, fb);

	pthread_create(&fb_crc->thread_id, NULL,
		       chamelium_calculate_fb_crc_async_work, fb_crc);

	return fb_crc;
}

/**
 * chamelium_calculate_fb_crc_result:
 * @fb_crc: An intermediate structure with thread-related information
 *
 * Blocks until the asynchronous CRC calculation is finished, and then returns
 * its result.
 *
 * Returns: The calculated CRC
 */
igt_crc_t *chamelium_calculate_fb_crc_async_finish(struct chamelium_fb_crc_async_data *fb_crc)
{
	igt_crc_t *ret;

	pthread_join(fb_crc->thread_id, NULL);

	ret = fb_crc->ret;
	free(fb_crc);

	return ret;
}

static unsigned int chamelium_get_port_type(struct chamelium *chamelium,
					    struct chamelium_port *port)
{
	xmlrpc_value *res;
	const char *port_type_str;
	unsigned int port_type;

	res = chamelium_rpc(chamelium, NULL, "GetConnectorType",
			    "(i)", port->id);

	xmlrpc_read_string(&chamelium->env, res, &port_type_str);
	igt_debug("Port %d is of type '%s'\n", port->id, 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 bool chamelium_has_video_support(struct chamelium *chamelium,
					int port_id)
{
	xmlrpc_value *res;
	int has_video_support;

	res = chamelium_rpc(chamelium, NULL, "HasVideoSupport", "(i)", port_id);
	xmlrpc_read_bool(&chamelium->env, res, &has_video_support);
	xmlrpc_DECREF(res);

	return has_video_support;
}

/**
 * chamelium_get_video_ports: retrieve a list of video port IDs
 *
 * Returns: the number of video port IDs
 */
static size_t chamelium_get_video_ports(struct chamelium *chamelium,
					int port_ids[static CHAMELIUM_MAX_PORTS])
{
	xmlrpc_value *res, *res_port;
	int res_len, i, port_id;
	size_t port_ids_len = 0;

	res = __chamelium_rpc(chamelium, NULL, "GetSupportedInputs", "()");
	if (chamelium->env.fault_occurred) {
		igt_debug("Chamelium RPC call failed: %s\n",
		     chamelium->env.fault_string);

		return -1;
	}
	res_len = xmlrpc_array_size(&chamelium->env, res);
	for (i = 0; i < res_len; i++) {
		xmlrpc_array_read_item(&chamelium->env, res, i, &res_port);
		xmlrpc_read_int(&chamelium->env, res_port, &port_id);
		xmlrpc_DECREF(res_port);

		if (!chamelium_has_video_support(chamelium, port_id))
			continue;

		igt_assert(port_ids_len < CHAMELIUM_MAX_PORTS);
		port_ids[port_ids_len] = port_id;
		port_ids_len++;
	}
	xmlrpc_DECREF(res);

	return port_ids_len;
}

static void chamelium_set_port_path(struct chamelium_port *port,
									uint32_t connector_id,
									int drm_fd)
{
	uint64_t path_blob_id;
	drmModePropertyBlobPtr path_blob = NULL;

	if (kmstest_get_property(drm_fd, connector_id, DRM_MODE_OBJECT_CONNECTOR,
							 "PATH", NULL, &path_blob_id, NULL))
	{
		igt_assert(path_blob = drmModeGetPropertyBlob(drm_fd, path_blob_id));
		port->connector_path = strdup(path_blob->data);

		drmModeFreePropertyBlob(path_blob);
	}
}

static bool chamelium_read_port_mappings(struct chamelium *chamelium,
					 int drm_fd)
{
	drmModeRes *res;
	drmModeConnector *connector;
	struct chamelium_port *port;
	GError *error = NULL;
	char **group_list;
	char *group, *map_name;
	int port_i, i, j;
	bool ret = true;

	res = drmModeGetResources(drm_fd);
	if (!res)
		return false;

	group_list = g_key_file_get_groups(igt_key_file, NULL);

	/* Count how many connector mappings are specified in the config */
	for (i = 0; group_list[i] != NULL; i++) {
		if (strstr(group_list[i], "Chamelium:"))
			chamelium->port_count++;
	}
	igt_assert(chamelium->port_count <= CHAMELIUM_MAX_PORTS);

	port_i = 0;
	for (i = 0; group_list[i] != NULL; i++) {
		group = group_list[i];

		if (!strstr(group, "Chamelium:"))
			continue;

		map_name = group + (sizeof("Chamelium:") - 1);

		port = &chamelium->ports[port_i++];
		port->name = strdup(map_name);
		port->id = g_key_file_get_integer(igt_key_file, group,
						  "ChameliumPortID",
						  &error);
		if (error) {
			igt_warn("Failed to read chamelium port ID for %s: %s\n",
				 map_name, error->message);
			ret = false;
			goto out;
		}

		port->type = chamelium_get_port_type(chamelium, port);
		if (port->type == DRM_MODE_CONNECTOR_Unknown) {
			igt_warn("Unable to retrieve the physical port type from the Chamelium for '%s'\n",
				 map_name);
			ret = false;
			goto out;
		}

		if (g_key_file_has_key(igt_key_file, group, "AdapterAllowed", NULL)) {
			port->adapter_allowed = g_key_file_get_boolean(igt_key_file, group,
								       "AdapterAllowed", &error);
			if (error) {
				igt_warn("Unable to read AdapterAllowed: %s\n", error->message);
				ret = false;
				goto out;
			}
		}

		for (j = 0;
		     j < res->count_connectors && !port->connector_id;
		     j++) {
			char name[50];

			connector = drmModeGetConnectorCurrent(
			    drm_fd, res->connectors[j]);

			/* We have to generate the connector name on our own */
			snprintf(name, 50, "%s-%u",
				 kmstest_connector_type_str(connector->connector_type),
				 connector->connector_type_id);

			if (strcmp(name, map_name) == 0)
				port->connector_id = connector->connector_id;

			chamelium_set_port_path(port, connector->connector_id,
									drm_fd);

			drmModeFreeConnector(connector);
		}
		if (!port->connector_id) {
			igt_warn("No connector found with name '%s'\n",
				 map_name);
			ret = false;
			goto out;
		}

		igt_debug("Port '%s' with physical type '%s' mapped to Chamelium port %d\n",
			  map_name, kmstest_connector_type_str(port->type),
			  port->id);
	}

out:
	g_strfreev(group_list);
	drmModeFreeResources(res);

	return ret;
}

static int port_id_from_edid(int drm_fd, drmModeConnector *connector)
{
	int port_id = -1;
	bool ok;
	uint64_t edid_blob_id;
	drmModePropertyBlobRes *edid_blob;
	const struct edid *edid;
	char mfg[3];

	/* MST connectors stop being valid on unplug but would still exist in DRM Resources. */
	if (!connector)
		return -1;

	if (connector->connection != DRM_MODE_CONNECTED) {
		igt_debug("Skipping auto-discovery for connector %s-%d: "
			  "connector status is not connected\n",
			  kmstest_connector_type_str(connector->connector_type),
			  connector->connector_type_id);
		return -1;
	}

	ok = kmstest_get_property(drm_fd, connector->connector_id,
				  DRM_MODE_OBJECT_CONNECTOR, "EDID",
				  NULL, &edid_blob_id, NULL);
	if (!ok || !edid_blob_id) {
		igt_debug("Skipping auto-discovery for connector %s-%d: "
			  "missing the EDID property\n",
			  kmstest_connector_type_str(connector->connector_type),
			  connector->connector_type_id);
		return -1;
	}

	edid_blob = drmModeGetPropertyBlob(drm_fd, edid_blob_id);
	igt_assert(edid_blob);

	edid = (const struct edid *) edid_blob->data;

	edid_get_mfg(edid, mfg);
	if (memcmp(mfg, "IGT", 3) != 0) {
		igt_debug("Skipping connector %s-%d for auto-discovery: "
			  "manufacturer is %.3s, not IGT\n",
			  kmstest_connector_type_str(connector->connector_type),
			  connector->connector_type_id, mfg);
		goto out;
	}

	if (edid->prod_code[0] != 'C' || edid->prod_code[1] != 'H') {
		igt_warn("Invalid EDID for IGT connector %s-%d: "
			  "invalid product code\n",
			  kmstest_connector_type_str(connector->connector_type),
			  connector->connector_type_id);
		goto out;
	}

	port_id = *(uint32_t *) &edid->serial;
	igt_debug("Auto-discovery mapped connector %s-%d to Chamelium "
		  "port ID %d\n",
		  kmstest_connector_type_str(connector->connector_type),
		  connector->connector_type_id, port_id);

out:
	drmModeFreePropertyBlob(edid_blob);
	return port_id;
}

static bool get_connector_id_for_port(struct chamelium *chamelium,
				      const int expected_port_id,
				      uint32_t *attached_connector_id)
{
	int i;

	int drm_fd = chamelium->drm_fd;
	drmModeRes *res = drmModeGetResources(drm_fd);
	if (!res)
		return false;

	for (i = 0; i < res->count_connectors; i++) {
		uint32_t conn_id;
		size_t j;

		/* Read the EDID and parse the Chamelium port ID we stored there. */
		drmModeConnector *connector =
			drmModeGetConnector(drm_fd, res->connectors[i]);
		int port_id = port_id_from_edid(drm_fd, connector);
		drmModeFreeConnector(connector);
		if (port_id != expected_port_id)
			continue;

		/* If we already have a mapping from the config file, check that it's consistent. */
		conn_id = res->connectors[i];
		for (j = 0; j < chamelium->port_count; j++) {
			struct chamelium_port *port = &chamelium->ports[j];
			if (port->connector_id == conn_id) {
				igt_assert_f(
					port->id == port_id,
					"Inconsistency detected in .igtrc: "
					"connector %s is configured with "
					"Chamelium port %d, but is "
					"connected to port %d\n",
					port->name, port->id, port_id);
				return false;
			}
		}

		*attached_connector_id = conn_id;
		return true;
	}

	return false;
}

static void selective_sort_ports(struct chamelium_port *ports) {
    int mapped_count = 0;
    int unmapped_count = 0;
    int mapped_index = 0;
    int unmapped_index = mapped_count;
    struct chamelium_port sorted_ports[CHAMELIUM_MAX_PORTS];

    /*
     * Count the number of mapped and unmapped ports
     */
    for (int i = 0; i < CHAMELIUM_MAX_PORTS; i++) {
        if (ports[i].is_mapped) {
            mapped_count++;
        } else {
            unmapped_count++;
        }
    }

    /*
     * Rearrange the ports such that mapped ports are at the start
     */
    unmapped_index = mapped_count;

    for (int i = 0; i < CHAMELIUM_MAX_PORTS; i++) {
        if (ports[i].is_mapped) {
            sorted_ports[mapped_index++] = ports[i];
        } else {
            sorted_ports[unmapped_index++] = ports[i];
        }
    }

    /*
     * Copy the sorted ports back to the original array
     */
    memcpy(ports, sorted_ports, CHAMELIUM_MAX_PORTS * sizeof(struct chamelium_port));
}

/**
 * chamelium_autodiscover: automagically discover the Chamelium port mapping
 *
 * The Chamelium API uses port IDs wheras the Device Under Test uses DRM
 * connectors. We need to know which Chamelium port is plugged to a given DRM
 * connector. This has typically been done via a configuration file in the
 * past (see #chamelium_read_port_mappings), but this function provides an
 * automatic way to do it.
 *
 * We will plug the Chamelium ports one by one with a different EDID on each. 
 * Then we'll read the EDID on each DRM connector and infer the Chamelium port ID.
 */
static bool chamelium_autodiscover(struct chamelium *chamelium)
{
	struct timespec start;
	struct chamelium_edid *edid;
	size_t port_count;
	size_t i;
	bool is_any_port_mapped = false;
	uint64_t elapsed_ns;

	int candidate_ports[CHAMELIUM_MAX_PORTS];
	size_t candidate_ports_len =
		chamelium_get_video_ports(chamelium, candidate_ports);
	igt_assert(candidate_ports_len > 0);

	igt_debug("Starting Chamelium port auto-discovery on %zu ports\n",
		  candidate_ports_len);
	igt_gettime(&start);

	/* Reset Chamelium to turn off all ports and test them one at a time. */
	chamelium_reset(chamelium);

	edid = chamelium_new_edid(chamelium, igt_kms_get_base_edid());

	/* Set EDID and plug ports we want to auto-discover */
	port_count = chamelium->port_count;
	/* Iterate over every port that Chamelium supports and check if it's connected. */
	for (i = 0; i < candidate_ports_len; i++) {
		int j;
		int wait_interval_ms, wait_timeout_ms;
		bool ret;
		drmModeConnector *connector;
		uint32_t conn_id = 0;
		int drm_fd = chamelium->drm_fd;
		char conn_name[64];

		int port_id = candidate_ports[i];
		/* Get or add a chamelium_port slot - The port could have been created earlier. */
		struct chamelium_port *port = NULL;
		for (j = 0; j < chamelium->port_count; j++) {
			if (chamelium->ports[j].id == port_id) {
				port = &chamelium->ports[j];
				break;
			}
		}
		if (!port) {
			igt_assert(port_count < CHAMELIUM_MAX_PORTS);
			port = &chamelium->ports[port_count];
			port_count++;
			igt_assert(port);
			port->id = port_id;
		}

		/* Chameleon V3 works nicely with EDIDs assigned to ports that are not connected. */
		chamelium_port_set_edid(chamelium, port, edid);
		chamelium_plug(chamelium, port);

		/* The ITE chip on Chamelium V3 can only hold 1 EDID at a time, so let's test each port individually. */
		wait_interval_ms = 1000;
		wait_timeout_ms = CHAMELIUM_HOTPLUG_DETECTION_DELAY * 1000 +
				  wait_interval_ms;
		igt_info(
			"Polling every %f second(s) for %d seconds for the hotplug to take effect.\n",
			(float)wait_interval_ms / 1000,
			CHAMELIUM_HOTPLUG_DETECTION_DELAY);

		ret = igt_wait(get_connector_id_for_port(chamelium, port_id,
							 &conn_id),
			       wait_timeout_ms, wait_interval_ms);
		if (!ret || conn_id < 1) {
			igt_info("Failed to auto-discover port %d\n", port_id);
			goto unplug_port;
		}
		port->is_mapped = true;
		is_any_port_mapped = true;

		/* If we found a connector for our port, increment the number of valid Chamelium ports. */
		chamelium->port_count++;

		/* With a valid port, assign all of its properties. */
		port->type = chamelium_get_port_type(chamelium, port);
		port->connector_id = conn_id;
		port->adapter_allowed = false;
		chamelium_set_port_path(port, conn_id, drm_fd);

		/* Get and assign Connector name. */
		connector = drmModeGetConnectorCurrent(drm_fd, conn_id);
		snprintf(conn_name, sizeof(conn_name), "%s-%u",
			 kmstest_connector_type_str(connector->connector_type),
			 connector->connector_type_id);
		drmModeFreeConnector(connector);
		port->name = strdup(conn_name);

unplug_port:
		/* Unplug the port so we can move on to the next one. */
		chamelium_unplug(chamelium, port);
	}

	/* After we're all set, turn on all supported ports */
	for (i = 0; i < CHAMELIUM_MAX_PORTS; i++) {
		struct chamelium_port *port = &chamelium->ports[i];
		if (port->is_mapped)
			chamelium_plug(chamelium, port);
	}
	sleep(CHAMELIUM_HOTPLUG_DETECTION_DELAY);

	/*
	 * Sort the ports based on the is_mapped flag
	 */
	selective_sort_ports(chamelium->ports);
	elapsed_ns = igt_nsec_elapsed(&start);
	igt_debug("Auto-discovery took %fms and found %i connector(s)\n",
		  (float)elapsed_ns / (1000 * 1000), chamelium->port_count);

	return is_any_port_mapped;
}

static bool chamelium_read_config(struct chamelium *chamelium)
{
	GError *error = NULL;

	if (!igt_key_file) {
		igt_debug("No configuration file available for chamelium\n");
		return false;
	}

	chamelium->url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
					       &error);
	if (!chamelium->url) {
		igt_debug("Couldn't read chamelium URL from config file: %s\n",
			 error->message);
		return false;
	}

	return true;
}

/**
 * chamelium_reset:
 * @chamelium: The Chamelium instance to use
 *
 * 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(struct chamelium *chamelium)
{
	igt_debug("Resetting the chamelium\n");
	xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Reset", "()"));
}

static void chamelium_exit_handler(int sig)
{
	igt_debug("Deinitializing Chamelium\n");

	if (cleanup_instance)
		chamelium_deinit(cleanup_instance);
}

/**
 * chamelium_deinit_rpc_only:
 * @chamelium: The Chamelium instance to use
 *
 * Frees the resources used by a connection to the chamelium that was set up
 * with #chamelium_init_rpc_only.
 */
void chamelium_deinit_rpc_only(struct chamelium *chamelium)
{
	xmlrpc_env_clean(&chamelium->env);
	free(chamelium);
}

/**
 * chamelium_init_rpc_only:
 *
 * Sets up a connection with a chamelium, using the URL specified in the
 * Chamelium configuration. The function initializes only the RPC - no port
 * autodiscovery happens, which means only the functions that do not require
 * struct #chamelium_port can be called with an instance produced by this
 * function.
 *
 * #chamelium_init is almost always a better choice.
 *
 * Returns: A newly initialized chamelium struct, or NULL on lack of
 * configuration
 */
struct chamelium *chamelium_init_rpc_only(void)
{
	struct chamelium *chamelium = malloc(sizeof(struct chamelium));
	struct xmlrpc_clientparms clientparms;
	struct xmlrpc_curl_xportparms curlparms;

	if (!chamelium)
		return NULL;

	memset(chamelium, 0, sizeof(*chamelium));
	memset(&clientparms, 0, sizeof(clientparms));
	memset(&curlparms, 0, sizeof(curlparms));

	/* curl's timeout is in milliseconds */
	curlparms.timeout = _RECEIVER_RESPONSIVE_AFTER_RESET_SECONDS * 1000;

	clientparms.transport = "curl";
	clientparms.transportparmsP = &curlparms;
	clientparms.transportparm_size = XMLRPC_CXPSIZE(timeout);

	chamelium->drm_fd = -1;

	/* Setup the libxmlrpc context */
	xmlrpc_env_init(&chamelium->env);
	xmlrpc_client_setup_global_const(&chamelium->env);
	xmlrpc_client_create(&chamelium->env, XMLRPC_CLIENT_NO_FLAGS, PACKAGE,
			     PACKAGE_VERSION, &clientparms, 0, &chamelium->client);
	if (chamelium->env.fault_occurred) {
		igt_debug("Failed to init xmlrpc: %s\n",
			  chamelium->env.fault_string);
		goto error;
	}

	if (!chamelium_read_config(chamelium))
		goto error;

	return chamelium;
error:
	chamelium_deinit_rpc_only(chamelium);
	return NULL;
}

/**
 * chamelium_init:
 * @chamelium: The Chamelium instance to use
 * @drm_fd: a display initialized with #igt_display_require
 *
 * Sets up a connection with a chamelium, using the URL specified in the
 * Chamelium configuration. This must be called first before trying to use the
 * chamelium.
 *
 * Needs to happen *after* igt_display_require() as otherwise the board will
 * get reset.
 *
 * If we fail to establish a connection with the chamelium, fail to find a
 * configured connector, etc. we fail the current test.
 *
 * Returns: A newly initialized chamelium struct, or NULL on error
 */
struct chamelium *chamelium_init(int drm_fd, igt_display_t *display)
{
	struct chamelium *chamelium = chamelium_init_rpc_only();
	bool mismatching_ports_found = false;

	if (chamelium == NULL)
		return NULL;

	/* A chamelium instance was set up previously, so clean it up before
	 * starting a new one
	 */
	if (cleanup_instance)
		chamelium_deinit(cleanup_instance);

	chamelium->drm_fd = dup(drm_fd);
	IGT_INIT_LIST_HEAD(&chamelium->edids);

	/*
	 * Reset the chamelium and do a disabling modeset for
	 * TypeC connector to come up nicely
	 */
	chamelium_reset(chamelium);
	igt_modeset_disable_all_outputs(display);

	if (!chamelium_read_port_mappings(chamelium, drm_fd))
		goto error;

	if (chamelium->port_count == 0) {
		igt_info("Chamelium configured without port mapping, "
			 "performing autodiscovery\n");

		if (!chamelium_autodiscover(chamelium))
			goto error;

		if (chamelium->port_count != 0)
			igt_info("\nConsider adding the following to your .igtrc:\n");
		for (int i = 0; i < chamelium->port_count; ++i) {
			igt_info("[Chamelium:%s]\n", chamelium->ports[i].name);
			igt_info("ChameliumPortID=%d\n\n", chamelium->ports[i].id);
		}
	}

	for (int i = 0; i < chamelium->port_count; i++) {
		bool type_mismatch = false;
		struct chamelium_port * port = &chamelium->ports[i];
		drmModeConnectorPtr connector =
			chamelium_port_get_connector(chamelium, port, false);

		igt_assert(connector != NULL);

		type_mismatch = port->type != connector->connector_type;

		if (type_mismatch)
			igt_info("Chamelium port %d is %s, but the DRM connector is %s\n",
				 port->id, kmstest_connector_type_str(port->type),
				 kmstest_connector_type_str(connector->connector_type));

		if (type_mismatch && !port->adapter_allowed)
			mismatching_ports_found = true;

		drmModeFreeConnector(connector);
	}

	cleanup_instance = chamelium;
	igt_install_exit_handler(chamelium_exit_handler);

	igt_abort_on_f(mismatching_ports_found,
		       "Chamelium port(s) with mismatching connector type on the "
		       "DRM side found - this will most likely cause test failures. "
		       "If you want to proceed with this this configuration, set the "
		       "port mapping manually in .igtrc with AdapterAllowed=1. See "
		       "docs/chamelium.txt for more information.\n");

	/* After a Chamelium init, all ports are now connected, and MST
	 * connectors are now known to the kernel. MST connectors would spawn
	 * new connectors that were previously unknown to the kernel. Refresh
	 * the outputs to grab all supported connectors.*/
	igt_display_reset_outputs(display);

	return chamelium;
error:
	close(chamelium->drm_fd);

	chamelium_deinit_rpc_only(chamelium);

	return NULL;
}

/**
 * chamelium_deinit:
 * @chamelium: The Chamelium instance to use
 *
 * 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(struct chamelium *chamelium)
{
	int i;
	struct chamelium_edid *pos, *tmp;

	/* We want to make sure we leave all of the ports plugged in, since
	 * testing setups requiring multiple monitors are probably using the
	 * chamelium to provide said monitors
	 */
	chamelium_reset(chamelium);
	for (i = 0; i < chamelium->port_count; i++)
		chamelium_plug(chamelium, &chamelium->ports[i]);

	igt_assert(chamelium->drm_fd != -1);
	for (i = 0; i < chamelium->port_count; i++)
		wait_for_connected_state(chamelium->drm_fd,
					 &chamelium->ports[i].connector_id, 1);

	/* Destroy any EDIDs we created to make sure we don't leak them */
	igt_list_for_each_entry_safe(pos, tmp, &chamelium->edids, link) {
		for (i = 0; i < CHAMELIUM_MAX_PORTS; i++) {
			if (pos->ids[i])
				chamelium_destroy_edid(chamelium, pos->ids[i]);
			free(pos->raw[i]);
		}
		free(pos->base);
		free(pos);
	}

	close(chamelium->drm_fd);

	xmlrpc_client_destroy(chamelium->client);

	for (i = 0; i < chamelium->port_count; i++)
		free(chamelium->ports[i].name);

	chamelium_deinit_rpc_only(chamelium);
}

bool chamelium_plug_all(struct chamelium *chamelium)
{
	size_t port_count;
	int port_ids[CHAMELIUM_MAX_PORTS];
	xmlrpc_value *v;

	port_count = chamelium_get_video_ports(chamelium, port_ids);
	if (port_count <= 0)
		return false;

	/*
	 * A temporary workaround for Chamelium V3: Currently, Cv3 doesn't allow
	 * all ports to be plugged in at the same time. Cv3 first port has an ID
	 * of 0 while Cv2 has first port ID as 1.
	 * TODO(markyacoub): Remove this workaround when V3 is fixed.
	 */
	if (port_ids[0] == 0) {
		igt_debug("This should be Cv3. Skipping plugging all ports\n");
		return true;
	}

	for (int i = 0; i < port_count; ++i) {
		v = __chamelium_rpc(chamelium, NULL, "Plug", "(i)", port_ids[i]);

		if (v != NULL)
			xmlrpc_DECREF(v);

		if (chamelium->env.fault_occurred) {
			igt_debug("Chamelium RPC call failed: %s\n",
			     chamelium->env.fault_string);

			return false;
		}
	}

	return true;
}

bool chamelium_wait_all_configured_ports_connected(struct chamelium *chamelium, int drm_fd)
{
	drmModeRes *res;
	drmModeConnector *connector;
	char **group_list;
	char *group;

	int connectors[CHAMELIUM_MAX_PORTS];
	int connectors_count = 0;

	res = drmModeGetResources(drm_fd);

	group_list = g_key_file_get_groups(igt_key_file, NULL);

	for (int i = 0; group_list[i] != NULL; i++) {
		char *map_name;
		group = group_list[i];

		if (!strstr(group, "Chamelium:"))
			continue;

		igt_assert(chamelium->port_count <= CHAMELIUM_MAX_PORTS);

		map_name = group + (sizeof("Chamelium:") - 1);

		for (int j = 0;
		     j < res->count_connectors;
		     j++) {
			char name[50];

			connector = drmModeGetConnectorCurrent(
			    drm_fd, res->connectors[j]);

			/* We have to generate the connector name on our own */
			snprintf(name, 50, "%s-%u",
				 kmstest_connector_type_str(connector->connector_type),
				 connector->connector_type_id);


			if (strcmp(name, map_name) == 0) {
				igt_assert(connectors_count < CHAMELIUM_MAX_PORTS);
				connectors[connectors_count++] = connector->connector_id;
				break;
			}

			drmModeFreeConnector(connector);
		}
	}

	drmModeFreeResources(res);

	if (connectors_count == 0) {
		igt_info("No chamelium port mappping, sleeping for %d seconds "
			 "for the hotplug to take effect\n",
			 CHAMELIUM_HOTPLUG_DETECTION_DELAY);
		sleep(CHAMELIUM_HOTPLUG_DETECTION_DELAY);
		return true;
	}

	return wait_for_connected_state(drm_fd, connectors, connectors_count);
}

igt_constructor {
	/* Frame dumps can be large, so we need to be able to handle very large
	 * responses
	 *
	 * Limit here is 15MB
	 */
	xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 15728640);
}
