/*
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 * Copyright © 2017 Intel Corporation
 *
 * 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.
 *
 * Displayport Compliance Testing Application
 *
 * This is the userspace component of the Displayport Compliance testing
 * software required for compliance testing of the I915 Display Port driver.
 * This must be running in order to successfully complete Display Port
 * compliance testing. This app and the kernel code that accompanies it has been
 * written to satify the requirements of the Displayport Link CTS 1.2 rev1.1
 * specification from VESA. Note that this application does not support eDP
 * compliance testing.
 *
 * Compliance Testing requires several components:
 *   - A kernel build that contains the patch set for DP compliance support
 *   - A Displayport Compliance Testing appliance such as Unigraf-DPR120
 *   - This user application
 *   - A windows host machine to run the DPR test software
 *   - Root access on the DUT due to the use of sysfs utility
 *
 * Test Setup:
 * It is strongly recommended that the windows host, test appliance and DUT
 * be freshly restarted before any testing begins to ensure that any previous
 * configurations and settings will not interfere with test process. Refer to
 * the test appliance documentation for setup, software installation and
 * operation specific to that device.
 *
 * The Linux DUT must be in text (console) mode and cannot have any other
 * display manager running. You must be logged in as root to run this user app.
 * Once the user application is up and running, waiting for test requests, the
 * software on the windows host can now be used to execute the compliance tests.
 *
 * This userspace application supports following tests from the DP CTS Spec
 * Rev 1.1:
 *   - Link Training Tests: Supports tests 4.3.1.1 to 4.3.2.3
 *   - EDID Tests: Supports EDID read (4.2.2.3),EDID Read failure and corruption
 *     detection tests (4.2.2.4, 4.2.2.5, 4.2.2.6)
 *   - Video Pattern generation tests: This supports only the 24 and 18bpp color
 *     ramp test pattern (4.3.3.1).
 *
 * Connections (required):
 *   - Test Appliance connected to the external Displayport connector on the DUT
 *   - Test Appliance Monitor Out connected to Displayport connector on the
 * monitor
 *   - Test appliance connected to the Windows Host via USB
 *
 * Debugfs Files:
 * The file root for all  the debugfs file:
 * /sys/kernel/debug/dri/0/
 *
 * The specific files are as follows:
 *
 * i915_dp_test_active
 * A simple flag that indicates whether or not compliance testing is currently
 * active in the kernel. This flag is polled by userspace and once set, invokes
 * the test handler in the user app. This flag is set by the test handler in the
 * kernel after reading the registers requested by the test appliance.
 *
 * i915_dp_test_data
 * Test data is used by the kernel to pass parameters to the user app. Eg: In
 * case of EDID tests, the data that is delivered to the userspace is the video
 * mode to be set for the test.
 * In case of video pattern test, the data that is delivered to the userspace is
 * the width and height of the test pattern and the bits per color value.
 *
 * i915_dp_test_type
 * The test type variable instructs the user app as to what the requested test
 * was from the sink device. These values defined at the top of the application's
 * main implementation file must be kept in sync with the values defined in the
 * kernel's drm_dp_helper.h file.
 * This app is based on some prior work submitted in April 2015 by Todd Previte
 * (<tprevite@gmail.com>)
 *
 *
 * This tool can be run as:
 * ./intel_dp_compliance  It will wait till you start compliance suite from
 * DPR 120.
 * ./intel_dp_compliance -h  This will open the help
 * ./intel_dp_compliance -i  This will provide information about current
 * connectors/CRTCs. This can be used for debugging purpose.
 *
 * Authors:
 *    Manasi Navare <manasi.d.navare@intel.com>
 *
 * Elements of the modeset code adapted from David Herrmann's
 * DRM modeset example
 *
 */
#include "igt.h"
#include <errno.h>
#include <getopt.h>
#include <math.h>
#include <stdint.h>
#include <stdbool.h>
#include <strings.h>
#include <poll.h>
#include <unistd.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <assert.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>

