1/*
2 * Copyright (c) 2015 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 * To compile standalone: gcc -o dri3info dri3info.c `pkg-config --cflags --libs xcb-dri3 x11-xcb xrandr xxf86vm libdrm`
24 */
25
26#include <X11/Xlib.h>
27#include <X11/Xlib-xcb.h>
28#include <xcb/xcb.h>
29#include <xcb/dri3.h>
30#include <unistd.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <stdint.h>
34#include <string.h>
35#include <sys/stat.h>
36#include <drm.h>
37#include <xf86drm.h>
38
39#include <X11/extensions/Xrandr.h>
40#include <X11/extensions/xf86vmode.h>
41
42static int dri3_query_version(Display *dpy, int *major, int *minor)
43{
44	xcb_connection_t *c = XGetXCBConnection(dpy);
45	xcb_dri3_query_version_reply_t *reply;
46	xcb_generic_error_t *error;
47
48	*major = *minor = -1;
49
50	reply = xcb_dri3_query_version_reply(c,
51					     xcb_dri3_query_version(c,
52								    XCB_DRI3_MAJOR_VERSION,
53								    XCB_DRI3_MINOR_VERSION),
54					     &error);
55	free(error);
56	if (reply == NULL)
57		return -1;
58
59	*major = reply->major_version;
60	*minor = reply->minor_version;
61	free(reply);
62
63	return 0;
64}
65
66static int dri3_exists(Display *dpy)
67{
68	const xcb_query_extension_reply_t *ext;
69	int major, minor;
70
71	ext = xcb_get_extension_data(XGetXCBConnection(dpy), &xcb_dri3_id);
72	if (ext == NULL || !ext->present)
73		return 0;
74
75	if (dri3_query_version(dpy, &major, &minor) < 0)
76		return 0;
77
78	return major >= 0;
79}
80
81static int dri3_open(Display *dpy)
82{
83	xcb_connection_t *c = XGetXCBConnection(dpy);
84	xcb_dri3_open_cookie_t cookie;
85	xcb_dri3_open_reply_t *reply;
86
87	if (!dri3_exists(dpy))
88		return -1;
89
90	cookie = xcb_dri3_open(c, RootWindow(dpy, DefaultScreen(dpy)), None);
91	reply = xcb_dri3_open_reply(c, cookie, NULL);
92
93	if (!reply)
94		return -1;
95
96	if (reply->nfd != 1)
97		return -1;
98
99	return xcb_dri3_open_reply_fds(c, reply)[0];
100}
101
102static void get_device_path(int fd, char *buf, int len)
103{
104	struct stat remote, local;
105	int i;
106
107	if (fstat(fd, &remote))
108		goto out;
109
110	for (i = 0; i < 16; i++) {
111		snprintf(buf, len, "/dev/dri/card%d", i);
112		if (stat(buf, &local))
113			continue;
114
115		if (local.st_mode == remote.st_mode &&
116		    local.st_rdev == remote.st_rdev)
117			return;
118
119		snprintf(buf, len, "/dev/dri/renderD%d", i + 128);
120		if (stat(buf, &local))
121			continue;
122
123		if (local.st_mode == remote.st_mode &&
124		    local.st_rdev == remote.st_rdev)
125			return;
126	}
127
128out:
129	strncpy(buf, "unknown path", len);
130}
131
132static void get_driver_name(int fd, char *name, int len)
133{
134	drm_version_t version;
135
136	memset(name, 0, len);
137	memset(&version, 0, sizeof(version));
138	version.name_len = len;
139	version.name = name;
140
141	(void)drmIoctl(fd, DRM_IOCTL_VERSION, &version);
142}
143
144static int compute_refresh_rate_from_mode(long n, long d, unsigned flags,
145					   int32_t *numerator,
146					   int32_t *denominator)
147{
148	int i;
149
150	/* The mode flags are only defined privately to the Xserver (in xf86str.h)
151	 * but they at least bit compatible between VidMode, RandR and DRM.
152	 */
153# define V_INTERLACE 0x010
154# define V_DBLSCAN   0x020
155
156	if (flags & V_INTERLACE)
157		n *= 2;
158	else if (flags & V_DBLSCAN)
159		d *= 2;
160
161	/* The OML_sync_control spec requires that if the refresh rate is a
162	 * whole number, that the returned numerator be equal to the refresh
163	 * rate and the denominator be 1.
164	 */
165
166	if (n % d == 0) {
167		n /= d;
168		d = 1;
169	}
170	else {
171		static const unsigned f[] = { 13, 11, 7, 5, 3, 2, 0 };
172
173		/* This is a poor man's way to reduce a fraction.  It's far from
174		 * perfect, but it will work well enough for this situation.
175		 */
176
177		for (i = 0; f[i] != 0; i++) {
178			while (n % f[i] == 0 && d % f[i] == 0) {
179				d /= f[i];
180				n /= f[i];
181			}
182		}
183	}
184
185	*numerator = n;
186	*denominator = d;
187	return 1;
188}
189
190static int RRGetMscRate(Display *dpy, int32_t *numerator, int32_t *denominator)
191{
192	int ret = 0;
193	Window root = RootWindow(dpy, DefaultScreen(dpy));
194	XRRScreenResources *res;
195	int rr_event, rr_error;
196	RROutput primary;
197	RRMode mode = 0;
198	int n;
199
200	if (!XRRQueryExtension(dpy, &rr_event, &rr_error))
201		return ret;
202
203	res = XRRGetScreenResourcesCurrent(dpy, root);
204	if (res == NULL)
205		return ret;
206
207	/* Use the primary output if specified, otherwise
208	 * use the mode on the first enabled crtc.
209	 */
210	primary = XRRGetOutputPrimary(dpy, root);
211	if (primary) {
212		XRROutputInfo *output;
213
214		output = XRRGetOutputInfo(dpy, res, primary);
215		if (output != NULL) {
216			if (output->crtc) {
217				XRRCrtcInfo *crtc;
218
219				crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
220				if (crtc) {
221					mode = crtc->mode;
222					XRRFreeCrtcInfo(crtc);
223				}
224			}
225			XRRFreeOutputInfo(output);
226		}
227	}
228
229	for (n = 0; mode == 0 && n < res->ncrtc; n++) {
230		XRRCrtcInfo *crtc;
231
232		crtc = XRRGetCrtcInfo(dpy, res, res->crtcs[n]);
233		if (crtc) {
234			mode = crtc->mode;
235			XRRFreeCrtcInfo(crtc);
236		}
237	}
238
239	for (n = 0; n < res->nmode; n++) {
240		if (res->modes[n].id == mode) {
241			ret = compute_refresh_rate_from_mode(res->modes[n].dotClock,
242							     res->modes[n].hTotal*res->modes[n].vTotal,
243							     res->modes[n].modeFlags,
244							     numerator, denominator);
245			break;
246		}
247	}
248
249	XRRFreeScreenResources(res);
250	return ret;
251}
252
253static int VMGetMscRate(Display *dpy, int32_t *numerator, int32_t *denominator)
254{
255	XF86VidModeModeLine mode_line;
256	int dot_clock;
257	int i;
258
259	if (XF86VidModeQueryVersion(dpy, &i, &i) &&
260	    XF86VidModeGetModeLine(dpy, DefaultScreen(dpy), &dot_clock, &mode_line))
261		return compute_refresh_rate_from_mode(dot_clock * 1000,
262						      mode_line.vtotal * mode_line.htotal,
263						      mode_line.flags,
264						      numerator, denominator);
265
266	return 0;
267}
268
269static int get_refresh_rate(Display *dpy,
270			     int32_t *numerator,
271			     int32_t *denominator)
272{
273	if (RRGetMscRate(dpy, numerator, denominator))
274		return 1;
275
276	if (VMGetMscRate(dpy, numerator, denominator))
277		return 1;
278
279	return 0;
280}
281
282static void info(const char *dpyname)
283{
284	Display *dpy;
285	int device;
286	int32_t numerator, denominator;
287
288	dpy = XOpenDisplay(dpyname);
289	if (dpy == NULL) {
290		printf("Unable to connect to display '%s'\n",
291		       dpyname ?: getenv("DISPLAY") ?: "unset");
292		return;
293	}
294
295	printf("Display '%s'\n", DisplayString(dpy));
296	device = dri3_open(dpy);
297	if (device < 0) {
298		printf("\tUnable to connect to DRI3\n");
299	} else {
300		char device_path[1024];
301		char driver_name[1024];
302
303		get_device_path(device, device_path, sizeof(device_path));
304		get_driver_name(device, driver_name, sizeof(driver_name));
305
306		printf("Connected to DRI3, using fd %d which matches %s, driver %s\n",
307		       device, device_path, driver_name);
308		close(device);
309	}
310
311	if (get_refresh_rate(dpy, &numerator, &denominator))
312		printf("\tPrimary refresh rate: %d/%d (%.1fHz)\n",
313		       numerator, denominator, numerator/(float)denominator);
314
315	XCloseDisplay(dpy);
316}
317
318int main(int argc, char **argv)
319{
320	int i;
321
322	if (argc > 1) {
323		for (i = 1; i < argc; i++)
324			info(argv[i]);
325	} else
326		info(NULL);
327
328	return 0;
329}
330