testdisplay.c 16 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
/*
 * Copyright 2010 Intel Corporation
 *   Jesse Barnes <jesse.barnes@intel.com>
 *
 * 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 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.
 */

/*
 * This program is intended for testing of display functionality.  It should
 * allow for testing of
 *   - hotplug
 *   - mode setting
 *   - clone & twin modes
 *   - panel fitting
 *   - test patterns & pixel generators
 * Additional programs can test the detected outputs against VBT provided
 * device lists (both docked & undocked).
 *
 * TODO:
 * - pixel generator in transcoder
 * - test pattern reg in pipe
 * - test patterns on outputs (e.g. TV)
 * - handle hotplug (leaks crtcs, can't handle clones)
 * - allow mode force
 * - expose output specific controls
 *  - e.g. DDC-CI brightness
 *  - HDMI controls
 *  - panel brightness
 *  - DP commands (e.g. poweroff)
 * - verify outputs against VBT/physical connectors
 */
48
#ifdef HAVE_CONFIG_H
49
#include "config.h"
50
#endif
51 52 53 54 55 56 57 58 59

#include <assert.h>
#include <cairo.h>
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/time.h>
Chris Wilson's avatar
Chris Wilson committed
60 61
#include <sys/mman.h>
#include <sys/ioctl.h>
62 63

#include "i915_drm.h"
64
#include "drmtest.h"
65
#include "testdisplay.h"
66

67 68 69
#include <stdlib.h>
#include <signal.h>

70
drmModeRes *resources;
71
int drm_fd, modes;
72
int dump_info = 0, test_all_modes =0, test_preferred_mode = 0, force_mode = 0,
73
	test_plane, enable_tiling;
74
int sleep_between_modes = 5;
75
uint32_t depth = 24, stride, bpp;
76
int qr_code = 0;
77
int specified_mode_num = -1, specified_disp_id = -1;
78

79
drmModeModeInfo force_timing;
80

81
int crtc_x, crtc_y, crtc_w, crtc_h, width, height;
82 83 84 85
unsigned int plane_fb_id;
unsigned int plane_crtc_id;
unsigned int plane_id;
int plane_width, plane_height;
86 87
static const uint32_t SPRITE_COLOR_KEY = 0x00aaaaaa;
uint32_t *fb_ptr;
88

89 90 91 92 93 94 95 96 97 98 99 100 101 102
/*
 * Mode setting with the kernel interfaces is a bit of a chore.
 * First you have to find the connector in question and make sure the
 * requested mode is available.
 * Then you need to find the encoder attached to that connector so you
 * can bind it with a free crtc.
 */
struct connector {
	uint32_t id;
	int mode_valid;
	drmModeModeInfo mode;
	drmModeEncoder *encoder;
	drmModeConnector *connector;
	int crtc;
103
	int crtc_idx;
104
	int pipe;
105 106
};

107
static void dump_connectors_fd(int drmfd)
108 109 110
{
	int i, j;

111 112 113 114 115 116 117 118
	drmModeRes *mode_resources = drmModeGetResources(drmfd);

	if (!mode_resources) {
		fprintf(stderr, "drmModeGetResources failed: %s\n",
			strerror(errno));
		return;
	}

119 120
	printf("Connectors:\n");
	printf("id\tencoder\tstatus\t\ttype\tsize (mm)\tmodes\n");
121
	for (i = 0; i < mode_resources->count_connectors; i++) {
122 123
		drmModeConnector *connector;

124
		connector = drmModeGetConnector(drmfd, mode_resources->connectors[i]);
125 126
		if (!connector) {
			fprintf(stderr, "could not get connector %i: %s\n",
127
				mode_resources->connectors[i], strerror(errno));
128 129 130 131 132 133
			continue;
		}

		printf("%d\t%d\t%s\t%s\t%dx%d\t\t%d\n",
		       connector->connector_id,
		       connector->encoder_id,
134 135
		       kmstest_connector_status_str(connector->connection),
		       kmstest_connector_type_str(connector->connector_type),
136 137 138 139 140 141 142 143
		       connector->mmWidth, connector->mmHeight,
		       connector->count_modes);

		if (!connector->count_modes)
			continue;

		printf("  modes:\n");
		printf("  name refresh (Hz) hdisp hss hse htot vdisp "
144
		       "vss vse vtot flags type clock\n");
145 146
		for (j = 0; j < connector->count_modes; j++){
			fprintf(stdout, "[%d]", j );
Daniel Vetter's avatar
Daniel Vetter committed
147
			kmstest_dump_mode(&connector->modes[j]);
148
		}
149 150 151 152

		drmModeFreeConnector(connector);
	}
	printf("\n");
153 154

	drmModeFreeResources(mode_resources);
155 156
}