#include "igt_dp_compliance.h"

#include <stdlib.h>
#include <signal.h>

/* User Input definitions */
#define HELP_DESCRIPTION 1

/* Debugfs file definitions */
#define INTEL_DP_TEST_TYPE_FILE		"i915_dp_test_type"
#define INTEL_DP_TEST_ACTIVE_FILE	"i915_dp_test_active"
#define INTEL_DP_TEST_DATA_FILE		"i915_dp_test_data"

/* DRM definitions - must be kept in sync with the DRM header */
#define DP_TEST_LINK_TRAINING		(1 << 0)
#define DP_TEST_LINK_VIDEO_PATTERN	(1 << 1)
#define DP_TEST_LINK_EDID_READ		(1 << 2)
#define DP_TEST_LINK_PHY_TEST_PATTERN	(1 << 3) /* DPCD >= 1.1 */

#define DP_COMPLIANCE_TEST_TYPE_MASK	(DP_TEST_LINK_TRAINING      |	\
					 DP_TEST_LINK_VIDEO_PATTERN |	\
					 DP_TEST_LINK_EDID_READ     |	\
					 DP_TEST_LINK_PHY_TEST_PATTERN)

/* NOTE: These must be kept in sync with the definitions in intel_dp.c */
#define INTEL_DP_EDID_SHIFT_MASK	0
#define INTEL_DP_EDID_OK		(0 << INTEL_DP_EDID_SHIFT_MASK)
#define INTEL_DP_EDID_CORRUPT		(1 << INTEL_DP_EDID_SHIFT_MASK)
#define INTEL_DP_RESOLUTION_SHIFT_MASK	0
#define INTEL_DP_RESOLUTION_PREFERRED	(1 << INTEL_DP_RESOLUTION_SHIFT_MASK)
#define INTEL_DP_RESOLUTION_STANDARD	(2 << INTEL_DP_RESOLUTION_SHIFT_MASK)
#define INTEL_DP_RESOLUTION_FAILSAFE	(3 << INTEL_DP_RESOLUTION_SHIFT_MASK)
#define DP_COMPLIANCE_VIDEO_MODE_MASK	(INTEL_DP_RESOLUTION_PREFERRED	|\
					 INTEL_DP_RESOLUTION_STANDARD	|\
					 INTEL_DP_RESOLUTION_FAILSAFE)

/* Global file pointers for the sysfs files */
FILE *test_active_fp, *test_data_fp, *test_type_fp;

bool video_pattern_flag;

/* Video pattern test globals */
uint16_t hdisplay;
uint16_t vdisplay;
uint8_t bitdepth;

drmModeRes *resources;
int drm_fd, modes, gen;
uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
uint32_t depth = 24, stride, bpp;
int specified_mode_num = -1, specified_disp_id = -1;
int width, height;
uint32_t test_crtc;
uint32_t test_connector_id;
enum {
	INTEL_MODE_INVALID = -1,
	INTEL_MODE_NONE = 0,
	INTEL_MODE_PREFERRED,
	INTEL_MODE_STANDARD,
	INTEL_MODE_FAILSAFE,
	INTEL_MODE_VIDEO_PATTERN_TEST
} intel_display_mode;

struct test_video_pattern {
	uint16_t hdisplay;
	uint16_t vdisplay;
	uint8_t bitdepth;
	uint32_t fb;
	uint32_t size;
	struct igt_fb fb_pattern;
	drmModeModeInfo mode;
	uint32_t *pixmap;
};

struct connector {
	uint32_t id;
	int mode_valid;
	drmModeModeInfo mode, mode_standard, mode_preferred, mode_failsafe;
	drmModeConnector *connector;
	int crtc;
	/* Standard and preferred frame buffer*/
	uint32_t fb, fb_width, fb_height, fb_size;
	uint8_t *pixmap;
	struct igt_fb fb_video_pattern;
	/* Failsafe framebuffer - note this is a 16-bit buffer */
	uint32_t failsafe_fb, failsafe_width, failsafe_height;
	uint32_t failsafe_size;
	uint8_t *failsafe_pixmap;
	struct igt_fb fb_failsafe_pattern;
	struct test_video_pattern test_pattern;
};

