summaryrefslogtreecommitdiff
path: root/hw/xfree86/drivers/modesetting/vblank.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/xfree86/drivers/modesetting/vblank.c')
-rw-r--r--hw/xfree86/drivers/modesetting/vblank.c382
1 files changed, 382 insertions, 0 deletions
diff --git a/hw/xfree86/drivers/modesetting/vblank.c b/hw/xfree86/drivers/modesetting/vblank.c
new file mode 100644
index 000000000..35cadb222
--- /dev/null
+++ b/hw/xfree86/drivers/modesetting/vblank.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright © 2013 Keith Packard
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+/** @file vblank.c
+ *
+ * Support for tracking the DRM's vblank events.
+ */
+
+#ifdef HAVE_DIX_CONFIG_H
+#include "dix-config.h"
+#endif
+
+#include <unistd.h>
+#include <xf86.h>
+#include <xf86Crtc.h>
+#include <poll.h>
+#include "driver.h"
+#include "drmmode_display.h"
+
+/**
+ * Tracking for outstanding events queued to the kernel.
+ *
+ * Each list entry is a struct ms_drm_queue, which has a uint32_t
+ * value generated from drm_seq that identifies the event and a
+ * reference back to the crtc/screen associated with the event. It's
+ * done this way rather than in the screen because we want to be able
+ * to drain the list of event handlers that should be called at server
+ * regen time, even though we don't close the drm fd and have no way
+ * to actually drain the kernel events.
+ */
+static struct xorg_list ms_drm_queue;
+static uint32_t ms_drm_seq;
+
+struct ms_pageflip {
+ ScreenPtr screen;
+ Bool crtc_for_msc_ust;
+};
+
+static void ms_box_intersect(BoxPtr dest, BoxPtr a, BoxPtr b)
+{
+ dest->x1 = a->x1 > b->x1 ? a->x1 : b->x1;
+ dest->x2 = a->x2 < b->x2 ? a->x2 : b->x2;
+ if (dest->x1 >= dest->x2) {
+ dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0;
+ return;
+ }
+
+ dest->y1 = a->y1 > b->y1 ? a->y1 : b->y1;
+ dest->y2 = a->y2 < b->y2 ? a->y2 : b->y2;
+ if (dest->y1 >= dest->y2)
+ dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0;
+}
+
+static void ms_crtc_box(xf86CrtcPtr crtc, BoxPtr crtc_box)
+{
+ if (crtc->enabled) {
+ crtc_box->x1 = crtc->x;
+ crtc_box->x2 =
+ crtc->x + xf86ModeWidth(&crtc->mode, crtc->rotation);
+ crtc_box->y1 = crtc->y;
+ crtc_box->y2 =
+ crtc->y + xf86ModeHeight(&crtc->mode, crtc->rotation);
+ } else
+ crtc_box->x1 = crtc_box->x2 = crtc_box->y1 = crtc_box->y2 = 0;
+}
+
+static int ms_box_area(BoxPtr box)
+{
+ return (int)(box->x2 - box->x1) * (int)(box->y2 - box->y1);
+}
+
+/*
+ * Return the crtc covering 'box'. If two crtcs cover a portion of
+ * 'box', then prefer 'desired'. If 'desired' is NULL, then prefer the crtc
+ * with greater coverage
+ */
+
+xf86CrtcPtr
+ms_covering_crtc(ScrnInfoPtr scrn,
+ BoxPtr box, xf86CrtcPtr desired, BoxPtr crtc_box_ret)
+{
+ xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn);
+ xf86CrtcPtr crtc, best_crtc;
+ int coverage, best_coverage;
+ int c;
+ BoxRec crtc_box, cover_box;
+
+ best_crtc = NULL;
+ best_coverage = 0;
+ crtc_box_ret->x1 = 0;
+ crtc_box_ret->x2 = 0;
+ crtc_box_ret->y1 = 0;
+ crtc_box_ret->y2 = 0;
+ for (c = 0; c < xf86_config->num_crtc; c++) {
+ crtc = xf86_config->crtc[c];
+
+ /* If the CRTC is off, treat it as not covering */
+ if (!crtc->enabled)
+ continue;
+
+ ms_crtc_box(crtc, &crtc_box);
+ ms_box_intersect(&cover_box, &crtc_box, box);
+ coverage = ms_box_area(&cover_box);
+ if (coverage && crtc == desired) {
+ *crtc_box_ret = crtc_box;
+ return crtc;
+ }
+ if (coverage > best_coverage) {
+ *crtc_box_ret = crtc_box;
+ best_crtc = crtc;
+ best_coverage = coverage;
+ }
+ }
+ return best_crtc;
+}
+
+xf86CrtcPtr
+ms_dri2_crtc_covering_drawable(DrawablePtr pDraw)
+{
+ ScreenPtr pScreen = pDraw->pScreen;
+ ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
+ BoxRec box, crtcbox;
+ xf86CrtcPtr crtc;
+
+ box.x1 = pDraw->x;
+ box.y1 = pDraw->y;
+ box.x2 = box.x1 + pDraw->width;
+ box.y2 = box.y1 + pDraw->height;
+
+ crtc = ms_covering_crtc(pScrn, &box, NULL, &crtcbox);
+
+ /* Make sure the CRTC is valid and this is the real front buffer */
+ if (crtc != NULL && !crtc->rotatedData)
+ return crtc;
+
+ return NULL;
+}
+
+static Bool
+ms_get_kernel_ust_msc(xf86CrtcPtr crtc,
+ uint32_t *msc, uint64_t *ust)
+{
+ ScreenPtr screen = crtc->randr_crtc->pScreen;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+ drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+ drmVBlank vbl;
+ int ret;
+
+ /* Get current count */
+ vbl.request.type = DRM_VBLANK_RELATIVE | drmmode_crtc->vblank_pipe;
+ vbl.request.sequence = 0;
+ vbl.request.signal = 0;
+ ret = drmWaitVBlank(ms->fd, &vbl);
+ if (ret) {
+ *msc = 0;
+ *ust = 0;
+ return FALSE;
+ } else {
+ *msc = vbl.reply.sequence;
+ *ust = (CARD64) vbl.reply.tval_sec * 1000000 + vbl.reply.tval_usec;
+ return TRUE;
+ }
+}
+
+/**
+ * Convert a 32-bit kernel MSC sequence number to a 64-bit local sequence
+ * number, adding in the vblank_offset and high 32 bits, and dealing
+ * with 64-bit wrapping
+ */
+uint64_t
+ms_kernel_msc_to_crtc_msc(xf86CrtcPtr crtc, uint32_t sequence)
+{
+ drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private;
+ sequence += drmmode_crtc->vblank_offset;
+
+ if ((int32_t) (sequence - drmmode_crtc->msc_prev) < -0x40000000)
+ drmmode_crtc->msc_high += 0x100000000L;
+ drmmode_crtc->msc_prev = sequence;
+ return drmmode_crtc->msc_high + sequence;
+}
+
+int
+ms_get_crtc_ust_msc(xf86CrtcPtr crtc, CARD64 *ust, CARD64 *msc)
+{
+ uint32_t kernel_msc;
+
+ if (!ms_get_kernel_ust_msc(crtc, &kernel_msc, ust))
+ return BadMatch;
+ *msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_msc);
+
+ return Success;
+}
+
+#define MAX_VBLANK_OFFSET 1000
+
+/**
+ * Convert a 64-bit adjusted MSC value into a 32-bit kernel sequence number,
+ * removing the high 32 bits and subtracting out the vblank_offset term.
+ *
+ * This also updates the vblank_offset when it notices that the value should
+ * change.
+ */
+uint32_t
+ms_crtc_msc_to_kernel_msc(xf86CrtcPtr crtc, uint64_t expect)
+{
+ drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private;
+ uint64_t msc;
+ uint64_t ust;
+ int64_t diff;
+
+ ms_get_crtc_ust_msc(crtc, &ust, &msc);
+ diff = expect - msc;
+
+ /* We're way off here, assume that the kernel has lost its mind
+ * and smack the vblank back to something sensible
+ */
+ if (diff < -MAX_VBLANK_OFFSET || MAX_VBLANK_OFFSET < diff) {
+ drmmode_crtc->vblank_offset += (int32_t) diff;
+ if (drmmode_crtc->vblank_offset > -MAX_VBLANK_OFFSET &&
+ drmmode_crtc->vblank_offset < MAX_VBLANK_OFFSET)
+ drmmode_crtc->vblank_offset = 0;
+ }
+ return (uint32_t) (expect - drmmode_crtc->vblank_offset);
+}
+
+/**
+ * Check for pending DRM events and process them.
+ */
+static void
+ms_drm_wakeup_handler(void *data, int err, void *mask)
+{
+ ScreenPtr screen = data;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+ fd_set *read_mask = mask;
+
+ if (data == NULL || err < 0)
+ return;
+
+ if (FD_ISSET(ms->fd, read_mask))
+ drmHandleEvent(ms->fd, &ms->event_context);
+}
+
+/*
+ * Enqueue a potential drm response; when the associated response
+ * appears, we've got data to pass to the handler from here
+ */
+uint32_t
+ms_drm_queue_alloc(xf86CrtcPtr crtc,
+ void *data,
+ ms_drm_handler_proc handler,
+ ms_drm_abort_proc abort)
+{
+ ScreenPtr screen = crtc->randr_crtc->pScreen;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ struct ms_drm_queue *q;
+
+ q = calloc(1, sizeof(struct ms_drm_queue));
+
+ if (!q)
+ return 0;
+ if (!ms_drm_seq)
+ ++ms_drm_seq;
+ q->seq = ms_drm_seq++;
+ q->scrn = scrn;
+ q->crtc = crtc;
+ q->data = data;
+ q->handler = handler;
+ q->abort = abort;
+
+ xorg_list_add(&q->list, &ms_drm_queue);
+
+ return q->seq;
+}
+
+/**
+ * Abort one queued DRM entry, removing it
+ * from the list, calling the abort function and
+ * freeing the memory
+ */
+static void
+ms_drm_abort_one(struct ms_drm_queue *q)
+{
+ xorg_list_del(&q->list);
+ q->abort(q->data);
+ free(q);
+}
+
+/**
+ * Abort all queued entries on a specific scrn, used
+ * when resetting the X server
+ */
+static void
+ms_drm_abort_scrn(ScrnInfoPtr scrn)
+{
+ struct ms_drm_queue *q, *tmp;
+
+ xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
+ if (q->scrn == scrn)
+ ms_drm_abort_one(q);
+ }
+}
+
+/*
+ * General DRM kernel handler. Looks for the matching sequence number in the
+ * drm event queue and calls the handler for it.
+ */
+static void
+ms_drm_handler(int fd, uint32_t frame, uint32_t sec, uint32_t usec,
+ void *user_ptr)
+{
+ struct ms_drm_queue *q, *tmp;
+ uint32_t user_data = (uint32_t) (intptr_t) user_ptr;
+
+ xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
+ if (q->seq == user_data) {
+ uint64_t msc;
+
+ msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame);
+ xorg_list_del(&q->list);
+ q->handler(msc, (uint64_t) sec * 1000000 + usec, q->data);
+ free(q);
+ break;
+ }
+ }
+}
+
+Bool
+ms_vblank_screen_init(ScreenPtr screen)
+{
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+
+ xorg_list_init(&ms_drm_queue);
+
+ ms->event_context.version = DRM_EVENT_CONTEXT_VERSION;
+ ms->event_context.vblank_handler = ms_drm_handler;
+ ms->event_context.page_flip_handler = ms_drm_handler;
+
+ /* We need to re-register the DRM fd for the synchronisation
+ * feedback on every server generation, so perform the
+ * registration within ScreenInit and not PreInit.
+ */
+ AddGeneralSocket(ms->fd);
+ RegisterBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA,
+ ms_drm_wakeup_handler, screen);
+
+ return TRUE;
+}
+
+void
+ms_vblank_close_screen(ScreenPtr screen)
+{
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+
+ ms_drm_abort_scrn(scrn);
+
+ RemoveBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA,
+ ms_drm_wakeup_handler, screen);
+ RemoveGeneralSocket(ms->fd);
+}