157
static void dump_crtcs_fd(int drmfd)
158 159
{
	int i;
160
	drmModeRes *mode_resources = drmModeGetResources(drmfd);
161 162 163

	printf("CRTCs:\n");
	printf("id\tfb\tpos\tsize\n");
164
	for (i = 0; i < mode_resources->count_crtcs; i++) {
165 166
		drmModeCrtc *crtc;

167
		crtc = drmModeGetCrtc(drmfd, mode_resources->crtcs[i]);
168 169
		if (!crtc) {
			fprintf(stderr, "could not get crtc %i: %s\n",
170
				mode_resources->crtcs[i], strerror(errno));
171 172 173 174 175 176 177
			continue;
		}
		printf("%d\t%d\t(%d,%d)\t(%dx%d)\n",
		       crtc->crtc_id,
		       crtc->buffer_id,
		       crtc->x, crtc->y,
		       crtc->width, crtc->height);
Daniel Vetter's avatar
Daniel Vetter committed
178
		kmstest_dump_mode(&crtc->mode);
179 180 181 182

		drmModeFreeCrtc(crtc);
	}
	printf("\n");
183 184

	drmModeFreeResources(mode_resources);
185 186
}

187 188 189
static void connector_find_preferred_mode(uint32_t connector_id,
					  unsigned long crtc_idx_mask,
					  int mode_num, struct connector *c)
190
{
191
	struct kmstest_connector_config config;
192

193 194
	if (kmstest_get_connector_config(drm_fd, connector_id, crtc_idx_mask,
					 &config) < 0) {
195 196 197 198
		c->mode_valid = 0;
		return;
	}

199 200 201 202 203 204 205 206 207 208 209
	c->connector = config.connector;
	c->encoder = config.encoder;
	c->crtc = config.crtc->crtc_id;
	c->crtc_idx = config.crtc_idx;
	c->pipe = config.pipe;

	if (mode_num != -1) {
		assert(mode_num < config.connector->count_modes);
		c->mode = config.connector->modes[mode_num];
	} else {
		c->mode = config.default_mode;
210
	}
211
	c->mode_valid = 1;
212 213
}

214
static void
215
paint_color_key(struct kmstest_fb *fb_info)
216 217 218 219 220 221 222
{
	int i, j;

	for (i = crtc_y; i < crtc_y + crtc_h; i++)
		for (j = crtc_x; j < crtc_x + crtc_w; j++) {
			uint32_t offset;

223
			offset = (i * fb_info->stride / 4) + j;
224 225 226 227
			fb_ptr[offset] = SPRITE_COLOR_KEY;
		}
}

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
static void paint_image(cairo_t *cr, const char *file)
{
	int img_x, img_y, img_w, img_h, img_w_o, img_h_o;
	double img_w_scale, img_h_scale;

	cairo_surface_t *image;

	img_y = height * (0.10 );
	img_h = height * 0.08 * 4;
	img_w = img_h;

	img_x = (width / 2) - (img_w / 2);

	image = cairo_image_surface_create_from_png(file);

	img_w_o = cairo_image_surface_get_width(image);
	img_h_o = cairo_image_surface_get_height(image);

	cairo_translate(cr, img_x, img_y);

	img_w_scale = (double)img_w / (double)img_w_o;
	img_h_scale = (double)img_h / (double)img_h_o;
	cairo_scale(cr, img_w_scale, img_h_scale);

	cairo_set_source_surface(cr, image, 0, 0);
	cairo_scale(cr, 1, 1);

	cairo_paint(cr);
	cairo_surface_destroy(image);
}