static void clear_test_active(void)
{
	rewind(test_active_fp);
	fprintf(test_active_fp, "%d", 0);
	fflush(test_active_fp);
}

static FILE *fopenat(int dir, const char *name, const char *mode)
{
	int fd = openat(dir, name, O_RDWR);
	return fdopen(fd, mode);
}

static void setup_debugfs_files(void)
{
	int dir = igt_debugfs_dir(drm_fd);

	test_type_fp = fopenat(dir, INTEL_DP_TEST_TYPE_FILE, "r");
	igt_require(test_type_fp);

	test_data_fp = fopenat(dir, INTEL_DP_TEST_DATA_FILE, "r");
	igt_require(test_data_fp);

	test_active_fp = fopenat(dir, INTEL_DP_TEST_ACTIVE_FILE, "w+");
	igt_require(test_active_fp);

	close(dir);

	/* Reset the active flag for safety */
	clear_test_active();
}

static unsigned long get_test_type(void)
{
	unsigned long test_type;
	int ret;

	if (!test_type_fp)
		fprintf(stderr, "Invalid test_type file\n");
	rewind(test_type_fp);
	ret = fscanf(test_type_fp, "%lx", &test_type);
	if (ret < 1 || test_type <= 0) {
		igt_warn("test_type read failed - %lx\n", test_type);
		return 0;
	}

	return test_type;
}

static unsigned long get_test_edid_data(void)
{
	unsigned long test_data;
	int ret;

	if (!test_data_fp)
		fprintf(stderr, "Invalid test_data file\r\n");

	rewind(test_data_fp);
	ret = fscanf(test_data_fp, "%lx", &test_data);
	if (ret < 1 || test_data <= 0) {
		igt_warn("test_data read failed - %lx\r\n", test_data);
		return 0;
	}

	return test_data;
}

static void get_test_videopattern_data(void)
{
	int count = 0;
	uint16_t video_pattern_value[3];
	char video_pattern_attribute[15];
	int ret;

	if (!test_data_fp)
		fprintf(stderr, "Invalid test_data file\n");

	rewind(test_data_fp);
	while (!feof(test_data_fp) && count < 3) {
		ret = fscanf(test_data_fp, "%s %u\n", video_pattern_attribute,
		       (unsigned int *)&video_pattern_value[count++]);
		if (ret < 2) {
			igt_warn("test_data read failed\n");
			return;
		}
	}

	hdisplay = video_pattern_value[0];
	vdisplay = video_pattern_value[1];
	bitdepth = video_pattern_value[2];
	igt_info("Hdisplay = %d\n", hdisplay);
	igt_info("Vdisplay = %d\n", vdisplay);
	igt_info("BitDepth = %u\n", bitdepth);

}

static int process_test_request(int test_type)
{
	int mode;
	unsigned long test_data_edid;
	bool valid = false;
	switch (test_type) {
	case DP_TEST_LINK_VIDEO_PATTERN:
		video_pattern_flag = true;
		get_test_videopattern_data();
		mode = INTEL_MODE_VIDEO_PATTERN_TEST;
		valid = true;
		break;
	case DP_TEST_LINK_EDID_READ:
		test_data_edid = get_test_edid_data();
		mode = (test_data_edid & DP_COMPLIANCE_VIDEO_MODE_MASK) >>
			INTEL_DP_RESOLUTION_SHIFT_MASK;
		valid = true;
		break;
	default:
		/* Unknown test type */
		fprintf(stderr, "Invalid test request, ignored.\n");
		break;
	}

	if (valid)
		return update_display(mode, true);

	return -1;
}

static void dump_info(void)
{
	igt_dump_connectors_fd(drm_fd);
	igt_dump_crtcs_fd(drm_fd);
}

