1/*
2 * Copyright (c) 2014 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 *
23 */
24
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#endif
28
29#include <X11/Xlib.h>
30#include <X11/Xlib-xcb.h>
31#include <X11/xshmfence.h>
32#include <X11/Xutil.h>
33#include <X11/Xlibint.h>
34#include <X11/extensions/randr.h>
35#include <X11/extensions/Xrandr.h>
36#include <X11/extensions/Xrender.h>
37#include <X11/extensions/XShm.h>
38#if HAVE_X11_EXTENSIONS_SHMPROTO_H
39#include <X11/extensions/shmproto.h>
40#elif HAVE_X11_EXTENSIONS_SHMSTR_H
41#include <X11/extensions/shmstr.h>
42#else
43#error Failed to find the right header for X11 MIT-SHM protocol definitions
44#endif
45#include <xcb/xcb.h>
46#include <xcb/present.h>
47#include <xf86drm.h>
48#include <i915_drm.h>
49
50#include <stdio.h>
51#include <string.h>
52#include <fcntl.h>
53#include <unistd.h>
54#include <assert.h>
55#include <errno.h>
56#include <setjmp.h>
57#include <signal.h>
58
59#include <sys/mman.h>
60#include <sys/ipc.h>
61#include <sys/shm.h>
62#include <pciaccess.h>
63
64#include "dri3.h"
65
66#define ALIGN(x, y) (((x) + (y) - 1) & -(y))
67#define PAGE_ALIGN(x) ALIGN(x, 4096)
68
69#define GTT I915_GEM_DOMAIN_GTT
70#define CPU I915_GEM_DOMAIN_CPU
71
72static int _x_error_occurred;
73static uint32_t stamp;
74
75static int
76_check_error_handler(Display     *display,
77		     XErrorEvent *event)
78{
79	printf("X11 error from display %s, serial=%ld, error=%d, req=%d.%d\n",
80	       DisplayString(display),
81	       event->serial,
82	       event->error_code,
83	       event->request_code,
84	       event->minor_code);
85	_x_error_occurred++;
86	return False; /* ignored */
87}
88
89static int is_i915_device(int fd)
90{
91	drm_version_t version;
92	char name[5] = "";
93
94	memset(&version, 0, sizeof(version));
95	version.name_len = 4;
96	version.name = name;
97
98	if (drmIoctl(fd, DRM_IOCTL_VERSION, &version))
99		return 0;
100
101	return strcmp("i915", name) == 0;
102}
103
104static int is_intel(int fd)
105{
106	struct drm_i915_getparam gp;
107	int ret;
108
109	/* Confirm that this is a i915.ko device with GEM/KMS enabled */
110	ret = is_i915_device(fd);
111	if (ret) {
112		gp.param = I915_PARAM_HAS_GEM;
113		gp.value = &ret;
114		if (drmIoctl(fd, DRM_IOCTL_I915_GETPARAM, &gp))
115			ret = 0;
116	}
117	return ret;
118}
119
120static void *setup_msc(Display *dpy,  Window win)
121{
122	xcb_connection_t *c = XGetXCBConnection(dpy);
123	xcb_void_cookie_t cookie;
124	uint32_t id = xcb_generate_id(c);
125	xcb_generic_error_t *error;
126	void *q;
127
128	cookie = xcb_present_select_input_checked(c, id, win, XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY);
129	q = xcb_register_for_special_xge(c, &xcb_present_id, id, &stamp);
130
131	error = xcb_request_check(c, cookie);
132	assert(error == NULL);
133
134	return q;
135}
136
137static uint64_t check_msc(Display *dpy, Window win, void *q, uint64_t last_msc)
138{
139	xcb_connection_t *c = XGetXCBConnection(dpy);
140	uint64_t msc = 0;
141
142	xcb_present_notify_msc(c, win, 0, 0, 0, 0);
143	xcb_flush(c);
144
145	do {
146		xcb_present_complete_notify_event_t *ce;
147		xcb_generic_event_t *ev;
148
149		ev = xcb_wait_for_special_event(c, q);
150		if (ev == NULL)
151			break;
152
153		ce = (xcb_present_complete_notify_event_t *)ev;
154		if (ce->kind != XCB_PRESENT_COMPLETE_KIND_PIXMAP)
155			msc = ce->msc;
156		free(ev);
157	} while (msc == 0);
158
159	if (msc < last_msc) {
160		printf("Invalid MSC: was %llu, now %llu\n",
161		       (long long)last_msc, (long long)msc);
162	}
163
164	return msc;
165}
166
167static void teardown_msc(Display *dpy, void *q)
168{
169	xcb_unregister_for_special_event(XGetXCBConnection(dpy), q);
170}
171static int test_whole(Display *dpy)
172{
173	Pixmap pixmap;
174	struct dri3_fence fence;
175	Window root;
176	unsigned int width, height;
177	unsigned border, depth;
178	int x, y, ret = 1;
179
180	XGetGeometry(dpy, DefaultRootWindow(dpy),
181		     &root, &x, &y, &width, &height, &border, &depth);
182
183	if (dri3_create_fence(dpy, root, &fence))
184		return 0;
185
186	printf("Testing whole screen flip: %dx%d\n", width, height);
187	_x_error_occurred = 0;
188
189	xshmfence_reset(fence.addr);
190
191	pixmap = XCreatePixmap(dpy, root, width, height, depth);
192	xcb_present_pixmap(XGetXCBConnection(dpy),
193			   root, pixmap,
194			   0, /* sbc */
195			   0, /* valid */
196			   0, /* update */
197			   0, /* x_off */
198			   0, /* y_off */
199			   None,
200			   None, /* wait fence */
201			   fence.xid,
202			   XCB_PRESENT_OPTION_NONE,
203			   0, /* target msc */
204			   0, /* divisor */
205			   0, /* remainder */
206			   0, NULL);
207	XFreePixmap(dpy, pixmap);
208
209	pixmap = XCreatePixmap(dpy, root, width, height, depth);
210	xcb_present_pixmap(XGetXCBConnection(dpy),
211			   root, pixmap,
212			   0, /* sbc */
213			   0, /* valid */
214			   0, /* update */
215			   0, /* x_off */
216			   0, /* y_off */
217			   None,
218			   None, /* wait fence */
219			   None, /* sync fence */
220			   XCB_PRESENT_OPTION_NONE,
221			   0, /* target msc */
222			   0, /* divisor */
223			   0, /* remainder */
224			   0, NULL);
225	XFreePixmap(dpy, pixmap);
226	XFlush(dpy);
227
228	ret = !!xshmfence_await(fence.addr);
229	dri3_fence_free(dpy, &fence);
230
231	XSync(dpy, True);
232	ret += !!_x_error_occurred;
233
234	return ret;
235}
236
237static inline XRRScreenResources *_XRRGetScreenResourcesCurrent(Display *dpy, Window window)
238{
239	XRRScreenResources *res;
240
241	res = XRRGetScreenResourcesCurrent(dpy, window);
242	if (res == NULL)
243		res = XRRGetScreenResources(dpy, window);
244
245	return res;
246}
247
248static XRRModeInfo *lookup_mode(XRRScreenResources *res, int id)
249{
250	int i;
251
252	for (i = 0; i < res->nmode; i++) {
253		if (res->modes[i].id == id)
254			return &res->modes[i];
255	}
256
257	return NULL;
258}
259
260static int for_each_crtc(Display *dpy,
261			  int (*func)(Display *dpy,
262				      RRCrtc crtc,
263				      int width, int height,
264				      void *closure),
265			  void *closure)
266{
267	XRRScreenResources *res;
268	XRRCrtcInfo **original_crtc;
269	int i, j, err = 0;
270
271	if (!XRRQueryVersion(dpy, &i, &j))
272		return -1;
273
274	res = _XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy));
275	if (res == NULL)
276		return -1;
277
278	original_crtc = malloc(sizeof(XRRCrtcInfo *)*res->ncrtc);
279	for (i = 0; i < res->ncrtc; i++)
280		original_crtc[i] = XRRGetCrtcInfo(dpy, res, res->crtcs[i]);
281
282	printf("noutput=%d, ncrtc=%d\n", res->noutput, res->ncrtc);
283
284	for (i = 0; i < res->noutput; i++) {
285		XRROutputInfo *output;
286		XRRModeInfo *mode;
287
288		output = XRRGetOutputInfo(dpy, res, res->outputs[i]);
289		if (output == NULL)
290			continue;
291
292		mode = NULL;
293		if (res->nmode)
294			mode = lookup_mode(res, output->modes[0]);
295
296		for (j = 0; mode && j < output->ncrtc; j++) {
297			printf("[%d, %d] -- OUTPUT:%ld, CRTC:%ld\n",
298			       i, j, (long)res->outputs[i], (long)output->crtcs[j]);
299			XRRSetCrtcConfig(dpy, res, output->crtcs[j], CurrentTime,
300					 0, 0, output->modes[0], RR_Rotate_0, &res->outputs[i], 1);
301			XSync(dpy, True);
302
303			err += func(dpy, output->crtcs[j], mode->width, mode->height, closure);
304
305			XRRSetCrtcConfig(dpy, res, output->crtcs[j], CurrentTime,
306					 0, 0, None, RR_Rotate_0, NULL, 0);
307			XSync(dpy, True);
308		}
309
310		XRRFreeOutputInfo(output);
311	}
312
313	for (i = 0; i < res->ncrtc; i++)
314		XRRSetCrtcConfig(dpy, res, res->crtcs[i], CurrentTime,
315				 original_crtc[i]->x,
316				 original_crtc[i]->y,
317				 original_crtc[i]->mode,
318				 original_crtc[i]->rotation,
319				 original_crtc[i]->outputs,
320				 original_crtc[i]->noutput);
321
322	free(original_crtc);
323	XRRFreeScreenResources(res);
324
325	return j;
326}
327
328struct test_crtc {
329	Window win;
330	int depth;
331	unsigned flags;
332
333	struct dri3_fence fence;
334	void *queue;
335	uint64_t msc;
336};
337#define SYNC 0x1
338
339static int __test_crtc(Display *dpy, RRCrtc crtc,
340		       int width, int height,
341		       void *closure)
342{
343	struct test_crtc *test = closure;
344	Pixmap pixmap;
345	int err = 0;
346
347	test->msc = check_msc(dpy, test->win, test->queue, test->msc);
348
349	if (test->flags & SYNC)
350		xshmfence_reset(test->fence.addr);
351
352	pixmap = XCreatePixmap(dpy, test->win, width, height, test->depth);
353	xcb_present_pixmap(XGetXCBConnection(dpy),
354			   test->win, pixmap,
355			   0, /* sbc */
356			   0, /* valid */
357			   0, /* update */
358			   0, /* x_off */
359			   0, /* y_off */
360			   crtc,
361			   None, /* wait fence */
362			   test->flags & SYNC ? test->fence.xid : None,
363			   XCB_PRESENT_OPTION_NONE,
364			   0, /* target msc */
365			   1, /* divisor */
366			   0, /* remainder */
367			   0, NULL);
368	XFreePixmap(dpy, pixmap);
369
370	if (test->flags & SYNC) {
371		pixmap = XCreatePixmap(dpy, test->win, width, height, test->depth);
372		xcb_present_pixmap(XGetXCBConnection(dpy),
373				   test->win, pixmap,
374				   1, /* sbc */
375				   0, /* valid */
376				   0, /* update */
377				   0, /* x_off */
378				   0, /* y_off */
379				   crtc,
380				   None, /* wait fence */
381				   None, /* sync fence */
382				   XCB_PRESENT_OPTION_NONE,
383				   1, /* target msc */
384				   1, /* divisor */
385				   0, /* remainder */
386				   0, NULL);
387		XFreePixmap(dpy, pixmap);
388		XFlush(dpy);
389		err += !!xshmfence_await(test->fence.addr);
390	}
391
392	test->msc = check_msc(dpy, test->win, test->queue, test->msc);
393	return err;
394}
395
396static int test_crtc(Display *dpy, void *queue, uint64_t last_msc)
397{
398	struct test_crtc test;
399	int err = 0;
400
401	XSync(dpy, True);
402	_x_error_occurred = 0;
403
404	test.win = DefaultRootWindow(dpy);
405	test.depth = DefaultDepth(dpy, DefaultScreen(dpy));
406	if (dri3_create_fence(dpy, test.win, &test.fence))
407		return -1;
408	test.queue = queue;
409	test.msc = last_msc;
410
411	printf("Testing each crtc, without waiting for each flip\n");
412	test.flags = 0;
413	err += for_each_crtc(dpy, __test_crtc, &test);
414
415	printf("Testing each crtc, waiting for flips to complete\n");
416	test.flags = SYNC;
417	err += for_each_crtc(dpy, __test_crtc, &test);
418
419	test.msc = check_msc(dpy, test.win, test.queue, test.msc);
420	dri3_fence_free(dpy, &test.fence);
421
422	XSync(dpy, True);
423	err += !!_x_error_occurred;
424
425	if (err)
426		printf("%s: failures=%d\n", __func__, err);
427
428	return err;
429}
430
431static int
432can_use_shm(Display *dpy)
433{
434	int major, minor, has_pixmap;
435
436	if (!XShmQueryExtension(dpy))
437		return 0;
438
439	XShmQueryVersion(dpy, &major, &minor, &has_pixmap);
440	return has_pixmap;
441}
442
443static int test_shm(Display *dpy)
444{
445	Window win = DefaultRootWindow(dpy);
446	XShmSegmentInfo shm;
447	Pixmap pixmap;
448	Window root;
449	unsigned int width, height;
450	unsigned border, depth;
451	int x, y, ret = 1;
452
453	if (!can_use_shm(dpy))
454		return 0;
455
456	_x_error_occurred = 0;
457
458	XGetGeometry(dpy, win, &root, &x, &y,
459		     &width, &height, &border, &depth);
460
461	printf("Using %dx%d SHM\n", width, height);
462
463	shm.shmid = shmget(IPC_PRIVATE, height * 4*width, IPC_CREAT | 0666);
464	if (shm.shmid == -1)
465		return 0;
466
467	shm.shmaddr = shmat(shm.shmid, 0, 0);
468	if (shm.shmaddr == (char *) -1)
469		goto rmid;
470
471	shm.readOnly = False;
472	XShmAttach(dpy, &shm);
473
474	pixmap = XShmCreatePixmap(dpy, DefaultRootWindow(dpy),
475				  shm.shmaddr, &shm, width, height, 24);
476	if (_x_error_occurred)
477		goto detach;
478
479	xcb_present_pixmap(XGetXCBConnection(dpy),
480			   win, pixmap,
481			   0, /* sbc */
482			   0, /* valid */
483			   0, /* update */
484			   0, /* x_off */
485			   0, /* y_off */
486			   None,
487			   None, /* wait fence */
488			   None,
489			   XCB_PRESENT_OPTION_NONE,
490			   0, /* target msc */
491			   0, /* divisor */
492			   0, /* remainder */
493			   0, NULL);
494	XFreePixmap(dpy, pixmap);
495
496	XSync(dpy, True);
497	if (_x_error_occurred)
498		goto detach;
499
500	ret = 0;
501detach:
502	XShmDetach(dpy, &shm);
503	shmdt(shm.shmaddr);
504	XSync(dpy, False);
505rmid:
506	shmctl(shm.shmid, IPC_RMID, NULL);
507	return ret;
508}
509
510static uint32_t gem_create(int fd, int size)
511{
512	struct drm_i915_gem_create create;
513
514	create.handle = 0;
515	create.size = size;
516	(void)drmIoctl(fd, DRM_IOCTL_I915_GEM_CREATE, &create);
517
518	return create.handle;
519}
520
521struct local_i915_gem_caching {
522	uint32_t handle;
523	uint32_t caching;
524};
525
526#define LOCAL_I915_GEM_SET_CACHING	0x2f
527#define LOCAL_IOCTL_I915_GEM_SET_CACHING DRM_IOW(DRM_COMMAND_BASE + LOCAL_I915_GEM_SET_CACHING, struct local_i915_gem_caching)
528
529static int gem_set_caching(int fd, uint32_t handle, int caching)
530{
531	struct local_i915_gem_caching arg;
532
533	arg.handle = handle;
534	arg.caching = caching;
535
536	return drmIoctl(fd, LOCAL_IOCTL_I915_GEM_SET_CACHING, &arg) == 0;
537}
538
539static int gem_export(int fd, uint32_t handle)
540{
541	struct drm_prime_handle args;
542
543	args.handle = handle;
544	args.flags = O_CLOEXEC;
545
546	if (drmIoctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args))
547		return -1;
548
549	return args.fd;
550}
551
552static void gem_close(int fd, uint32_t handle)
553{
554	struct drm_gem_close close;
555
556	close.handle = handle;
557	(void)drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &close);
558}
559
560static int test_dri3(Display *dpy)
561{
562	Window win = DefaultRootWindow(dpy);
563	Pixmap pixmap;
564	Window root;
565	unsigned int width, height;
566	unsigned border, depth;
567	unsigned stride, size;
568	int x, y, ret = 1;
569	int device, handle;
570	int bpp;
571
572	device = dri3_open(dpy);
573	if (device < 0)
574		return 0;
575
576	if (!is_intel(device))
577		return 0;
578
579	printf("Opened Intel DRI3 device\n");
580
581	XGetGeometry(dpy, win, &root, &x, &y,
582		     &width, &height, &border, &depth);
583
584	switch (depth) {
585	case 8: bpp = 8; break;
586	case 15: case 16: bpp = 16; break;
587	case 24: case 32: bpp = 32; break;
588	default: return 0;
589	}
590
591	stride = width * bpp/8;
592	size = PAGE_ALIGN(stride * height);
593	printf("Creating DRI3 %dx%d (source stride=%d, size=%d) for GTT\n",
594	       width, height, stride, size);
595
596	pixmap = 0;
597	handle = gem_create(device, size);
598	if (handle) {
599		pixmap = dri3_create_pixmap(dpy, root,
600					     width, height, depth,
601					     gem_export(device, handle), bpp, stride, size);
602		gem_close(device, handle);
603	}
604	if (pixmap == 0)
605		goto fail;
606
607	xcb_present_pixmap(XGetXCBConnection(dpy),
608			   win, pixmap,
609			   0, /* sbc */
610			   0, /* valid */
611			   0, /* update */
612			   0, /* x_off */
613			   0, /* y_off */
614			   None,
615			   None, /* wait fence */
616			   None,
617			   XCB_PRESENT_OPTION_NONE,
618			   0, /* target msc */
619			   0, /* divisor */
620			   0, /* remainder */
621			   0, NULL);
622	XFreePixmap(dpy, pixmap);
623
624	XSync(dpy, True);
625	if (_x_error_occurred)
626		goto fail;
627
628	printf("Creating DRI3 %dx%d (source stride=%d, size=%d) for CPU\n",
629	       width, height, stride, size);
630
631	pixmap = 0;
632	handle = gem_create(device, size);
633	if (handle) {
634		gem_set_caching(device, handle, CPU);
635		handle = dri3_create_pixmap(dpy, root,
636					     width, height, depth,
637					     gem_export(device, handle), bpp, stride, size);
638		gem_close(device, handle);
639	}
640	if (pixmap == 0)
641		goto fail;
642
643	xcb_present_pixmap(XGetXCBConnection(dpy),
644			   win, pixmap,
645			   0, /* sbc */
646			   0, /* valid */
647			   0, /* update */
648			   0, /* x_off */
649			   0, /* y_off */
650			   None,
651			   None, /* wait fence */
652			   None,
653			   XCB_PRESENT_OPTION_NONE,
654			   0, /* target msc */
655			   0, /* divisor */
656			   0, /* remainder */
657			   0, NULL);
658	XFreePixmap(dpy, pixmap);
659
660	XSync(dpy, True);
661	if (_x_error_occurred)
662		goto fail;
663
664	ret = 0;
665fail:
666	close(device);
667	return ret;
668}
669
670static int has_present(Display *dpy)
671{
672	xcb_connection_t *c = XGetXCBConnection(dpy);
673	xcb_present_query_version_reply_t *reply;
674	xcb_generic_error_t *error = NULL;
675
676	reply = xcb_present_query_version_reply(c,
677						xcb_present_query_version(c,
678									  XCB_PRESENT_MAJOR_VERSION,
679									  XCB_PRESENT_MINOR_VERSION),
680						&error);
681
682	free(reply);
683	free(error);
684
685	return reply != NULL;
686}
687
688int main(void)
689{
690	Display *dpy;
691	Window root;
692	int error = 0;
693	uint64_t last_msc;
694	void *queue;
695
696	dpy = XOpenDisplay(NULL);
697	if (dpy == NULL)
698		return 77;
699
700	if (!has_present(dpy))
701		return 77;
702
703	root = DefaultRootWindow(dpy);
704
705	signal(SIGALRM, SIG_IGN);
706	XSetErrorHandler(_check_error_handler);
707
708	queue = setup_msc(dpy, root);
709	last_msc = check_msc(dpy, root, queue, 0);
710
711	error += test_whole(dpy);
712	last_msc = check_msc(dpy, root, queue, last_msc);
713
714	error += test_crtc(dpy, queue, last_msc);
715	last_msc = check_msc(dpy, root, queue, last_msc);
716
717	error += test_shm(dpy);
718	last_msc = check_msc(dpy, root, queue, last_msc);
719
720	error += test_dri3(dpy);
721	last_msc = check_msc(dpy, root, queue, last_msc);
722
723	teardown_msc(dpy, queue);
724
725	return !!error;
726}
727