1/*
2 * DRM based mode setting test program
3 * Copyright 2008 Tungsten Graphics
4 *   Jakob Bornecrantz <jakob@tungstengraphics.com>
5 * Copyright 2008 Intel Corporation
6 *   Jesse Barnes <jesse.barnes@intel.com>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 * IN THE SOFTWARE.
25 */
26
27/*
28 * This fairly simple test program dumps output in a similar format to the
29 * "xrandr" tool everyone knows & loves.  It's necessarily slightly different
30 * since the kernel separates outputs into encoder and connector structures,
31 * each with their own unique ID.  The program also allows test testing of the
32 * memory management and mode setting APIs by allowing the user to specify a
33 * connector and mode to use for mode setting.  If all works as expected, a
34 * blue background should be painted on the monitor attached to the specified
35 * connector after the selected mode is set.
36 *
37 * TODO: use cairo to write the mode info on the selected output once
38 *       the mode has been programmed, along with possible test patterns.
39 */
40#include "config.h"
41
42#include <assert.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <stdint.h>
46#include <unistd.h>
47#include <string.h>
48#include <errno.h>
49
50#include "xf86drm.h"
51#include "xf86drmMode.h"
52#include "intel_bufmgr.h"
53
54#ifdef HAVE_CAIRO
55#include <math.h>
56#include <cairo.h>
57#endif
58
59drmModeRes *resources;
60int fd, modes;
61
62#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
63
64struct type_name {
65	int type;
66	char *name;
67};
68
69#define type_name_fn(res) \
70char * res##_str(int type) {			\
71	int i;						\
72	for (i = 0; i < ARRAY_SIZE(res##_names); i++) { \
73		if (res##_names[i].type == type)	\
74			return res##_names[i].name;	\
75	}						\
76	return "(invalid)";				\
77}
78
79struct type_name encoder_type_names[] = {
80	{ DRM_MODE_ENCODER_NONE, "none" },
81	{ DRM_MODE_ENCODER_DAC, "DAC" },
82	{ DRM_MODE_ENCODER_TMDS, "TMDS" },
83	{ DRM_MODE_ENCODER_LVDS, "LVDS" },
84	{ DRM_MODE_ENCODER_TVDAC, "TVDAC" },
85};
86
87type_name_fn(encoder_type)
88
89struct type_name connector_status_names[] = {
90	{ DRM_MODE_CONNECTED, "connected" },
91	{ DRM_MODE_DISCONNECTED, "disconnected" },
92	{ DRM_MODE_UNKNOWNCONNECTION, "unknown" },
93};
94
95type_name_fn(connector_status)
96
97struct type_name connector_type_names[] = {
98	{ DRM_MODE_CONNECTOR_Unknown, "unknown" },
99	{ DRM_MODE_CONNECTOR_VGA, "VGA" },
100	{ DRM_MODE_CONNECTOR_DVII, "DVI-I" },
101	{ DRM_MODE_CONNECTOR_DVID, "DVI-D" },
102	{ DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
103	{ DRM_MODE_CONNECTOR_Composite, "composite" },
104	{ DRM_MODE_CONNECTOR_SVIDEO, "s-video" },
105	{ DRM_MODE_CONNECTOR_LVDS, "LVDS" },
106	{ DRM_MODE_CONNECTOR_Component, "component" },
107	{ DRM_MODE_CONNECTOR_9PinDIN, "9-pin DIN" },
108	{ DRM_MODE_CONNECTOR_DisplayPort, "displayport" },
109	{ DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
110	{ DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
111};
112
113type_name_fn(connector_type)
114
115void dump_encoders(void)
116{
117	drmModeEncoder *encoder;
118	int i;
119
120	printf("Encoders:\n");
121	printf("id\tcrtc\ttype\tpossible crtcs\tpossible clones\t\n");
122	for (i = 0; i < resources->count_encoders; i++) {
123		encoder = drmModeGetEncoder(fd, resources->encoders[i]);
124
125		if (!encoder) {
126			fprintf(stderr, "could not get encoder %i: %s\n",
127				resources->encoders[i], strerror(errno));
128			continue;
129		}
130		printf("%d\t%d\t%s\t0x%08x\t0x%08x\n",
131		       encoder->encoder_id,
132		       encoder->crtc_id,
133		       encoder_type_str(encoder->encoder_type),
134		       encoder->possible_crtcs,
135		       encoder->possible_clones);
136		drmModeFreeEncoder(encoder);
137	}
138	printf("\n");
139}
140
141void dump_mode(drmModeModeInfo *mode)
142{
143	printf("  %s %.02f %d %d %d %d %d %d %d %d\n",
144	       mode->name,
145	       (float)mode->vrefresh / 1000,
146	       mode->hdisplay,
147	       mode->hsync_start,
148	       mode->hsync_end,
149	       mode->htotal,
150	       mode->vdisplay,
151	       mode->vsync_start,
152	       mode->vsync_end,
153	       mode->vtotal);
154}
155
156static void
157dump_props(drmModeConnector *connector)
158{
159	drmModePropertyPtr props;
160	int i;
161
162	for (i = 0; i < connector->count_props; i++) {
163		props = drmModeGetProperty(fd, connector->props[i]);
164		printf("\t%s, flags %d\n", props->name, props->flags);
165		drmModeFreeProperty(props);
166	}
167}
168
169void dump_connectors(void)
170{
171	drmModeConnector *connector;
172	int i, j;
173
174	printf("Connectors:\n");
175	printf("id\tencoder\tstatus\t\ttype\tsize (mm)\tmodes\n");
176	for (i = 0; i < resources->count_connectors; i++) {
177		connector = drmModeGetConnector(fd, resources->connectors[i]);
178
179		if (!connector) {
180			fprintf(stderr, "could not get connector %i: %s\n",
181				resources->connectors[i], strerror(errno));
182			continue;
183		}
184
185		printf("%d\t%d\t%s\t%s\t%dx%d\t\t%d\n",
186		       connector->connector_id,
187		       connector->encoder_id,
188		       connector_status_str(connector->connection),
189		       connector_type_str(connector->connector_type),
190		       connector->mmWidth, connector->mmHeight,
191		       connector->count_modes);
192
193		if (!connector->count_modes)
194			continue;
195
196		printf("  modes:\n");
197		printf("  name refresh (Hz) hdisp hss hse htot vdisp "
198		       "vss vse vtot)\n");
199		for (j = 0; j < connector->count_modes; j++)
200			dump_mode(&connector->modes[j]);
201
202		drmModeFreeConnector(connector);
203
204		printf("  props:\n");
205		dump_props(connector);
206	}
207	printf("\n");
208}
209
210void dump_crtcs(void)
211{
212	drmModeCrtc *crtc;
213	int i;
214
215	printf("CRTCs:\n");
216	printf("id\tfb\tpos\tsize\n");
217	for (i = 0; i < resources->count_crtcs; i++) {
218		crtc = drmModeGetCrtc(fd, resources->crtcs[i]);
219
220		if (!crtc) {
221			fprintf(stderr, "could not get crtc %i: %s\n",
222				resources->crtcs[i], strerror(errno));
223			continue;
224		}
225		printf("%d\t%d\t(%d,%d)\t(%dx%d)\n",
226		       crtc->crtc_id,
227		       crtc->buffer_id,
228		       crtc->x, crtc->y,
229		       crtc->width, crtc->height);
230		dump_mode(&crtc->mode);
231
232		drmModeFreeCrtc(crtc);
233	}
234	printf("\n");
235}
236
237void dump_framebuffers(void)
238{
239	drmModeFB *fb;
240	int i;
241
242	printf("Frame buffers:\n");
243	printf("id\tsize\tpitch\n");
244	for (i = 0; i < resources->count_fbs; i++) {
245		fb = drmModeGetFB(fd, resources->fbs[i]);
246
247		if (!fb) {
248			fprintf(stderr, "could not get fb %i: %s\n",
249				resources->fbs[i], strerror(errno));
250			continue;
251		}
252		printf("%d\t(%dx%d)\t%d\n",
253		       fb->fb_id,
254		       fb->width, fb->height);
255
256		drmModeFreeFB(fb);
257	}
258	printf("\n");
259}
260
261/*
262 * Mode setting with the kernel interfaces is a bit of a chore.
263 * First you have to find the connector in question and make sure the
264 * requested mode is available.
265 * Then you need to find the encoder attached to that connector so you
266 * can bind it with a free crtc.
267 */
268struct connector {
269	int id;
270	char mode_str[64];
271	drmModeModeInfo *mode;
272	drmModeEncoder *encoder;
273	int crtc;
274};
275
276static void
277connector_find_mode(struct connector *c)
278{
279	drmModeConnector *connector;
280	int i, j, size, ret, width, height;
281
282	/* First, find the connector & mode */
283	c->mode = NULL;
284	for (i = 0; i < resources->count_connectors; i++) {
285		connector = drmModeGetConnector(fd, resources->connectors[i]);
286
287		if (!connector) {
288			fprintf(stderr, "could not get connector %i: %s\n",
289				resources->connectors[i], strerror(errno));
290			drmModeFreeConnector(connector);
291			continue;
292		}
293
294		if (!connector->count_modes) {
295			drmModeFreeConnector(connector);
296			continue;
297		}
298
299		if (connector->connector_id != c->id) {
300			drmModeFreeConnector(connector);
301			continue;
302		}
303
304		for (j = 0; j < connector->count_modes; j++) {
305			c->mode = &connector->modes[j];
306			if (!strcmp(c->mode->name, c->mode_str))
307				break;
308		}
309
310		/* Found it, break out */
311		if (c->mode)
312			break;
313
314		drmModeFreeConnector(connector);
315	}
316
317	if (!c->mode) {
318		fprintf(stderr, "failed to find mode \"%s\"\n", c->mode_str);
319		return;
320	}
321
322	/* Now get the encoder */
323	for (i = 0; i < resources->count_encoders; i++) {
324		c->encoder = drmModeGetEncoder(fd, resources->encoders[i]);
325
326		if (!c->encoder) {
327			fprintf(stderr, "could not get encoder %i: %s\n",
328				resources->encoders[i], strerror(errno));
329			drmModeFreeEncoder(c->encoder);
330			continue;
331		}
332
333		if (c->encoder->encoder_id  == connector->encoder_id)
334			break;
335
336		drmModeFreeEncoder(c->encoder);
337	}
338
339	if (c->crtc == -1)
340		c->crtc = c->encoder->crtc_id;
341}
342
343#ifdef HAVE_CAIRO
344
345static int
346create_test_buffer(drm_intel_bufmgr *bufmgr,
347		   int width, int height, int *stride_out, drm_intel_bo **bo_out)
348{
349	drm_intel_bo *bo;
350	unsigned int *fb_ptr;
351	int size, ret, i, stride;
352	div_t d;
353	cairo_surface_t *surface;
354	cairo_t *cr;
355	char buf[64];
356	int x, y;
357
358	surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
359	stride = cairo_image_surface_get_stride(surface);
360	size = stride * height;
361	fb_ptr = (unsigned int *) cairo_image_surface_get_data(surface);
362
363	/* paint the buffer with colored tiles */
364	for (i = 0; i < width * height; i++) {
365		d = div(i, width);
366		fb_ptr[i] = 0x00130502 * (d.quot >> 6) + 0x000a1120 * (d.rem >> 6);
367	}
368
369	cr = cairo_create(surface);
370	cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
371	for (x = 0; x < width; x += 250)
372		for (y = 0; y < height; y += 250) {
373			cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
374			cairo_move_to(cr, x, y - 20);
375			cairo_line_to(cr, x, y + 20);
376			cairo_move_to(cr, x - 20, y);
377			cairo_line_to(cr, x + 20, y);
378			cairo_new_sub_path(cr);
379			cairo_arc(cr, x, y, 10, 0, M_PI * 2);
380			cairo_set_line_width(cr, 4);
381			cairo_set_source_rgb(cr, 0, 0, 0);
382			cairo_stroke_preserve(cr);
383			cairo_set_source_rgb(cr, 1, 1, 1);
384			cairo_set_line_width(cr, 2);
385			cairo_stroke(cr);
386			snprintf(buf, sizeof buf, "%d, %d", x, y);
387			cairo_move_to(cr, x + 20, y + 20);
388			cairo_text_path(cr, buf);
389			cairo_set_source_rgb(cr, 0, 0, 0);
390			cairo_stroke_preserve(cr);
391			cairo_set_source_rgb(cr, 1, 1, 1);
392			cairo_fill(cr);
393		}
394
395	cairo_destroy(cr);
396
397	bo = drm_intel_bo_alloc(bufmgr, "frontbuffer", size, 4096);
398	if (!bo) {
399		fprintf(stderr, "failed to alloc buffer: %s\n",
400			strerror(errno));
401		return -1;
402	}
403
404	drm_intel_bo_subdata(bo, 0, size, fb_ptr);
405
406	cairo_surface_destroy(surface);
407
408	*bo_out = bo;
409	*stride_out = stride;
410
411	return 0;
412}
413
414#else
415
416static int
417create_test_buffer(drm_intel_bufmgr *bufmgr,
418		   int width, int height, int *stride_out, drm_intel_bo **bo_out)
419{
420	drm_intel_bo *bo;
421	unsigned int *fb_ptr;
422	int size, ret, i, stride;
423	div_t d;
424
425	/* Mode size at 32 bpp */
426	stride = width * 4;
427	size = stride * height;
428
429	bo = drm_intel_bo_alloc(bufmgr, "frontbuffer", size, 4096);
430	if (!bo) {
431		fprintf(stderr, "failed to alloc buffer: %s\n",
432			strerror(errno));
433		return -1;
434	}
435
436	ret = drm_intel_gem_bo_map_gtt(bo);
437	if (ret) {
438		fprintf(stderr, "failed to GTT map buffer: %s\n",
439			strerror(errno));
440		return -1;
441	}
442
443	fb_ptr = bo->virtual;
444
445	/* paint the buffer with colored tiles */
446	for (i = 0; i < width * height; i++) {
447		d = div(i, width);
448		fb_ptr[i] = 0x00130502 * (d.quot >> 6) + 0x000a1120 * (d.rem >> 6);
449	}
450	drm_intel_gem_bo_unmap_gtt(bo);
451
452	*bo_out = bo;
453	*stride_out = stride;
454
455	return 0;
456}
457
458#endif
459
460static void
461set_mode(struct connector *c, int count)
462{
463	drmModeConnector *connector;
464	drmModeEncoder *encoder = NULL;
465	struct drm_mode_modeinfo *mode = NULL;
466	drm_intel_bufmgr *bufmgr;
467	drm_intel_bo *bo;
468	unsigned int fb_id;
469	int i, j, ret, width, height, x, stride;
470
471	width = 0;
472	height = 0;
473	for (i = 0; i < count; i++) {
474		connector_find_mode(&c[i]);
475		if (c[i].mode == NULL)
476			continue;
477		width += c[i].mode->hdisplay;
478		if (height < c[i].mode->vdisplay)
479			height = c[i].mode->vdisplay;
480	}
481
482	bufmgr = drm_intel_bufmgr_gem_init(fd, 2<<20);
483	if (!bufmgr) {
484		fprintf(stderr, "failed to init bufmgr: %s\n", strerror(errno));
485		return;
486	}
487
488	if (create_test_buffer(bufmgr, width, height, &stride, &bo))
489		return;
490
491	ret = drmModeAddFB(fd, width, height, 32, 32, stride, bo->handle,
492			   &fb_id);
493	if (ret) {
494		fprintf(stderr, "failed to add fb: %s\n", strerror(errno));
495		return;
496	}
497
498	x = 0;
499	for (i = 0; i < count; i++) {
500		int crtc_id;
501		if (c[i].mode == NULL)
502			continue;
503
504		printf("setting mode %s on connector %d, crtc %d\n",
505		       c[i].mode_str, c[i].id, c[i].crtc);
506
507		ret = drmModeSetCrtc(fd, c[i].crtc, fb_id, x, 0,
508				     &c[i].id, 1, c[i].mode);
509		x += c[i].mode->hdisplay;
510
511		if (ret) {
512			fprintf(stderr, "failed to set mode: %s\n", strerror(errno));
513			return;
514		}
515	}
516}
517
518extern char *optarg;
519extern int optind, opterr, optopt;
520static char optstr[] = "ecpmfs:";
521
522void usage(char *name)
523{
524	fprintf(stderr, "usage: %s [-ecpmf]\n", name);
525	fprintf(stderr, "\t-e\tlist encoders\n");
526	fprintf(stderr, "\t-c\tlist connectors\n");
527	fprintf(stderr, "\t-p\tlist CRTCs (pipes)\n");
528	fprintf(stderr, "\t-m\tlist modes\n");
529	fprintf(stderr, "\t-f\tlist framebuffers\n");
530	fprintf(stderr, "\t-s <connector_id>:<mode>\tset a mode\n");
531	fprintf(stderr, "\t-s <connector_id>@<crtc_id>:<mode>\tset a mode\n");
532	fprintf(stderr, "\n\tDefault is to dump all info.\n");
533	exit(0);
534}
535
536#define dump_resource(res) if (res) dump_##res()
537
538int main(int argc, char **argv)
539{
540	int c;
541	int encoders = 0, connectors = 0, crtcs = 0, framebuffers = 0;
542	char *modules[] = { "i915", "radeon" };
543	char *modeset = NULL, *mode, *connector;
544	int i, connector_id, count = 0;
545	struct connector con_args[2];
546
547	opterr = 0;
548	while ((c = getopt(argc, argv, optstr)) != -1) {
549		switch (c) {
550		case 'e':
551			encoders = 1;
552			break;
553		case 'c':
554			connectors = 1;
555			break;
556		case 'p':
557			crtcs = 1;
558			break;
559		case 'm':
560			modes = 1;
561			break;
562		case 'f':
563			framebuffers = 1;
564			break;
565		case 's':
566			modeset = strdup(optarg);
567			con_args[count].crtc = -1;
568			if (sscanf(optarg, "%d:%64s",
569				   &con_args[count].id,
570				   &con_args[count].mode_str) != 2 &&
571			    sscanf(optarg, "%d@%d:%64s",
572				   &con_args[count].id,
573				   &con_args[count].crtc,
574				   &con_args[count].mode_str) != 3)
575				usage(argv[0]);
576			count++;
577			break;
578		default:
579			usage(argv[0]);
580			break;
581		}
582	}
583
584	if (argc == 1)
585		encoders = connectors = crtcs = modes = framebuffers = 1;
586
587	for (i = 0; i < ARRAY_SIZE(modules); i++) {
588		printf("trying to load module %s...", modules[i]);
589		fd = drmOpen(modules[i], NULL);
590		if (fd < 0) {
591			printf("failed.\n");
592		} else {
593			printf("success.\n");
594			break;
595		}
596	}
597
598	if (i == ARRAY_SIZE(modules)) {
599		fprintf(stderr, "failed to load any modules, aborting.\n");
600		return -1;
601	}
602
603	resources = drmModeGetResources(fd);
604	if (!resources) {
605		fprintf(stderr, "drmModeGetResources failed: %s\n",
606			strerror(errno));
607		drmClose(fd);
608		return 1;
609	}
610
611	dump_resource(encoders);
612	dump_resource(connectors);
613	dump_resource(crtcs);
614	dump_resource(framebuffers);
615
616	if (count > 0) {
617		set_mode(con_args, count);
618		getchar();
619	}
620
621	drmModeFreeResources(resources);
622
623	return 0;
624}
625