1/*
2 * Copyright 2013 Red Hat Inc.
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 shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 *
22 * Authors: Ben Skeggs <bskeggs@redhat.com>
23 */
24
25#include "nouveau_present.h"
26#if defined(DRI3)
27#include "nv_include.h"
28#include "xf86drmMode.h"
29
30struct nouveau_present {
31	struct present_screen_info info;
32};
33
34static RRCrtcPtr
35nouveau_present_crtc(WindowPtr window)
36{
37	return randr_crtc_covering_drawable(&window->drawable);
38}
39
40static int
41nouveau_present_ust_msc(RRCrtcPtr rrcrtc, uint64_t *ust, uint64_t *msc)
42{
43	xf86CrtcPtr crtc = rrcrtc->devPrivate;
44	NVPtr pNv = NVPTR(crtc->scrn);
45	drmVBlank args;
46	int ret;
47
48	args.request.type = DRM_VBLANK_RELATIVE;
49	args.request.type |= drmmode_head(crtc) << DRM_VBLANK_HIGH_CRTC_SHIFT;
50	args.request.sequence = 0,
51	args.request.signal = 0,
52
53	ret = drmWaitVBlank(pNv->dev->fd, &args);
54	if (ret) {
55		*ust = *msc = 0;
56		return BadMatch;
57	}
58
59	*ust = (CARD64)args.reply.tval_sec * 1000000 + args.reply.tval_usec;
60	*msc = args.reply.sequence;
61	return Success;
62}
63
64struct nouveau_present_vblank {
65	uint64_t msc;
66};
67
68static void
69nouveau_present_vblank(void *priv, uint64_t name, uint64_t ust, uint32_t msc_lo)
70{
71	struct nouveau_present_vblank *event = priv;
72	uint64_t msc;
73
74	msc = (event->msc & 0xffffffff00000000ULL) | msc_lo;
75	if (msc < event->msc)
76		event->msc += 1ULL << 32;
77
78	present_event_notify(name, ust, msc);
79}
80
81static int
82nouveau_present_vblank_queue(RRCrtcPtr rrcrtc, uint64_t event_id, uint64_t msc)
83{
84	xf86CrtcPtr crtc = rrcrtc->devPrivate;
85	NVPtr pNv = NVPTR(crtc->scrn);
86	drmVBlank args;
87	struct nouveau_present_vblank *event;
88	void *token;
89	int ret;
90
91	event = drmmode_event_queue(crtc->scrn, event_id, sizeof(*event),
92				    nouveau_present_vblank, &token);
93	if (!event)
94		return BadAlloc;
95
96	event->msc = msc;
97
98	args.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT;
99	args.request.type |= drmmode_head(crtc) << DRM_VBLANK_HIGH_CRTC_SHIFT;
100	args.request.sequence = msc;
101	args.request.signal = (unsigned long)token;
102
103	while ((ret = drmWaitVBlank(pNv->dev->fd, &args)) != 0) {
104		if (errno != EBUSY) {
105			xf86DrvMsgVerb(crtc->scrn->scrnIndex, X_WARNING, 4,
106				   "PRESENT: Wait for VBlank failed: %s\n", strerror(errno));
107			drmmode_event_abort(crtc->scrn, event_id, false);
108			return BadAlloc;
109		}
110		ret = drmmode_event_flush(crtc->scrn);
111		if (ret < 0) {
112			xf86DrvMsgVerb(crtc->scrn->scrnIndex, X_WARNING, 4,
113				   "PRESENT: Event flush failed\n");
114			drmmode_event_abort(crtc->scrn, event_id, false);
115			return BadAlloc;
116		}
117	}
118
119	return Success;
120}
121
122static void
123nouveau_present_vblank_abort(RRCrtcPtr rrcrtc, uint64_t event_id, uint64_t msc)
124{
125	xf86CrtcPtr crtc = rrcrtc->devPrivate;
126	drmmode_event_abort(crtc->scrn, event_id, true);
127}
128
129static void
130nouveau_present_flush(WindowPtr window)
131{
132	ScrnInfoPtr scrn = xf86ScreenToScrn(window->drawable.pScreen);
133	NVPtr pNv = NVPTR(scrn);
134	if (pNv->Flush)
135		pNv->Flush(scrn);
136}
137
138struct nouveau_present_flip {
139	uint64_t msc;
140	uint32_t old;
141	int fd;
142};
143
144static Bool
145nouveau_present_flip_check(RRCrtcPtr rrcrtc, WindowPtr window,
146			   PixmapPtr pixmap, Bool sync_flip)
147{
148	ScrnInfoPtr scrn = xf86ScreenToScrn(window->drawable.pScreen);
149	NVPtr pNv = NVPTR(scrn);
150	xf86CrtcPtr crtc = rrcrtc->devPrivate;
151	struct nouveau_pixmap *priv = nouveau_pixmap(pixmap);
152
153	if (!scrn->vtSema || !xf86_crtc_on(crtc) || crtc->rotatedData)
154		return FALSE;
155
156	if (!priv) {
157		/* The pixmap may not have had backing for low-memory GPUs, or
158		 * if we ran out of VRAM. Make sure it's properly backed for
159		 * flipping.
160		 */
161		pNv->exa_force_cp = TRUE;
162		exaMoveInPixmap(pixmap);
163		pNv->exa_force_cp = FALSE;
164		priv = nouveau_pixmap(pixmap);
165	}
166
167	return priv ? TRUE : FALSE;
168}
169
170static void
171nouveau_present_flip(void *priv, uint64_t name, uint64_t ust, uint32_t msc_lo)
172{
173	struct nouveau_present_flip *flip = priv;
174	uint64_t msc;
175
176	msc = (flip->msc & ~0xffffffffULL) | msc_lo;
177	if (msc < flip->msc)
178		msc += 1ULL << 32;
179
180	present_event_notify(name, ust, msc);
181	drmModeRmFB(flip->fd, flip->old);
182}
183
184static Bool
185nouveau_present_flip_exec(ScrnInfoPtr scrn, uint64_t event_id, int sync,
186			  uint64_t target_msc, PixmapPtr pixmap, Bool vsync)
187{
188	struct nouveau_pixmap *priv = nouveau_pixmap(pixmap);
189	NVPtr pNv = NVPTR(scrn);
190	uint32_t next_fb;
191	void *token;
192	int ret;
193
194	ret = drmModeAddFB(pNv->dev->fd, pixmap->drawable.width,
195			   pixmap->drawable.height, pixmap->drawable.depth,
196			   pixmap->drawable.bitsPerPixel, pixmap->devKind,
197			   priv->bo->handle, &next_fb);
198	if (ret == 0) {
199		struct nouveau_present_flip *flip =
200			drmmode_event_queue(scrn, event_id, sizeof(*flip),
201					    nouveau_present_flip, &token);
202		if (flip) {
203			xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn);
204			int last = 0, i;
205
206			drmmode_swap(scrn, next_fb, &flip->old);
207			flip->fd = pNv->dev->fd;
208			flip->msc = target_msc;
209
210			for (i = 0; i < config->num_crtc; i++) {
211				if (xf86_crtc_on(config->crtc[i]))
212					last = i;
213			}
214
215			for (i = 0; i < config->num_crtc; i++) {
216				int type = vsync ? 0 : DRM_MODE_PAGE_FLIP_ASYNC;
217				int crtc = drmmode_crtc(config->crtc[i]);
218				void *user = NULL;
219
220				if (!xf86_crtc_on(config->crtc[i]))
221					continue;
222
223				if (token && ((crtc == sync) || (i == last))) {
224					type |= DRM_MODE_PAGE_FLIP_EVENT;
225					user  = token;
226				}
227
228				ret = drmModePageFlip(pNv->dev->fd, crtc,
229						      next_fb, type, user);
230				if (ret == 0 && user) {
231					token = NULL;
232				}
233			}
234
235			if (token == NULL) {
236				return TRUE;
237			}
238
239			drmmode_swap(scrn, flip->old, &next_fb);
240			drmmode_event_abort(scrn, event_id, false);
241		}
242
243		drmModeRmFB(pNv->dev->fd, next_fb);
244	}
245
246	return FALSE;
247}
248
249static Bool
250nouveau_present_flip_next(RRCrtcPtr rrcrtc, uint64_t event_id,
251			  uint64_t target_msc, PixmapPtr pixmap, Bool vsync)
252{
253	xf86CrtcPtr crtc = rrcrtc->devPrivate;
254	ScrnInfoPtr scrn = crtc->scrn;
255	return nouveau_present_flip_exec(scrn, event_id, drmmode_crtc(crtc),
256					 target_msc, pixmap, vsync);
257}
258
259static void
260nouveau_present_flip_stop(ScreenPtr screen, uint64_t event_id)
261{
262	PixmapPtr pixmap = screen->GetScreenPixmap(screen);
263	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
264	nouveau_present_flip_exec(scrn, event_id, 0, 0, pixmap, TRUE);
265}
266
267void
268nouveau_present_fini(ScreenPtr screen)
269{
270	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
271	NVPtr pNv = NVPTR(scrn);
272	if (pNv->present) {
273		free(pNv->present);
274		pNv->present = NULL;
275	}
276}
277
278Bool
279nouveau_present_init(ScreenPtr screen)
280{
281	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
282	NVPtr pNv = NVPTR(scrn);
283	struct nouveau_present *present;
284	uint64_t value;
285	int ret;
286
287	present = pNv->present = calloc(1, sizeof(*present));
288	if (!present)
289		return FALSE;
290
291	present->info.version = PRESENT_SCREEN_INFO_VERSION;
292	present->info.get_crtc = nouveau_present_crtc;
293	present->info.get_ust_msc = nouveau_present_ust_msc;
294	present->info.queue_vblank = nouveau_present_vblank_queue;
295	present->info.abort_vblank = nouveau_present_vblank_abort;
296	present->info.flush = nouveau_present_flush;
297
298	if (pNv->has_pageflip) {
299#ifdef DRM_CAP_ASYNC_PAGE_FLIP
300		ret = drmGetCap(pNv->dev->fd, DRM_CAP_ASYNC_PAGE_FLIP, &value);
301		if (ret == 0 && value == 1)
302			present->info.capabilities |= PresentCapabilityAsync;
303#endif
304		present->info.check_flip = nouveau_present_flip_check;
305		present->info.flip = nouveau_present_flip_next;
306		present->info.unflip = nouveau_present_flip_stop;
307	}
308
309	return present_screen_init(screen, &present->info);
310}
311#endif
312