static int setup_framebuffers(struct connector *dp_conn)
{

	dp_conn->fb = igt_create_fb(drm_fd,
				    dp_conn->fb_width, dp_conn->fb_height,
				    DRM_FORMAT_XRGB8888,
				    DRM_FORMAT_MOD_LINEAR,
				    &dp_conn->fb_video_pattern);
	igt_assert(dp_conn->fb);

	/* Map the mapping of GEM object into the virtual address space */
	dp_conn->pixmap = gem_mmap__device_coherent(drm_fd,
					dp_conn->fb_video_pattern.gem_handle,
					0, dp_conn->fb_video_pattern.size,
					PROT_READ | PROT_WRITE);
	if (dp_conn->pixmap == NULL)
		return -1;

	gem_set_domain(drm_fd, dp_conn->fb_video_pattern.gem_handle,
		       I915_GEM_DOMAIN_GTT, I915_GEM_DOMAIN_GTT);
	dp_conn->fb_size = dp_conn->fb_video_pattern.size;

	/* After filling the device memory with 0s it needs to be unmapped */
	memset(dp_conn->pixmap, 0, dp_conn->fb_size);
	munmap(dp_conn->pixmap, dp_conn->fb_size);

	return 0;
}

static int setup_failsafe_framebuffer(struct connector *dp_conn)
{

	dp_conn->failsafe_fb = igt_create_fb(drm_fd,
					     dp_conn->failsafe_width,
					     dp_conn->failsafe_height,
					     DRM_FORMAT_XRGB8888,
					     DRM_FORMAT_MOD_LINEAR,
					     &dp_conn->fb_failsafe_pattern);
	igt_assert(dp_conn->failsafe_fb);

	/* Map the mapping of GEM object into the virtual address space */
	dp_conn->failsafe_pixmap = gem_mmap__device_coherent(drm_fd,
						 dp_conn->fb_failsafe_pattern.gem_handle,
						 0, dp_conn->fb_failsafe_pattern.size,
						 PROT_READ | PROT_WRITE);
	if (dp_conn->failsafe_pixmap == NULL)
		return -1;

	gem_set_domain(drm_fd, dp_conn->fb_failsafe_pattern.gem_handle,
		       I915_GEM_DOMAIN_GTT, I915_GEM_DOMAIN_GTT);
	dp_conn->failsafe_size = dp_conn->fb_failsafe_pattern.size;

	/* After filling the device framebuffer the mapped memory needs to be freed */
	memset(dp_conn->failsafe_pixmap, 0, dp_conn->failsafe_size);
	munmap(dp_conn->failsafe_pixmap, dp_conn->failsafe_size);

	return 0;

}

static int setup_video_pattern_framebuffer(struct connector *dp_conn)
{
	uint32_t  video_width, video_height;

	video_width = dp_conn->test_pattern.hdisplay;
	video_height = dp_conn->test_pattern.vdisplay;
	/*
	 * Display WA1172: Gen10 To pass the color data unaffected set either
	 * per-pixel alpha or Plane alpha to 0xff. Use ARGB8888 and set alpha to 0xff.
	 */
	dp_conn->test_pattern.fb = igt_create_fb(drm_fd,
						 video_width, video_height,
						 gen == 10 ? DRM_FORMAT_ARGB8888 : DRM_FORMAT_XRGB8888,
						 DRM_FORMAT_MOD_LINEAR,
						 &dp_conn->test_pattern.fb_pattern);
	igt_assert(dp_conn->test_pattern.fb);

	/* Map the mapping of GEM object into the virtual address space */
	dp_conn->test_pattern.pixmap = gem_mmap__device_coherent(drm_fd,
						     dp_conn->test_pattern.fb_pattern.gem_handle,
						     0, dp_conn->test_pattern.fb_pattern.size,
						     PROT_READ | PROT_WRITE);
	if (dp_conn->test_pattern.pixmap == NULL)
		return -1;

	gem_set_domain(drm_fd, dp_conn->test_pattern.fb_pattern.gem_handle,
		       I915_GEM_DOMAIN_GTT, I915_GEM_DOMAIN_GTT);

	dp_conn->test_pattern.size = dp_conn->test_pattern.fb_pattern.size;

	memset(dp_conn->test_pattern.pixmap, 0, dp_conn->test_pattern.size);
	return 0;

}