Imre Deak's avatar
Imre Deak committed
259
static void paint_output_info(struct connector *c, struct kmstest_fb *fb)
260
{
Imre Deak's avatar
Imre Deak committed
261 262 263
	cairo_t *cr = kmstest_get_cairo_ctx(drm_fd, fb);
	int l_width = fb->width;
	int l_height = fb->height;
Imre Deak's avatar
Imre Deak committed
264 265 266 267
	double str_width;
	double x, y, top_y;
	double max_width;
	int i;
268

Imre Deak's avatar
Imre Deak committed
269 270
	kmstest_paint_test_pattern(cr, l_width, l_height);

271 272 273
	cairo_select_font_face(cr, "Helvetica",
			       CAIRO_FONT_SLANT_NORMAL,
			       CAIRO_FONT_WEIGHT_NORMAL);
Imre Deak's avatar
Imre Deak committed
274
	cairo_move_to(cr, l_width / 2, l_height / 2);
275

Imre Deak's avatar
Imre Deak committed
276
	/* Print connector and mode name */
277
	cairo_set_font_size(cr, 48);
Imre Deak's avatar
Imre Deak committed
278 279 280
	kmstest_cairo_printf_line(cr, align_hcenter, 10, "%s",
		 kmstest_connector_type_str(c->connector->connector_type));

281
	cairo_set_font_size(cr, 36);
Imre Deak's avatar
Imre Deak committed
282 283 284 285 286
	str_width = kmstest_cairo_printf_line(cr, align_hcenter, 10,
		"%s @ %dHz on %s encoder", c->mode.name, c->mode.vrefresh,
		kmstest_encoder_type_str(c->encoder->encoder_type));

	cairo_rel_move_to(cr, -str_width / 2, 0);
287 288 289

	/* List available modes */
	cairo_set_font_size(cr, 18);
Imre Deak's avatar
Imre Deak committed
290 291 292 293
	str_width = kmstest_cairo_printf_line(cr, align_left, 10,
					      "Available modes:");
	cairo_rel_move_to(cr, str_width, 0);
	cairo_get_current_point(cr, &x, &top_y);
294

Imre Deak's avatar
Imre Deak committed
295
	max_width = 0;
296
	for (i = 0; i < c->connector->count_modes; i++) {
Imre Deak's avatar
Imre Deak committed
297 298 299 300 301
		cairo_get_current_point(cr, &x, &y);
		if (y >= l_height) {
			x += max_width + 10;
			max_width = 0;
			cairo_move_to(cr, x, top_y);
302
		}
Imre Deak's avatar
Imre Deak committed
303 304 305 306 307
		str_width = kmstest_cairo_printf_line(cr, align_right, 10,
			"%s @ %dHz", c->connector->modes[i].name,
			 c->connector->modes[i].vrefresh);
		if (str_width > max_width)
			max_width = str_width;
308
	}
309 310 311

	if (qr_code)
		paint_image(cr, "./pass.png");
Imre Deak's avatar
Imre Deak committed
312 313

	assert(!cairo_status(cr));
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
}

static void sighandler(int signo)
{
	return;
}

static void set_single(void)
{
	int sigs[] = { SIGUSR1 };
	struct sigaction sa;
	sa.sa_handler = sighandler;

	sigemptyset(&sa.sa_mask);

	if (sigaction(sigs[0], &sa, NULL) == -1)
		perror("Could not set signal handler");
331 332 333 334 335
}

