vblank.c revision 2c83f951
1/*
2 * Copyright © 2013 Keith Packard
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting documentation, and
8 * that the name of the copyright holders not be used in advertising or
9 * publicity pertaining to distribution of the software without specific,
10 * written prior permission.  The copyright holders make no representations
11 * about the suitability of this software for any purpose.  It is provided "as
12 * is" without express or implied warranty.
13 *
14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20 * OF THIS SOFTWARE.
21 */
22
23/** @file vblank.c
24 *
25 * Support for tracking the DRM's vblank events.
26 */
27
28#ifdef HAVE_DIX_CONFIG_H
29#include "dix-config.h"
30#endif
31
32#include <unistd.h>
33#include <xf86.h>
34#include <xf86Crtc.h>
35#include <poll.h>
36#include "driver.h"
37#include "drmmode_display.h"
38
39/**
40 * Tracking for outstanding events queued to the kernel.
41 *
42 * Each list entry is a struct ms_drm_queue, which has a uint32_t
43 * value generated from drm_seq that identifies the event and a
44 * reference back to the crtc/screen associated with the event.  It's
45 * done this way rather than in the screen because we want to be able
46 * to drain the list of event handlers that should be called at server
47 * regen time, even though we don't close the drm fd and have no way
48 * to actually drain the kernel events.
49 */
50static struct xorg_list ms_drm_queue;
51static uint32_t ms_drm_seq;
52
53static void ms_box_intersect(BoxPtr dest, BoxPtr a, BoxPtr b)
54{
55    dest->x1 = a->x1 > b->x1 ? a->x1 : b->x1;
56    dest->x2 = a->x2 < b->x2 ? a->x2 : b->x2;
57    if (dest->x1 >= dest->x2) {
58        dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0;
59        return;
60    }
61
62    dest->y1 = a->y1 > b->y1 ? a->y1 : b->y1;
63    dest->y2 = a->y2 < b->y2 ? a->y2 : b->y2;
64    if (dest->y1 >= dest->y2)
65        dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0;
66}
67
68static void ms_crtc_box(xf86CrtcPtr crtc, BoxPtr crtc_box)
69{
70    if (crtc->enabled) {
71        crtc_box->x1 = crtc->x;
72        crtc_box->x2 =
73            crtc->x + xf86ModeWidth(&crtc->mode, crtc->rotation);
74        crtc_box->y1 = crtc->y;
75        crtc_box->y2 =
76            crtc->y + xf86ModeHeight(&crtc->mode, crtc->rotation);
77    } else
78        crtc_box->x1 = crtc_box->x2 = crtc_box->y1 = crtc_box->y2 = 0;
79}
80
81static int ms_box_area(BoxPtr box)
82{
83    return (int)(box->x2 - box->x1) * (int)(box->y2 - box->y1);
84}
85
86Bool
87ms_crtc_on(xf86CrtcPtr crtc)
88{
89    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
90
91    return crtc->enabled && drmmode_crtc->dpms_mode == DPMSModeOn;
92}
93
94/*
95 * Return the crtc covering 'box'. If two crtcs cover a portion of
96 * 'box', then prefer 'desired'. If 'desired' is NULL, then prefer the crtc
97 * with greater coverage
98 */
99
100xf86CrtcPtr
101ms_covering_crtc(ScrnInfoPtr scrn,
102                 BoxPtr box, xf86CrtcPtr desired, BoxPtr crtc_box_ret)
103{
104    xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn);
105    xf86CrtcPtr crtc, best_crtc;
106    int coverage, best_coverage;
107    int c;
108    BoxRec crtc_box, cover_box;
109
110    best_crtc = NULL;
111    best_coverage = 0;
112    crtc_box_ret->x1 = 0;
113    crtc_box_ret->x2 = 0;
114    crtc_box_ret->y1 = 0;
115    crtc_box_ret->y2 = 0;
116    for (c = 0; c < xf86_config->num_crtc; c++) {
117        crtc = xf86_config->crtc[c];
118
119        /* If the CRTC is off, treat it as not covering */
120        if (!ms_crtc_on(crtc))
121            continue;
122
123        ms_crtc_box(crtc, &crtc_box);
124        ms_box_intersect(&cover_box, &crtc_box, box);
125        coverage = ms_box_area(&cover_box);
126        if (coverage && crtc == desired) {
127            *crtc_box_ret = crtc_box;
128            return crtc;
129        }
130        if (coverage > best_coverage) {
131            *crtc_box_ret = crtc_box;
132            best_crtc = crtc;
133            best_coverage = coverage;
134        }
135    }
136    return best_crtc;
137}
138
139xf86CrtcPtr
140ms_dri2_crtc_covering_drawable(DrawablePtr pDraw)
141{
142    ScreenPtr pScreen = pDraw->pScreen;
143    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
144    BoxRec box, crtcbox;
145
146    box.x1 = pDraw->x;
147    box.y1 = pDraw->y;
148    box.x2 = box.x1 + pDraw->width;
149    box.y2 = box.y1 + pDraw->height;
150
151    return ms_covering_crtc(pScrn, &box, NULL, &crtcbox);
152}
153
154static Bool
155ms_get_kernel_ust_msc(xf86CrtcPtr crtc,
156                      uint32_t *msc, uint64_t *ust)
157{
158    ScreenPtr screen = crtc->randr_crtc->pScreen;
159    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
160    modesettingPtr ms = modesettingPTR(scrn);
161    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
162    drmVBlank vbl;
163    int ret;
164
165    /* Get current count */
166    vbl.request.type = DRM_VBLANK_RELATIVE | drmmode_crtc->vblank_pipe;
167    vbl.request.sequence = 0;
168    vbl.request.signal = 0;
169    ret = drmWaitVBlank(ms->fd, &vbl);
170    if (ret) {
171        *msc = 0;
172        *ust = 0;
173        return FALSE;
174    } else {
175        *msc = vbl.reply.sequence;
176        *ust = (CARD64) vbl.reply.tval_sec * 1000000 + vbl.reply.tval_usec;
177        return TRUE;
178    }
179}
180
181/**
182 * Convert a 32-bit kernel MSC sequence number to a 64-bit local sequence
183 * number, adding in the vblank_offset and high 32 bits, and dealing
184 * with 64-bit wrapping
185 */
186uint64_t
187ms_kernel_msc_to_crtc_msc(xf86CrtcPtr crtc, uint32_t sequence)
188{
189    drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private;
190    sequence += drmmode_crtc->vblank_offset;
191
192    if ((int32_t) (sequence - drmmode_crtc->msc_prev) < -0x40000000)
193        drmmode_crtc->msc_high += 0x100000000L;
194    drmmode_crtc->msc_prev = sequence;
195    return drmmode_crtc->msc_high + sequence;
196}
197
198int
199ms_get_crtc_ust_msc(xf86CrtcPtr crtc, uint64_t *ust, uint64_t *msc)
200{
201    uint32_t kernel_msc;
202
203    if (!ms_get_kernel_ust_msc(crtc, &kernel_msc, ust))
204        return BadMatch;
205    *msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_msc);
206
207    return Success;
208}
209
210#define MAX_VBLANK_OFFSET       1000
211
212/**
213 * Convert a 64-bit adjusted MSC value into a 32-bit kernel sequence number,
214 * removing the high 32 bits and subtracting out the vblank_offset term.
215 *
216 * This also updates the vblank_offset when it notices that the value should
217 * change.
218 */
219uint32_t
220ms_crtc_msc_to_kernel_msc(xf86CrtcPtr crtc, uint64_t expect)
221{
222    drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private;
223    uint64_t msc;
224    uint64_t ust;
225    int64_t diff;
226
227    if (ms_get_crtc_ust_msc(crtc, &ust, &msc) == Success) {
228        diff = expect - msc;
229
230        /* We're way off here, assume that the kernel has lost its mind
231         * and smack the vblank back to something sensible
232         */
233        if (diff < -MAX_VBLANK_OFFSET || MAX_VBLANK_OFFSET < diff) {
234            drmmode_crtc->vblank_offset += (int32_t) diff;
235            if (drmmode_crtc->vblank_offset > -MAX_VBLANK_OFFSET &&
236                drmmode_crtc->vblank_offset < MAX_VBLANK_OFFSET)
237                drmmode_crtc->vblank_offset = 0;
238        }
239    }
240    return (uint32_t) (expect - drmmode_crtc->vblank_offset);
241}
242
243/**
244 * Check for pending DRM events and process them.
245 */
246static void
247ms_drm_wakeup_handler(void *data, int err, void *mask)
248{
249    ScreenPtr screen;
250    ScrnInfoPtr scrn;
251    modesettingPtr ms;
252    fd_set *read_mask;
253
254    if (data == NULL || err < 0)
255        return;
256
257    screen = data;
258    scrn = xf86ScreenToScrn(screen);
259    ms = modesettingPTR(scrn);
260    read_mask = mask;
261
262    if (FD_ISSET(ms->fd, read_mask))
263        drmHandleEvent(ms->fd, &ms->event_context);
264}
265
266/*
267 * Enqueue a potential drm response; when the associated response
268 * appears, we've got data to pass to the handler from here
269 */
270uint32_t
271ms_drm_queue_alloc(xf86CrtcPtr crtc,
272                   void *data,
273                   ms_drm_handler_proc handler,
274                   ms_drm_abort_proc abort)
275{
276    ScreenPtr screen = crtc->randr_crtc->pScreen;
277    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
278    struct ms_drm_queue *q;
279
280    q = calloc(1, sizeof(struct ms_drm_queue));
281
282    if (!q)
283        return 0;
284    if (!ms_drm_seq)
285        ++ms_drm_seq;
286    q->seq = ms_drm_seq++;
287    q->scrn = scrn;
288    q->crtc = crtc;
289    q->data = data;
290    q->handler = handler;
291    q->abort = abort;
292
293    xorg_list_add(&q->list, &ms_drm_queue);
294
295    return q->seq;
296}
297
298/**
299 * Abort one queued DRM entry, removing it
300 * from the list, calling the abort function and
301 * freeing the memory
302 */
303static void
304ms_drm_abort_one(struct ms_drm_queue *q)
305{
306        xorg_list_del(&q->list);
307        q->abort(q->data);
308        free(q);
309}
310
311/**
312 * Abort all queued entries on a specific scrn, used
313 * when resetting the X server
314 */
315static void
316ms_drm_abort_scrn(ScrnInfoPtr scrn)
317{
318    struct ms_drm_queue *q, *tmp;
319
320    xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
321        if (q->scrn == scrn)
322            ms_drm_abort_one(q);
323    }
324}
325
326/**
327 * Abort by drm queue sequence number.
328 */
329void
330ms_drm_abort_seq(ScrnInfoPtr scrn, uint32_t seq)
331{
332    struct ms_drm_queue *q, *tmp;
333
334    xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
335        if (q->seq == seq) {
336            ms_drm_abort_one(q);
337            break;
338        }
339    }
340}
341
342/*
343 * Externally usable abort function that uses a callback to match a single
344 * queued entry to abort
345 */
346void
347ms_drm_abort(ScrnInfoPtr scrn, Bool (*match)(void *data, void *match_data),
348             void *match_data)
349{
350    struct ms_drm_queue *q;
351
352    xorg_list_for_each_entry(q, &ms_drm_queue, list) {
353        if (match(q->data, match_data)) {
354            ms_drm_abort_one(q);
355            break;
356        }
357    }
358}
359
360/*
361 * General DRM kernel handler. Looks for the matching sequence number in the
362 * drm event queue and calls the handler for it.
363 */
364static void
365ms_drm_handler(int fd, uint32_t frame, uint32_t sec, uint32_t usec,
366               void *user_ptr)
367{
368    struct ms_drm_queue *q, *tmp;
369    uint32_t user_data = (uint32_t) (intptr_t) user_ptr;
370
371    xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
372        if (q->seq == user_data) {
373            uint64_t msc;
374
375            msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame);
376            xorg_list_del(&q->list);
377            q->handler(msc, (uint64_t) sec * 1000000 + usec, q->data);
378            free(q);
379            break;
380        }
381    }
382}
383
384Bool
385ms_vblank_screen_init(ScreenPtr screen)
386{
387    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
388    modesettingPtr ms = modesettingPTR(scrn);
389    modesettingEntPtr ms_ent = ms_ent_priv(scrn);
390    xorg_list_init(&ms_drm_queue);
391
392    ms->event_context.version = DRM_EVENT_CONTEXT_VERSION;
393    ms->event_context.vblank_handler = ms_drm_handler;
394    ms->event_context.page_flip_handler = ms_drm_handler;
395
396    /* We need to re-register the DRM fd for the synchronisation
397     * feedback on every server generation, so perform the
398     * registration within ScreenInit and not PreInit.
399     */
400    if (ms_ent->fd_wakeup_registered != serverGeneration) {
401        AddGeneralSocket(ms->fd);
402        RegisterBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA,
403                                       ms_drm_wakeup_handler, screen);
404        ms_ent->fd_wakeup_registered = serverGeneration;
405        ms_ent->fd_wakeup_ref = 1;
406    } else
407        ms_ent->fd_wakeup_ref++;
408
409    return TRUE;
410}
411
412void
413ms_vblank_close_screen(ScreenPtr screen)
414{
415    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
416    modesettingPtr ms = modesettingPTR(scrn);
417    modesettingEntPtr ms_ent = ms_ent_priv(scrn);
418
419    ms_drm_abort_scrn(scrn);
420
421    if (ms_ent->fd_wakeup_registered == serverGeneration &&
422        !--ms_ent->fd_wakeup_ref) {
423        RemoveBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA,
424                                     ms_drm_wakeup_handler, screen);
425        RemoveGeneralSocket(ms->fd);
426    }
427}
428