static int set_test_mode(struct connector *dp_conn)
{
	int ret = 0;
	int i;
	bool found_std = false, found_fs = false;
	uint32_t alpha;
	drmModeConnector *c = dp_conn->connector;
	uint32_t *pixmap;

	/* Ignore any disconnected devices */
	if (c->connection != DRM_MODE_CONNECTED) {
		igt_warn("Connector %u disconnected\n", c->connector_id);
		return -ENOENT;
	}
	igt_info("Connector setup:\n");
	/* Setup preferred mode - should be mode[0] in the list */
	dp_conn->mode_preferred = c->modes[0];
	dp_conn->fb_width = c->modes[0].hdisplay;
	dp_conn->fb_height = c->modes[0].vdisplay;

	dp_conn->test_pattern.mode = c->modes[0];
	dp_conn->test_pattern.mode.hdisplay = c->modes[0].hdisplay;
	dp_conn->test_pattern.mode.vdisplay = c->modes[0].vdisplay;

	igt_info("Preferred mode (mode 0) for connector %u is %ux%u\n",
		 dp_conn->id, c->modes[0].hdisplay, c->modes[0].vdisplay);
	fflush(stdin);

	for (i = 1; i < c->count_modes; i++) {
		/* Standard mode is 800x600@60 */
		if (c->modes[i].hdisplay == 800 &&
		    c->modes[i].vdisplay == 600 &&
		    c->modes[i].vrefresh == 60 &&
		    found_std == false) {
			dp_conn->mode_standard = c->modes[i];
			igt_info("Standard mode (%d) for connector %u is %ux%u\n",
				 i,
				 c->connector_id,
				 c->modes[i].hdisplay,
				 c->modes[i].vdisplay);
			found_std = true;
		}
		/* Failsafe mode is 640x480@60 */
		if (c->modes[i].hdisplay == 640 &&
		    c->modes[i].vdisplay == 480 &&
		    c->modes[i].vrefresh == 60 &&
		    found_fs == false) {
			dp_conn->mode_failsafe = c->modes[i];
			dp_conn->failsafe_width = c->modes[i].hdisplay;
			dp_conn->failsafe_height = c->modes[i].vdisplay;
			igt_info("Failsafe mode (%d) for connector %u is %ux%u\n",
				 i,
				 c->connector_id,
				 c->modes[i].hdisplay,
				 c->modes[i].vdisplay);
			found_fs = true;
		}
	}

	ret = setup_framebuffers(dp_conn);
	if (ret) {
		igt_warn("Creating framebuffer for connector %u failed (%d)\n",
			 c->connector_id, ret);
		return ret;
	}

	if (found_fs) {
		ret = setup_failsafe_framebuffer(dp_conn);
		if (ret) {
			igt_warn("Creating failsafe framebuffer for connector %u failed (%d)\n",
				 c->connector_id, ret);
			return ret;
		}
	}

	if (video_pattern_flag) {
		dp_conn->test_pattern.hdisplay = hdisplay;
		dp_conn->test_pattern.vdisplay = vdisplay;
		dp_conn->test_pattern.bitdepth = bitdepth;
		alpha = gen == 10 ? 0xff : 0;

		ret = setup_video_pattern_framebuffer(dp_conn);
		if (ret) {
			igt_warn("Creating framebuffer for connector %u failed (%d)\n",
				 c->connector_id, ret);
			return ret;
		}

		pixmap = dp_conn->test_pattern.pixmap;

		ret = igt_fill_cts_color_ramp_framebuffer(pixmap,
				dp_conn->test_pattern.hdisplay,
				dp_conn->test_pattern.vdisplay,
				dp_conn->test_pattern.bitdepth,
				alpha);
		if (ret) {
			igt_warn("Filling framebuffer for connector %u failed (%d)\n",
				 c->connector_id, ret);
			return ret;
		}
		/* unmapping the buffer previously mapped during setup */
		munmap(dp_conn->test_pattern.pixmap,
				dp_conn->test_pattern.size);
	}

	return ret;
}