static void
set_mode(struct connector *c)
{
Daniel Vetter's avatar
Daniel Vetter committed
336
	unsigned int fb_id = 0;
337
	int j, test_mode_num;
338 339 340 341 342 343 344

	if (depth <= 8)
		bpp = 8;
	else if (depth > 8 && depth <= 16)
		bpp = 16;
	else if (depth > 16 && depth <= 32)
		bpp = 32;
345

Chris Wilson's avatar
Chris Wilson committed
346 347
	test_mode_num = 1;
	if (force_mode){
348 349
		memcpy( &c->mode, &force_timing, sizeof(force_timing));
		c->mode.vrefresh =(force_timing.clock*1e3)/(force_timing.htotal*force_timing.vtotal);
Chris Wilson's avatar
Chris Wilson committed
350
		c->mode_valid = 1;
351
		sprintf(c->mode.name, "%dx%d", force_timing.hdisplay, force_timing.vdisplay);
Chris Wilson's avatar
Chris Wilson committed
352
	} else if (test_all_modes)
353 354 355
		test_mode_num = c->connector->count_modes;

	for (j = 0; j < test_mode_num; j++) {
Daniel Vetter's avatar
Daniel Vetter committed
356
		struct kmstest_fb fb_info;
Chris Wilson's avatar
Chris Wilson committed
357

358 359
		if (test_all_modes)
			c->mode = c->connector->modes[j];
360

Chris Wilson's avatar
Chris Wilson committed
361 362
		if (!c->mode_valid)
			continue;
363

Chris Wilson's avatar
Chris Wilson committed
364 365 366
		width = c->mode.hdisplay;
		height = c->mode.vdisplay;

Daniel Vetter's avatar
Daniel Vetter committed
367
		fb_id = kmstest_create_fb(drm_fd, width, height, bpp, depth,
Imre Deak's avatar
Imre Deak committed
368 369
					  enable_tiling, &fb_info);
		paint_output_info(c, &fb_info);
370

Daniel Vetter's avatar
Daniel Vetter committed
371 372 373
		fb_ptr = gem_mmap(drm_fd, fb_info.gem_handle,
				  fb_info.size, PROT_READ | PROT_WRITE);
		assert(fb_ptr);
374
		paint_color_key(&fb_info);
375

Daniel Vetter's avatar
Daniel Vetter committed
376
		gem_close(drm_fd, fb_info.gem_handle);
377

378
		fprintf(stdout, "CRTS(%u):[%d]",c->crtc, j);
Daniel Vetter's avatar
Daniel Vetter committed
379
		kmstest_dump_mode(&c->mode);
380
		if (drmModeSetCrtc(drm_fd, c->crtc, fb_id, 0, 0,
Chris Wilson's avatar
Chris Wilson committed
381 382 383 384 385
				   &c->id, 1, &c->mode)) {
			fprintf(stderr, "failed to set mode (%dx%d@%dHz): %s\n",
				width, height, c->mode.vrefresh,
				strerror(errno));
			continue;
386
		}
387

388
		if (sleep_between_modes && test_all_modes && !qr_code)
Hai Lan's avatar
Hai Lan committed
389
			sleep(sleep_between_modes);
390

391 392 393 394 395
		if (qr_code){
			set_single();
			pause();
		}

396 397
	}

398
	if(test_all_modes){
399 400
		drmModeRmFB(drm_fd,fb_id);
		drmModeSetCrtc(drm_fd, c->crtc, fb_id, 0, 0,  &c->id, 1, 0);
401
	}
Chris Wilson's avatar
Chris Wilson committed
402

403 404 405 406 407 408 409 410 411 412 413 414 415
	drmModeFreeEncoder(c->encoder);
	drmModeFreeConnector(c->connector);
}

/*
 * Re-probe outputs and light up as many as possible.
 *
 * On Intel, we have two CRTCs that we can drive independently with
 * different timings and scanout buffers.
 *
 * Each connector has a corresponding encoder, except in the SDVO case
 * where an encoder may have multiple connectors.
 */