static int set_video(int mode, struct connector *test_connector)
{
	drmModeModeInfo *requested_mode;
	uint32_t required_fb_id;
	struct igt_fb required_fb;
	int ret = 0;

	switch (mode) {
	case INTEL_MODE_NONE:
		igt_info("NONE\n");
		ret = drmModeSetCrtc(drm_fd, test_connector->crtc,
				     -1, 0, 0, NULL, 0, NULL);
		goto out;
	case INTEL_MODE_PREFERRED:
		igt_info("PREFERRED\n");
		requested_mode =  &test_connector->mode_preferred;
		required_fb_id = test_connector->fb;
		required_fb = test_connector->fb_video_pattern;
		break;
	case INTEL_MODE_STANDARD:
		igt_info("STANDARD\n");
		requested_mode =  &test_connector->mode_standard;
		required_fb_id = test_connector->fb;
		required_fb = test_connector->fb_video_pattern;
		break;
	case INTEL_MODE_FAILSAFE:
		igt_info("FAILSAFE\n");
		requested_mode =  &test_connector->mode_failsafe;
		required_fb_id = test_connector->failsafe_fb;
		required_fb = test_connector->fb_failsafe_pattern;
		break;
	case INTEL_MODE_VIDEO_PATTERN_TEST:
		igt_info("VIDEO PATTERN TEST\n");
		requested_mode = &test_connector->test_pattern.mode;
		required_fb_id = test_connector->test_pattern.fb;
		required_fb = test_connector->test_pattern.fb_pattern;
		break;
	case INTEL_MODE_INVALID:
	default:
		igt_warn("INVALID! (%08x) Mode set aborted!\n", mode);
		return -1;
	}

	igt_info("CRTC(%u):", test_connector->crtc);
	kmstest_dump_mode(requested_mode);
	ret = drmModeSetCrtc(drm_fd, test_connector->crtc, required_fb_id, 0, 0,
			     &test_connector->id, 1, requested_mode);
	if (ret) {
		igt_warn("Failed to set mode (%dx%d@%dHz): %s\n",
			 requested_mode->hdisplay, requested_mode->vdisplay,
			 requested_mode->vrefresh, strerror(errno));
		igt_remove_fb(drm_fd, &required_fb);

	}
	/* Keep the pattern on output lines for 1 sec for DPR-120 to detect it */
	sleep(1);

out:
	if (ret) {
		igt_warn("Failed to set CRTC for connector %u\n",
			 test_connector->id);
	}

	return ret;
}

static int
set_default_mode(struct connector *c, bool set_mode)
{
	unsigned int fb_id = 0;
	struct igt_fb fb_info;
	int ret = 0;

	if (!set_mode) {
		ret = drmModeSetCrtc(drm_fd, c->crtc, 0, 0, 0,
				     NULL, 0, NULL);
		if (ret)
			igt_warn("Failed to unset mode");
		return ret;
	}

	c->mode = c->connector->modes[0];

	width = c->mode.hdisplay;
	height = c->mode.vdisplay;

	fb_id = igt_create_pattern_fb(drm_fd, width, height,
				      DRM_FORMAT_XRGB8888,
				      modifier, &fb_info);

	igt_info("CRTC(%u):[%d]", c->crtc, 0);
	kmstest_dump_mode(&c->mode);
	drmModeSetCrtc(drm_fd, c->crtc, -1, 0, 0, NULL, 0, NULL);
	ret = drmModeSetCrtc(drm_fd, c->crtc, fb_id, 0, 0,
			     &c->id, 1, &c->mode);
	if (ret) {
		igt_warn("Failed to set mode (%dx%d@%dHz): %s\n",
			 width, height, c->mode.vrefresh, strerror(errno));
		igt_remove_fb(drm_fd, &fb_info);

	}

	return ret;
}