416
int update_display(void)
417 418 419 420
{
	struct connector *connectors;
	int c;

421
	resources = drmModeGetResources(drm_fd);
422 423 424
	if (!resources) {
		fprintf(stderr, "drmModeGetResources failed: %s\n",
			strerror(errno));
425
		return 0;
426 427 428 429 430
	}

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

433
	if (dump_info) {
434 435
		dump_connectors_fd(drm_fd);
		dump_crtcs_fd(drm_fd);
436
	}
437

438
	if (test_preferred_mode || test_all_modes || force_mode || specified_disp_id != -1) {
439 440
		unsigned long crtc_idx_mask = -1UL;

441 442
		/* Find any connected displays */
		for (c = 0; c < resources->count_connectors; c++) {
443 444 445 446 447 448 449 450 451 452 453 454
			struct connector *connector = &connectors[c];

			connector->id = resources->connectors[c];
			if (specified_disp_id != -1 &&
			    connector->id != specified_disp_id)
				continue;

			connector_find_preferred_mode(connector->id,
						      crtc_idx_mask,
						      specified_mode_num,
						      connector);
			if (!connector->mode_valid)
455 456
				continue;

457 458 459 460 461 462
			set_mode(connector);

			if (test_preferred_mode || force_mode ||
			    specified_mode_num != -1)
				crtc_idx_mask &= ~(1 << connector->crtc_idx);

463
		}
464 465
	}
	drmModeFreeResources(resources);
466
	return 1;
467 468
}

469
static char optstr[] = "hiaf:s:d:p:mrto:";
470

471
static void __attribute__((noreturn)) usage(char *name)
472
{
473
	fprintf(stderr, "usage: %s [-hiasdpmtf]\n", name);
474 475 476
	fprintf(stderr, "\t-i\tdump info\n");
	fprintf(stderr, "\t-a\ttest all modes\n");
	fprintf(stderr, "\t-s\t<duration>\tsleep between each mode test\n");
477
	fprintf(stderr, "\t-d\t<depth>\tbit depth of scanout buffer\n");
478
	fprintf(stderr, "\t-p\t<planew,h>,<crtcx,y>,<crtcw,h> test overlay plane\n");
479
	fprintf(stderr, "\t-m\ttest the preferred mode\n");
480
	fprintf(stderr, "\t-t\tuse a tiled framebuffer\n");
481
	fprintf(stderr, "\t-r\tprint a QR code on the screen whose content is \"pass\" for the automatic test\n");
482
	fprintf(stderr, "\t-o\t<id of the display>,<number of the mode>\tonly test specified mode on the specified display\n");
483 484 485
	fprintf(stderr, "\t-f\t<clock MHz>,<hdisp>,<hsync-start>,<hsync-end>,<htotal>,\n");
	fprintf(stderr, "\t\t<vdisp>,<vsync-start>,<vsync-end>,<vtotal>\n");
	fprintf(stderr, "\t\ttest force mode\n");
486
	fprintf(stderr, "\tDefault is to test all modes.\n");
487 488 489
	exit(0);
}

490 491
#define dump_resource(res) if (res) dump_##res()

492
static gboolean input_event(GIOChannel *source, GIOCondition condition,
493
				gpointer data)
494
{
Daniel Vetter's avatar
Daniel Vetter committed
495
	gchar buf[2];
496 497
	gsize count;

498
	count = read(g_io_channel_unix_get_fd(source), buf, sizeof(buf));
499
	if (buf[0] == 'q' && (count == 1 || buf[1] == '\n')) {
500
		exit(0);
501
	}
502 503 504 505

	return TRUE;
}

506 507 508 509 510
static void enter_exec_path( char **argv )
{
	char *exec_path = NULL;
	char *pos = NULL;
	short len_path = 0;
511
	int ret;
512 513 514 515 516 517 518 519 520

	len_path = strlen( argv[0] );
	exec_path = (char*) malloc(len_path);

	memcpy(exec_path, argv[0], len_path);
	pos = strrchr(exec_path, '/');
	if (pos != NULL)
		*(pos+1) = '\0';

521 522
	ret = chdir(exec_path);
	assert(ret == 0);
523 524 525
	free(exec_path);
}