static uint32_t find_crtc_for_connector(drmModeConnector *c)
{
	drmModeEncoder *e;
	uint32_t possible_crtcs;
	int i, j;

	for (i = 0; i < c->count_encoders; i++) {
		e = drmModeGetEncoder(drm_fd, c->encoders[i]);
		possible_crtcs = e->possible_crtcs;
		drmModeFreeEncoder(e);

		for (j = 0; possible_crtcs >> j; j++)
			if (possible_crtcs & (1 << j))
				return resources->crtcs[j];
	}
	return 0;
}

/*
 * Re-probe connectors and do a modeset based on test request or
 * in case of a hotplug uevent.
 *
 * @mode: Video mode requested by the test
 * @is_compliance_test: 1: If it is a compliance test
 *                      0: If it is a hotplug event
 *
 * Returns:
 * 0: On Success
 * -1: On failure
 */
int update_display(int mode, bool is_compliance_test)
{
	struct connector *connectors, *conn;
	int cnt, ret = 0;
	bool set_mode;

	resources = drmModeGetResources(drm_fd);
	if (!resources) {
		igt_warn("drmModeGetResources failed: %s\n", strerror(errno));
		return -1;
	}

	connectors = calloc(resources->count_connectors,
			    sizeof(struct connector));
	if (!connectors)
		return -1;

	/* Find any connected displays */
	for (cnt = 0; cnt < resources->count_connectors; cnt++) {
		drmModeConnector *c;

		conn = &connectors[cnt];
		conn->id = resources->connectors[cnt];
		c = drmModeGetConnector(drm_fd, conn->id);
		if (c->connector_type == DRM_MODE_CONNECTOR_DisplayPort &&
		    c->connection == DRM_MODE_CONNECTED) {
			test_connector_id = c->connector_id;
			conn->connector = c;
			conn->crtc = find_crtc_for_connector(c);
			test_crtc = conn->crtc;
			set_mode = true;
			break;
		} else if (c->connector_id == test_connector_id &&
			   c->connection == DRM_MODE_DISCONNECTED) {
			conn->connector = c;
			conn->crtc = test_crtc;
			set_mode = false;
			break;
		}
	}

	if (cnt == resources->count_connectors) {
		ret = -1;
		goto err;
	}

	if (is_compliance_test) {
		set_test_mode(conn);
		ret = set_video(INTEL_MODE_NONE, conn);
		ret = set_video(mode, conn);

	} else
		ret = set_default_mode(conn, set_mode);

 err:
	drmModeFreeConnector(conn->connector);
	/* Error condition if no connected displays */
	free(connectors);
	drmModeFreeResources(resources);
	return ret;
}

static const char optstr[] = "hi";

static void __attribute__((noreturn)) usage(char *name, char opt)
{
	igt_info("usage: %s [-hi]\n", name);
	igt_info("\t-i\tdump info\n");
	igt_info("\tDefault is to respond to DPR-120 tests\n");
	exit((opt != 'h') ? -1 : 0);
}

static void cleanup_debugfs(void)
{
	fclose(test_active_fp);
	fclose(test_data_fp);
	fclose(test_type_fp);
}

static void __attribute__((noreturn)) cleanup_and_exit(int ret)
{
	cleanup_debugfs();
	close(drm_fd);
	igt_info("Compliance testing application exiting\n");
	exit(ret);
}

static void cleanup_test(void)
{
	video_pattern_flag = false;
	hdisplay = 0;
	vdisplay = 0;
	bitdepth = 0;
}

static void read_test_request(void)
{
	unsigned long test_type;

	test_type = get_test_type();
	process_test_request(test_type);
	cleanup_test();
	clear_test_active();
}

static gboolean test_handler(GIOChannel *source, GIOCondition condition,
			     gpointer data)
{
	unsigned long test_active;
	int ret;

	rewind(test_active_fp);

	ret = fscanf(test_active_fp, "%lx", &test_active);
	if (ret < 1)
		return FALSE;

	if (test_active)
		read_test_request();
	return TRUE;
}

static gboolean input_event(GIOChannel *source, GIOCondition condition,
				gpointer data)
{
	gchar buf[2];
	gsize count;

	count = read(g_io_channel_unix_get_fd(source), buf, sizeof(buf));
	if (buf[0] == 'q' && (count == 1 || buf[1] == '\n')) {
		cleanup_and_exit(0);
	}

	return TRUE;
}

int main(int argc, char **argv)
{
	int c;
	int ret = 0;
	GIOChannel *stdinchannel, *testactive_channel;
	GMainLoop *mainloop;
	bool opt_dump_info = false;
	struct option long_opts[] = {
		{"help-description", 0, 0, HELP_DESCRIPTION},
		{"help", 0, 0, 'h'},
	};

	enter_exec_path(argv);

	while ((c = getopt_long(argc, argv, optstr, long_opts, NULL)) != -1) {
		switch (c) {
		case 'i':
			opt_dump_info = true;
			break;
		case HELP_DESCRIPTION:
			igt_info("DP Compliance Test Suite using DPR-120\n");
			igt_info("EDID tests\n");
			igt_info("Video Pattern Generation tests\n");
			exit(0);
			break;
		default:
			/* fall through */
		case 'h':
			usage(argv[0], c);
			break;
		}
	}

	set_termio_mode();

	drm_fd = drm_open_driver(DRIVER_ANY);
	gen = intel_gen(intel_get_drm_devid(drm_fd));

	kmstest_set_vt_graphics_mode();
	setup_debugfs_files();
	cleanup_test();

	if (opt_dump_info) {
		dump_info();
		goto out_close;
	}

	/* Get the DP connector ID and CRTC */
	if (update_display(0, false)) {
		igt_warn("Failed to set default mode\n");
		ret = -1;
		goto out_close;
	}

	mainloop = g_main_loop_new(NULL, FALSE);
	if (!mainloop) {
		igt_warn("Failed to create GMainLoop\n");
		ret = -1;
		goto out_close;
	}

	if (!igt_dp_compliance_setup_hotplug()) {
		igt_warn("Failed to initialize hotplug support\n");
		goto out_mainloop;
	}

	testactive_channel = g_io_channel_unix_new(fileno(test_active_fp));
	if (!testactive_channel) {
		igt_warn("Failed to create test_active GIOChannel\n");
		goto out_close;
	}

	ret = g_io_add_watch(testactive_channel, G_IO_IN | G_IO_ERR,
			     test_handler, NULL);
	if (ret < 0) {
		igt_warn("Failed to add watch on udev GIOChannel\n");
		goto out_close;
	}

	stdinchannel = g_io_channel_unix_new(0);
	if (!stdinchannel) {
		igt_warn("Failed to create stdin GIOChannel\n");
		goto out_hotplug;
	}

	ret = g_io_add_watch(stdinchannel, G_IO_IN | G_IO_ERR, input_event,
			     NULL);
	if (ret < 0) {
		igt_warn("Failed to add watch on stdin GIOChannel\n");
		goto out_stdio;
	}

	ret = 0;

	igt_info("*************DP Compliance Testing using DPR-120*************\n");
	igt_info("Waiting for test request......\n");

	g_main_loop_run(mainloop);

out_stdio:
	g_io_channel_shutdown(stdinchannel, TRUE, NULL);
out_hotplug:
	igt_dp_compliance_cleanup_hotplug();
out_mainloop:
	g_main_loop_unref(mainloop);
out_close:
	cleanup_debugfs();
	close(drm_fd);

	igt_assert_eq(ret, 0);

	igt_info("Compliance testing application exiting\n");
	igt_exit();
}