526 527 528 529
int main(int argc, char **argv)
{
	int c;
	int ret = 0;
530
	GIOChannel *stdinchannel;
531
	GMainLoop *mainloop;
532
	float force_clock;
533

534 535
	drmtest_skip_on_simulation();

536 537
	enter_exec_path( argv );

538 539 540
	opterr = 0;
	while ((c = getopt(argc, argv, optstr)) != -1) {
		switch (c) {
541 542 543 544 545 546 547 548
		case 'i':
			dump_info = 1;
			break;
		case 'a':
			test_all_modes = 1;
			break;
		case 'f':
			force_mode = 1;
549 550 551
			if(sscanf(optarg,"%f,%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu",
				&force_clock,&force_timing.hdisplay, &force_timing.hsync_start,&force_timing.hsync_end,&force_timing.htotal,
				&force_timing.vdisplay, &force_timing.vsync_start, &force_timing.vsync_end, &force_timing.vtotal)!= 9)
552
				usage(argv[0]);
553 554
			force_timing.clock = force_clock*1000;

555 556 557 558
			break;
		case 's':
			sleep_between_modes = atoi(optarg);
			break;
559 560 561 562
		case 'd':
			depth = atoi(optarg);
			fprintf(stderr, "using depth %d\n", depth);
			break;
563
		case 'p':
564 565 566
			if (sscanf(optarg, "%d,%d,%d,%d,%d,%d", &plane_width,
				   &plane_height, &crtc_x, &crtc_y,
				   &crtc_w, &crtc_h) != 6)
567
				usage(argv[0]);
568 569
			test_plane = 1;
			break;
570 571 572
		case 'm':
			test_preferred_mode = 1;
			break;
573 574 575
		case 't':
			enable_tiling = 1;
			break;
576 577 578
		case 'r':
			qr_code = 1;
			break;
579
		case 'o':
580
			sscanf(optarg, "%d,%d", &specified_disp_id, &specified_mode_num);
581
			break;
582 583 584 585 586 587 588 589
		default:
			fprintf(stderr, "unknown option %c\n", c);
			/* fall through */
		case 'h':
			usage(argv[0]);
			break;
		}
	}
590
	if (!test_all_modes && !force_mode && !dump_info &&
591
	    !test_preferred_mode && specified_mode_num == -1)
592
		test_all_modes = 1;
593

Daniel Vetter's avatar
Daniel Vetter committed
594
	drm_fd = drm_open_any();
595

Imre Deak's avatar
Imre Deak committed
596 597
	do_or_die(drmtest_set_vt_graphics_mode());

598 599 600 601
	mainloop = g_main_loop_new(NULL, FALSE);
	if (!mainloop) {
		fprintf(stderr, "failed to create glib mainloop\n");
		ret = -1;
602
		goto out_close;
603 604
	}

605 606 607
	if (!testdisplay_setup_hotplug()) {
		fprintf(stderr, "failed to initialize hotplug support\n");
		goto out_mainloop;
608 609 610 611 612
	}

	stdinchannel = g_io_channel_unix_new(0);
	if (!stdinchannel) {
		fprintf(stderr, "failed to create stdin GIO channel\n");
613
		goto out_hotplug;
614 615 616 617 618 619
	}

	ret = g_io_add_watch(stdinchannel, G_IO_IN | G_IO_ERR, input_event,
			     NULL);
	if (ret < 0) {
		fprintf(stderr, "failed to add watch on stdin GIO channel\n");
620
		goto out_stdio;
621 622
	}

623 624
	ret = 0;

625 626
	if (!update_display()) {
		ret = 1;
627
		goto out_stdio;
628 629 630
	}

	if (dump_info || test_all_modes)
631
		goto out_stdio;
632

633 634
	g_main_loop_run(mainloop);

635
out_stdio:
636
	g_io_channel_shutdown(stdinchannel, TRUE, NULL);
637 638 639
out_hotplug:
	testdisplay_cleanup_hotplug();
out_mainloop:
640 641
	g_main_loop_unref(mainloop);
out_close:
Daniel Vetter's avatar
Daniel Vetter committed
642 643
	close(drm_fd);

644 645
	return ret;
}