summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Anholt <eric@anholt.net>2013-12-30 17:23:38 -0800
committerEric Anholt <eric@anholt.net>2014-10-27 13:16:43 -0700
commitcac4b064f9f66435430f61568c6a516c54bf3c40 (patch)
treebe3ccd7265942a4717a1b039c1ed5381ad7fda85
parent7064b00d478646cb428e427df2d3f91d7022c582 (diff)
modesetting: Add support for DRI2 with glamor.modesetting-dri2-no-pageflip
This is derived from the intel driver DRI2 code, with swapchain and pageflipping dropped, functions renamed, and vblank event management shared code moved to a vblank.c for reuse by Present. This allows AIGLX to load, which means that you get appropriate visuals exposed in GL, along with many extensions under direct-rendering that require presence in GLX (which aren't supported in glxdriswrast.c). v2: Drop unused header includes in pageflip.c, wrap in #ifdef GLAMOR. Drop triple-buffering, which was totally broken in practice (I'll try to fix this later). Fix up some style nits. Document the general flow of pageflipping and why, rename the DRI2 frame event type enums to reflect what they're for, and handle them in a single switch statement so you can understand the state machine more easily. v3: Drop pageflipping entirely -- it's unstable on my Intel laptop (not that the normal 2D driver is stable with pageflipping for me), and I won't get it fixed before the merge window. It now passes all of the OML_sync_control tests from Jamey and Theo (except for occasional warns in timing -fullscreen -divisor 2). v4: Fix doxygen at the top of vblank.c Signed-off-by: Eric Anholt <eric@anholt.net> Reviewed-by: Adam Jackson <ajax@redhat.com>
-rw-r--r--hw/xfree86/drivers/modesetting/Makefile.am6
-rw-r--r--hw/xfree86/drivers/modesetting/dri2.c863
-rw-r--r--hw/xfree86/drivers/modesetting/driver.c27
-rw-r--r--hw/xfree86/drivers/modesetting/driver.h44
-rw-r--r--hw/xfree86/drivers/modesetting/drmmode_display.c17
-rw-r--r--hw/xfree86/drivers/modesetting/drmmode_display.h28
-rw-r--r--hw/xfree86/drivers/modesetting/vblank.c382
7 files changed, 1364 insertions, 3 deletions
diff --git a/hw/xfree86/drivers/modesetting/Makefile.am b/hw/xfree86/drivers/modesetting/Makefile.am
index e6834e283..5b08600c1 100644
--- a/hw/xfree86/drivers/modesetting/Makefile.am
+++ b/hw/xfree86/drivers/modesetting/Makefile.am
@@ -30,6 +30,7 @@ AM_CPPFLAGS = \
$(XORG_INCS) \
-I$(top_srcdir)/glamor \
-I$(srcdir)/../../ddc \
+ -I$(srcdir)/../../dri2 \
-I$(srcdir)/../../i2c \
-I$(srcdir)/../../modes \
-I$(srcdir)/../../parser \
@@ -42,10 +43,13 @@ modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS)
modesetting_drv_ladir = @moduledir@/drivers
modesetting_drv_la_SOURCES = \
+ dri2.c \
driver.c \
driver.h \
drmmode_display.c \
- drmmode_display.h
+ drmmode_display.h \
+ vblank.c \
+ $(NULL)
drivermandir = $(DRIVER_MAN_DIR)
driverman_PRE = modesetting.man
diff --git a/hw/xfree86/drivers/modesetting/dri2.c b/hw/xfree86/drivers/modesetting/dri2.c
new file mode 100644
index 000000000..6c88060b0
--- /dev/null
+++ b/hw/xfree86/drivers/modesetting/dri2.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ * Copyright © 2014 Broadcom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/**
+ * @file dri2.c
+ *
+ * Implements generic support for DRI2 on KMS, using glamor pixmaps
+ * for color buffer management (no support for other aux buffers), and
+ * the DRM vblank ioctls.
+ *
+ * This doesn't implement pageflipping yet.
+ */
+
+#ifdef HAVE_DIX_CONFIG_H
+#include "dix-config.h"
+#endif
+
+#include <time.h>
+#include "list.h"
+#include "xf86.h"
+#include "driver.h"
+#include "dri2.h"
+
+#ifdef GLAMOR
+#define GLAMOR_FOR_XORG 1
+#include "glamor.h"
+
+enum ms_dri2_frame_event_type {
+ MS_DRI2_QUEUE_SWAP,
+ MS_DRI2_WAIT_MSC,
+};
+
+typedef struct ms_dri2_frame_event {
+ ScreenPtr screen;
+
+ DrawablePtr drawable;
+ ClientPtr client;
+ enum ms_dri2_frame_event_type type;
+ int frame;
+ xf86CrtcPtr crtc;
+
+ struct xorg_list drawable_resource, client_resource;
+
+ /* for swaps & flips only */
+ DRI2SwapEventPtr event_complete;
+ void *event_data;
+ DRI2BufferPtr front;
+ DRI2BufferPtr back;
+} ms_dri2_frame_event_rec, *ms_dri2_frame_event_ptr;
+
+typedef struct {
+ int refcnt;
+ PixmapPtr pixmap;
+} ms_dri2_buffer_private_rec, *ms_dri2_buffer_private_ptr;
+
+static DevPrivateKeyRec ms_dri2_client_key;
+static RESTYPE frame_event_client_type, frame_event_drawable_type;
+static int ms_dri2_server_generation;
+
+struct ms_dri2_resource {
+ XID id;
+ RESTYPE type;
+ struct xorg_list list;
+};
+
+static struct ms_dri2_resource *
+ms_get_resource(XID id, RESTYPE type)
+{
+ struct ms_dri2_resource *resource;
+ void *ptr;
+
+ ptr = NULL;
+ dixLookupResourceByType(&ptr, id, type, NULL, DixWriteAccess);
+ if (ptr)
+ return ptr;
+
+ resource = malloc(sizeof(*resource));
+ if (resource == NULL)
+ return NULL;
+
+ if (!AddResource(id, type, resource)) {
+ free(resource);
+ return NULL;
+ }
+
+ resource->id = id;
+ resource->type = type;
+ xorg_list_init(&resource->list);
+ return resource;
+}
+
+static inline PixmapPtr
+get_drawable_pixmap(DrawablePtr drawable)
+{
+ ScreenPtr screen = drawable->pScreen;
+
+ if (drawable->type == DRAWABLE_PIXMAP)
+ return (PixmapPtr) drawable;
+ else
+ return screen->GetWindowPixmap((WindowPtr) drawable);
+}
+
+static PixmapPtr
+get_front_buffer(DrawablePtr drawable)
+{
+ PixmapPtr pixmap;
+
+ pixmap = get_drawable_pixmap(drawable);
+ pixmap->refcnt++;
+
+ return pixmap;
+}
+
+static DRI2Buffer2Ptr
+ms_dri2_create_buffer(DrawablePtr drawable, unsigned int attachment,
+ unsigned int format)
+{
+ ScreenPtr screen = drawable->pScreen;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ DRI2Buffer2Ptr buffer;
+ PixmapPtr pixmap;
+ uint32_t size;
+ uint16_t pitch;
+ ms_dri2_buffer_private_ptr private;
+
+ buffer = calloc(1, sizeof *buffer);
+ if (buffer == NULL)
+ return NULL;
+
+ private = calloc(1, sizeof(*private));
+ if (private == NULL) {
+ free(buffer);
+ return NULL;
+ }
+
+ pixmap = NULL;
+ if (attachment == DRI2BufferFrontLeft)
+ pixmap = get_front_buffer(drawable);
+
+ if (pixmap == NULL) {
+ int pixmap_width = drawable->width;
+ int pixmap_height = drawable->height;
+ int pixmap_cpp = (format != 0) ? format : drawable->depth;
+
+ /* Assume that non-color-buffers require special
+ * device-specific handling. Mesa currently makes no requests
+ * for non-color aux buffers.
+ */
+ switch (attachment) {
+ case DRI2BufferAccum:
+ case DRI2BufferBackLeft:
+ case DRI2BufferBackRight:
+ case DRI2BufferFakeFrontLeft:
+ case DRI2BufferFakeFrontRight:
+ case DRI2BufferFrontLeft:
+ case DRI2BufferFrontRight:
+ break;
+
+ case DRI2BufferStencil:
+ case DRI2BufferDepth:
+ case DRI2BufferDepthStencil:
+ case DRI2BufferHiz:
+ default:
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "Request for DRI2 buffer attachment %d unsupported\n",
+ attachment);
+ free(private);
+ free(buffer);
+ return NULL;
+ }
+
+ pixmap = screen->CreatePixmap(screen,
+ pixmap_width,
+ pixmap_height,
+ pixmap_cpp,
+ 0);
+ if (pixmap == NULL) {
+ if (pixmap)
+ screen->DestroyPixmap(pixmap);
+ free(private);
+ free(buffer);
+ return NULL;
+ }
+ }
+
+ buffer->attachment = attachment;
+ buffer->cpp = pixmap->drawable.bitsPerPixel / 8;
+ buffer->format = format;
+ /* The buffer's flags field is unused by the client drivers in
+ * Mesa currently.
+ */
+ buffer->flags = 0;
+
+ buffer->name = glamor_name_from_pixmap(pixmap, &pitch, &size);
+ buffer->pitch = pitch;
+ if (buffer->name == -1) {
+ xf86DrvMsg(scrn->scrnIndex, X_ERROR,
+ "Failed to get DRI2 name for pixmap\n");
+ screen->DestroyPixmap(pixmap);
+ free(private);
+ free(buffer);
+ return NULL;
+ }
+
+ buffer->driverPrivate = private;
+ private->refcnt = 1;
+ private->pixmap = pixmap;
+
+ return buffer;
+}
+
+static void
+ms_dri2_reference_buffer(DRI2Buffer2Ptr buffer)
+{
+ if (buffer) {
+ ms_dri2_buffer_private_ptr private = buffer->driverPrivate;
+ private->refcnt++;
+ }
+}
+
+static void ms_dri2_destroy_buffer(DrawablePtr drawable, DRI2Buffer2Ptr buffer)
+{
+ if (!buffer)
+ return;
+
+ if (buffer->driverPrivate) {
+ ms_dri2_buffer_private_ptr private = buffer->driverPrivate;
+ if (--private->refcnt == 0) {
+ ScreenPtr screen = private->pixmap->drawable.pScreen;
+ screen->DestroyPixmap(private->pixmap);
+ free(private);
+ free(buffer);
+ }
+ } else {
+ free(buffer);
+ }
+}
+
+static void
+ms_dri2_copy_region(DrawablePtr drawable, RegionPtr pRegion,
+ DRI2BufferPtr destBuffer, DRI2BufferPtr sourceBuffer)
+{
+ ms_dri2_buffer_private_ptr src_priv = sourceBuffer->driverPrivate;
+ ms_dri2_buffer_private_ptr dst_priv = destBuffer->driverPrivate;
+ PixmapPtr src_pixmap = src_priv->pixmap;
+ PixmapPtr dst_pixmap = dst_priv->pixmap;
+ ScreenPtr screen = drawable->pScreen;
+ DrawablePtr src = (sourceBuffer->attachment == DRI2BufferFrontLeft)
+ ? drawable : &src_pixmap->drawable;
+ DrawablePtr dst = (destBuffer->attachment == DRI2BufferFrontLeft)
+ ? drawable : &dst_pixmap->drawable;
+ RegionPtr pCopyClip;
+ GCPtr gc;
+
+ gc = GetScratchGC(dst->depth, screen);
+ if (!gc)
+ return;
+
+ pCopyClip = REGION_CREATE(screen, NULL, 0);
+ REGION_COPY(screen, pCopyClip, pRegion);
+ (*gc->funcs->ChangeClip) (gc, CT_REGION, pCopyClip, 0);
+ ValidateGC(dst, gc);
+
+ /* It's important that this copy gets submitted before the direct
+ * rendering client submits rendering for the next frame, but we
+ * don't actually need to submit right now. The client will wait
+ * for the DRI2CopyRegion reply or the swap buffer event before
+ * rendering, and we'll hit the flush callback chain before those
+ * messages are sent. We submit our batch buffers from the flush
+ * callback chain so we know that will happen before the client
+ * tries to render again.
+ */
+ gc->ops->CopyArea(src, dst, gc,
+ 0, 0,
+ drawable->width, drawable->height,
+ 0, 0);
+
+ FreeScratchGC(gc);
+}
+
+static uint64_t
+gettime_us(void)
+{
+ struct timespec tv;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &tv))
+ return 0;
+
+ return (uint64_t)tv.tv_sec * 1000000 + tv.tv_nsec / 1000;
+}
+
+/**
+ * Get current frame count and frame count timestamp, based on drawable's
+ * crtc.
+ */
+static int
+ms_dri2_get_msc(DrawablePtr draw, CARD64 *ust, CARD64 *msc)
+{
+ int ret;
+ xf86CrtcPtr crtc = ms_dri2_crtc_covering_drawable(draw);
+
+ /* Drawable not displayed, make up a *monotonic* value */
+ if (crtc == NULL) {
+ *ust = gettime_us();
+ *msc = 0;
+ return TRUE;
+ }
+
+ ret = ms_get_crtc_ust_msc(crtc, ust, msc);
+
+ if (ret)
+ return FALSE;
+
+ return TRUE;
+}
+
+static XID
+get_client_id(ClientPtr client)
+{
+ XID *ptr = dixGetPrivateAddr(&client->devPrivates, &ms_dri2_client_key);
+ if (*ptr == 0)
+ *ptr = FakeClientID(client->index);
+ return *ptr;
+}
+
+/*
+ * Hook this frame event into the server resource
+ * database so we can clean it up if the drawable or
+ * client exits while the swap is pending
+ */
+static Bool
+ms_dri2_add_frame_event(ms_dri2_frame_event_ptr info)
+{
+ struct ms_dri2_resource *resource;
+
+ resource = ms_get_resource(get_client_id(info->client),
+ frame_event_client_type);
+ if (resource == NULL)
+ return FALSE;
+
+ xorg_list_add(&info->client_resource, &resource->list);
+
+ resource = ms_get_resource(info->drawable->id, frame_event_drawable_type);
+ if (resource == NULL) {
+ xorg_list_del(&info->client_resource);
+ return FALSE;
+ }
+
+ xorg_list_add(&info->drawable_resource, &resource->list);
+
+ return TRUE;
+}
+
+static void
+ms_dri2_del_frame_event(ms_dri2_frame_event_rec *info)
+{
+ xorg_list_del(&info->client_resource);
+ xorg_list_del(&info->drawable_resource);
+
+ if (info->front)
+ ms_dri2_destroy_buffer(NULL, info->front);
+ if (info->back)
+ ms_dri2_destroy_buffer(NULL, info->back);
+
+ free(info);
+}
+
+static void
+ms_dri2_blit_swap(DrawablePtr drawable,
+ DRI2BufferPtr dst,
+ DRI2BufferPtr src)
+{
+ BoxRec box;
+ RegionRec region;
+
+ box.x1 = 0;
+ box.y1 = 0;
+ box.x2 = drawable->width;
+ box.y2 = drawable->height;
+ REGION_INIT(pScreen, &region, &box, 0);
+
+ ms_dri2_copy_region(drawable, &region, dst, src);
+}
+
+static void
+ms_dri2_frame_event_handler(uint64_t msc,
+ uint64_t usec,
+ void *data)
+{
+ ms_dri2_frame_event_ptr frame_info = data;
+ DrawablePtr drawable = frame_info->drawable;
+ ScreenPtr screen = frame_info->screen;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ uint32_t tv_sec = usec / 1000000;
+ uint32_t tv_usec = usec % 1000000;
+
+ if (!drawable) {
+ ms_dri2_del_frame_event(frame_info);
+ return;
+ }
+
+ switch (frame_info->type) {
+ case MS_DRI2_QUEUE_SWAP:
+ ms_dri2_blit_swap(drawable, frame_info->front, frame_info->back);
+ DRI2SwapComplete(frame_info->client, drawable, msc, tv_sec, tv_usec,
+ DRI2_BLIT_COMPLETE,
+ frame_info->client ? frame_info->event_complete : NULL,
+ frame_info->event_data);
+ break;
+
+ case MS_DRI2_WAIT_MSC:
+ if (frame_info->client)
+ DRI2WaitMSCComplete(frame_info->client, drawable,
+ msc, tv_sec, tv_usec);
+ break;
+
+ default:
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "%s: unknown vblank event (type %d) received\n", __func__,
+ frame_info->type);
+ break;
+ }
+
+ ms_dri2_del_frame_event(frame_info);
+}
+
+static void
+ms_dri2_frame_event_abort(void *data)
+{
+ ms_dri2_frame_event_ptr frame_info = data;
+
+ ms_dri2_del_frame_event(frame_info);
+}
+
+/**
+ * Request a DRM event when the requested conditions will be satisfied.
+ *
+ * We need to handle the event and ask the server to wake up the client when
+ * we receive it.
+ */
+static int
+ms_dri2_schedule_wait_msc(ClientPtr client, DrawablePtr draw, CARD64 target_msc,
+ CARD64 divisor, CARD64 remainder)
+{
+ ScreenPtr screen = draw->pScreen;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+ ms_dri2_frame_event_ptr wait_info;
+ drmVBlank vbl;
+ int ret;
+ xf86CrtcPtr crtc = ms_dri2_crtc_covering_drawable(draw);
+ drmmode_crtc_private_ptr drmmode_crtc;
+ CARD64 current_msc, current_ust, request_msc;
+ uint32_t seq;
+
+ /* Drawable not visible, return immediately */
+ if (!crtc)
+ goto out_complete;
+ drmmode_crtc = crtc->driver_private;
+
+ wait_info = calloc(1, sizeof(*wait_info));
+ if (!wait_info)
+ goto out_complete;
+
+ wait_info->screen = screen;
+ wait_info->drawable = draw;
+ wait_info->client = client;
+ wait_info->type = MS_DRI2_WAIT_MSC;
+
+ if (!ms_dri2_add_frame_event(wait_info)) {
+ free(wait_info);
+ wait_info = NULL;
+ goto out_complete;
+ }
+
+ /* Get current count */
+ ret = ms_get_crtc_ust_msc(crtc, &current_ust, &current_msc);
+
+ /*
+ * If divisor is zero, or current_msc is smaller than target_msc,
+ * we just need to make sure target_msc passes before waking up the
+ * client.
+ */
+ if (divisor == 0 || current_msc < target_msc) {
+ /* If target_msc already reached or passed, set it to
+ * current_msc to ensure we return a reasonable value back
+ * to the caller. This keeps the client from continually
+ * sending us MSC targets from the past by forcibly updating
+ * their count on this call.
+ */
+ seq = ms_drm_queue_alloc(crtc, wait_info,
+ ms_dri2_frame_event_handler,
+ ms_dri2_frame_event_abort);
+ if (!seq)
+ goto out_free;
+
+ if (current_msc >= target_msc)
+ target_msc = current_msc;
+ vbl.request.type = (DRM_VBLANK_ABSOLUTE |
+ DRM_VBLANK_EVENT |
+ drmmode_crtc->vblank_pipe);
+ vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, target_msc);
+ vbl.request.signal = (unsigned long)seq;
+
+ ret = drmWaitVBlank(ms->fd, &vbl);
+ if (ret) {
+ static int limit = 5;
+ if (limit) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "%s:%d get vblank counter failed: %s\n",
+ __FUNCTION__, __LINE__,
+ strerror(errno));
+ limit--;
+ }
+ goto out_free;
+ }
+
+ wait_info->frame = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence);
+ DRI2BlockClient(client, draw);
+ return TRUE;
+ }
+
+ /*
+ * If we get here, target_msc has already passed or we don't have one,
+ * so we queue an event that will satisfy the divisor/remainder equation.
+ */
+ vbl.request.type =
+ DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT | drmmode_crtc->vblank_pipe;
+
+ request_msc = current_msc - (current_msc % divisor) +
+ remainder;
+ /*
+ * If calculated remainder is larger than requested remainder,
+ * it means we've passed the last point where
+ * seq % divisor == remainder, so we need to wait for the next time
+ * that will happen.
+ */
+ if ((current_msc % divisor) >= remainder)
+ request_msc += divisor;
+
+ seq = ms_drm_queue_alloc(crtc, wait_info,
+ ms_dri2_frame_event_handler,
+ ms_dri2_frame_event_abort);
+ if (!seq)
+ goto out_free;
+
+ vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, request_msc);
+ vbl.request.signal = (unsigned long)seq;
+
+ ret = drmWaitVBlank(ms->fd, &vbl);
+ if (ret) {
+ static int limit = 5;
+ if (limit) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "%s:%d get vblank counter failed: %s\n",
+ __FUNCTION__, __LINE__,
+ strerror(errno));
+ limit--;
+ }
+ goto out_free;
+ }
+
+ wait_info->frame = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence);
+ DRI2BlockClient(client, draw);
+
+ return TRUE;
+
+ out_free:
+ ms_dri2_del_frame_event(wait_info);
+ out_complete:
+ DRI2WaitMSCComplete(client, draw, target_msc, 0, 0);
+ return TRUE;
+}
+
+/**
+ * ScheduleSwap is responsible for requesting a DRM vblank event for
+ * the appropriate frame, or executing the swap immediately if it
+ * doesn't need to wait.
+ *
+ * When the swap is complete, the driver should call into the server so it
+ * can send any swap complete events that have been requested.
+ */
+static int
+ms_dri2_schedule_swap(ClientPtr client, DrawablePtr draw,
+ DRI2BufferPtr front, DRI2BufferPtr back,
+ CARD64 *target_msc, CARD64 divisor,
+ CARD64 remainder, DRI2SwapEventPtr func, void *data)
+{
+ ScreenPtr screen = draw->pScreen;
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+ drmVBlank vbl;
+ int ret;
+ xf86CrtcPtr crtc = ms_dri2_crtc_covering_drawable(draw);
+ drmmode_crtc_private_ptr drmmode_crtc;
+ ms_dri2_frame_event_ptr frame_info = NULL;
+ uint64_t current_msc, current_ust;
+ uint64_t request_msc;
+ uint32_t seq;
+
+ /* Drawable not displayed... just complete the swap */
+ if (!crtc)
+ goto blit_fallback;
+ drmmode_crtc = crtc->driver_private;
+
+ frame_info = calloc(1, sizeof(*frame_info));
+ if (!frame_info)
+ goto blit_fallback;
+
+ frame_info->screen = screen;
+ frame_info->drawable = draw;
+ frame_info->client = client;
+ frame_info->event_complete = func;
+ frame_info->event_data = data;
+ frame_info->front = front;
+ frame_info->back = back;
+ frame_info->crtc = crtc;
+ frame_info->type = MS_DRI2_QUEUE_SWAP;
+
+ if (!ms_dri2_add_frame_event(frame_info)) {
+ free(frame_info);
+ frame_info = NULL;
+ goto blit_fallback;
+ }
+
+ ms_dri2_reference_buffer(front);
+ ms_dri2_reference_buffer(back);
+
+ ret = ms_get_crtc_ust_msc(crtc, &current_ust, &current_msc);
+
+ /*
+ * If divisor is zero, or current_msc is smaller than target_msc
+ * we just need to make sure target_msc passes before initiating
+ * the swap.
+ */
+ if (divisor == 0 || current_msc < *target_msc) {
+ /* We need to use DRM_VBLANK_NEXTONMISS to avoid unreliable
+ * timestamping later on.
+ */
+ vbl.request.type = (DRM_VBLANK_ABSOLUTE |
+ DRM_VBLANK_NEXTONMISS |
+ DRM_VBLANK_EVENT |
+ drmmode_crtc->vblank_pipe);
+
+ /* If target_msc already reached or passed, set it to
+ * current_msc to ensure we return a reasonable value back
+ * to the caller. This makes swap_interval logic more robust.
+ */
+ if (current_msc >= *target_msc)
+ *target_msc = current_msc;
+
+ seq = ms_drm_queue_alloc(crtc, frame_info,
+ ms_dri2_frame_event_handler,
+ ms_dri2_frame_event_abort);
+ if (!seq)
+ goto blit_fallback;
+
+ vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, *target_msc);
+ vbl.request.signal = (unsigned long)seq;
+
+ ret = drmWaitVBlank(ms->fd, &vbl);
+ if (ret) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "divisor 0 get vblank counter failed: %s\n",
+ strerror(errno));
+ goto blit_fallback;
+ }
+
+ *target_msc = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence);
+ frame_info->frame = *target_msc;
+
+ return TRUE;
+ }
+
+ /*
+ * If we get here, target_msc has already passed or we don't have one,
+ * and we need to queue an event that will satisfy the divisor/remainder
+ * equation.
+ */
+ vbl.request.type = (DRM_VBLANK_ABSOLUTE |
+ DRM_VBLANK_NEXTONMISS |
+ DRM_VBLANK_EVENT |
+ drmmode_crtc->vblank_pipe);
+
+ request_msc = current_msc - (current_msc % divisor) +
+ remainder;
+
+ /*
+ * If the calculated deadline vbl.request.sequence is smaller than
+ * or equal to current_msc, it means we've passed the last point
+ * when effective onset frame seq could satisfy
+ * seq % divisor == remainder, so we need to wait for the next time
+ * this will happen.
+
+ * This comparison takes the DRM_VBLANK_NEXTONMISS delay into account.
+ */
+ if (request_msc <= current_msc)
+ request_msc += divisor;
+
+
+ seq = ms_drm_queue_alloc(crtc, frame_info,
+ ms_dri2_frame_event_handler,
+ ms_dri2_frame_event_abort);
+ if (!seq)
+ goto blit_fallback;
+
+ vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, request_msc);
+ vbl.request.signal = (unsigned long)seq;
+
+ ret = drmWaitVBlank(ms->fd, &vbl);
+ if (ret) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "final get vblank counter failed: %s\n",
+ strerror(errno));
+ goto blit_fallback;
+ }
+
+ *target_msc = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence);
+ frame_info->frame = *target_msc;
+
+ return TRUE;
+
+ blit_fallback:
+ ms_dri2_blit_swap(draw, front, back);
+ DRI2SwapComplete(client, draw, 0, 0, 0, DRI2_BLIT_COMPLETE, func, data);
+ if (frame_info)
+ ms_dri2_del_frame_event(frame_info);
+ *target_msc = 0; /* offscreen, so zero out target vblank count */
+ return TRUE;
+}
+
+static int
+ms_dri2_frame_event_client_gone(void *data, XID id)
+{
+ struct ms_dri2_resource *resource = data;
+
+ while (!xorg_list_is_empty(&resource->list)) {
+ ms_dri2_frame_event_ptr info =
+ xorg_list_first_entry(&resource->list,
+ ms_dri2_frame_event_rec,
+ client_resource);
+
+ xorg_list_del(&info->client_resource);
+ info->client = NULL;
+ }
+ free(resource);
+
+ return Success;
+}
+
+static int
+ms_dri2_frame_event_drawable_gone(void *data, XID id)
+{
+ struct ms_dri2_resource *resource = data;
+
+ while (!xorg_list_is_empty(&resource->list)) {
+ ms_dri2_frame_event_ptr info =
+ xorg_list_first_entry(&resource->list,
+ ms_dri2_frame_event_rec,
+ drawable_resource);
+
+ xorg_list_del(&info->drawable_resource);
+ info->drawable = NULL;
+ }
+ free(resource);
+
+ return Success;
+}
+
+static Bool
+ms_dri2_register_frame_event_resource_types(void)
+{
+ frame_event_client_type =
+ CreateNewResourceType(ms_dri2_frame_event_client_gone,
+ "Frame Event Client");
+ if (!frame_event_client_type)
+ return FALSE;
+
+ frame_event_drawable_type =
+ CreateNewResourceType(ms_dri2_frame_event_drawable_gone,
+ "Frame Event Drawable");
+ if (!frame_event_drawable_type)
+ return FALSE;
+
+ return TRUE;
+}
+
+Bool
+ms_dri2_screen_init(ScreenPtr screen)
+{
+ ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+ modesettingPtr ms = modesettingPTR(scrn);
+ DRI2InfoRec info;
+
+ if (!glamor_supports_pixmap_import_export(screen)) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "DRI2: glamor lacks support for pixmap import/export\n");
+ }
+
+ if (!xf86LoaderCheckSymbol("DRI2Version"))
+ return FALSE;
+
+ if (!dixRegisterPrivateKey(&ms_dri2_client_key,
+ PRIVATE_CLIENT, sizeof(XID)))
+ return FALSE;
+
+ if (serverGeneration != ms_dri2_server_generation) {
+ ms_dri2_server_generation = serverGeneration;
+ if (!ms_dri2_register_frame_event_resource_types()) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "Cannot register DRI2 frame event resources\n");
+ return FALSE;
+ }
+ }
+
+ memset(&info, '\0', sizeof(info));
+ info.fd = ms->fd;
+ info.driverName = NULL; /* Compat field, unused. */
+ info.deviceName = drmGetDeviceNameFromFd(ms->fd);
+
+ info.version = 4;
+ info.CreateBuffer = ms_dri2_create_buffer;
+ info.DestroyBuffer = ms_dri2_destroy_buffer;
+ info.CopyRegion = ms_dri2_copy_region;
+ info.ScheduleSwap = ms_dri2_schedule_swap;
+ info.GetMSC = ms_dri2_get_msc;
+ info.ScheduleWaitMSC = ms_dri2_schedule_wait_msc;
+
+ /* These two will be filled in by dri2.c */
+ info.numDrivers = 0;
+ info.driverNames = NULL;
+
+ return DRI2ScreenInit(screen, &info);
+}
+
+void
+ms_dri2_close_screen(ScreenPtr screen)
+{
+ DRI2CloseScreen(screen);
+}
+
+#endif /* GLAMOR */
diff --git a/hw/xfree86/drivers/modesetting/driver.c b/hw/xfree86/drivers/modesetting/driver.c
index c62147a0f..e0391dbc3 100644
--- a/hw/xfree86/drivers/modesetting/driver.c
+++ b/hw/xfree86/drivers/modesetting/driver.c
@@ -788,7 +788,9 @@ PreInit(ScrnInfoPtr pScrn, int flags)
try_enable_glamor(pScrn);
- if (!ms->glamor) {
+ if (ms->glamor) {
+ xf86LoadSubModule(pScrn, "dri2");
+ } else {
Bool prefer_shadow = TRUE;
ret = drmGetCap(ms->fd, DRM_CAP_DUMB_PREFER_SHADOW, &value);
@@ -1107,6 +1109,21 @@ ScreenInit(ScreenPtr pScreen, int argc, char **argv)
if (serverGeneration == 1)
xf86ShowUnusedOptions(pScrn->scrnIndex, pScrn->options);
+ if (!ms_vblank_screen_init(pScreen)) {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "Failed to initialize vblank support.\n");
+ return FALSE;
+ }
+
+#ifdef GLAMOR
+ if (ms->glamor) {
+ if (!ms_dri2_screen_init(pScreen)) {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "Failed to initialize the DRI2 extension.\n");
+ }
+ }
+#endif
+
return EnterVT(pScrn);
}
@@ -1172,6 +1189,14 @@ CloseScreen(ScreenPtr pScreen)
ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
modesettingPtr ms = modesettingPTR(pScrn);
+#ifdef GLAMOR
+ if (ms->glamor) {
+ ms_dri2_close_screen(pScreen);
+ }
+#endif
+
+ ms_vblank_close_screen(pScreen);
+
if (ms->damage) {
DamageUnregister(ms->damage);
DamageDestroy(ms->damage);
diff --git a/hw/xfree86/drivers/modesetting/driver.h b/hw/xfree86/drivers/modesetting/driver.h
index 35f24193a..9eda1c4da 100644
--- a/hw/xfree86/drivers/modesetting/driver.h
+++ b/hw/xfree86/drivers/modesetting/driver.h
@@ -30,6 +30,7 @@
#include <errno.h>
#include <drm.h>
#include <xf86drm.h>
+#include <xf86Crtc.h>
#include <damage.h>
#include "drmmode_display.h"
@@ -42,6 +43,26 @@ typedef struct {
ScrnInfoPtr pScrn_2;
} EntRec, *EntPtr;
+typedef void (*ms_drm_handler_proc)(uint64_t frame,
+ uint64_t usec,
+ void *data);
+
+typedef void (*ms_drm_abort_proc)(void *data);
+
+/**
+ * A tracked handler for an event that will hopefully be generated by
+ * the kernel, and what to do when it is encountered.
+ */
+struct ms_drm_queue {
+ struct xorg_list list;
+ xf86CrtcPtr crtc;
+ uint32_t seq;
+ void *data;
+ ScrnInfoPtr scrn;
+ ms_drm_handler_proc handler;
+ ms_drm_abort_proc abort;
+};
+
typedef struct _modesettingRec {
int fd;
@@ -70,6 +91,8 @@ typedef struct _modesettingRec {
drmmode_rec drmmode;
+ drmEventContext event_context;
+
DamagePtr damage;
Bool dirty_enabled;
@@ -78,3 +101,24 @@ typedef struct _modesettingRec {
} modesettingRec, *modesettingPtr;
#define modesettingPTR(p) ((modesettingPtr)((p)->driverPrivate))
+
+uint32_t ms_drm_queue_alloc(xf86CrtcPtr crtc,
+ void *data,
+ ms_drm_handler_proc handler,
+ ms_drm_abort_proc abort);
+
+xf86CrtcPtr ms_dri2_crtc_covering_drawable(DrawablePtr pDraw);
+xf86CrtcPtr ms_covering_crtc(ScrnInfoPtr scrn, BoxPtr box,
+ xf86CrtcPtr desired, BoxPtr crtc_box_ret);
+
+int ms_get_crtc_ust_msc(xf86CrtcPtr crtc, CARD64 *ust, CARD64 *msc);
+
+uint32_t ms_crtc_msc_to_kernel_msc(xf86CrtcPtr crtc, uint64_t expect);
+uint64_t ms_kernel_msc_to_crtc_msc(xf86CrtcPtr crtc, uint32_t sequence);
+
+
+Bool ms_dri2_screen_init(ScreenPtr screen);
+void ms_dri2_close_screen(ScreenPtr screen);
+
+Bool ms_vblank_screen_init(ScreenPtr screen);
+void ms_vblank_close_screen(ScreenPtr screen);
diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.c b/hw/xfree86/drivers/modesetting/drmmode_display.c
index d8d1b4485..ef9009e98 100644
--- a/hw/xfree86/drivers/modesetting/drmmode_display.c
+++ b/hw/xfree86/drivers/modesetting/drmmode_display.c
@@ -582,6 +582,17 @@ static const xf86CrtcFuncsRec drmmode_crtc_funcs = {
.shadow_create = drmmode_shadow_create,
};
+static uint32_t
+drmmode_crtc_vblank_pipe(int crtc_id)
+{
+ if (crtc_id > 1)
+ return crtc_id << DRM_VBLANK_HIGH_CRTC_SHIFT;
+ else if (crtc_id > 0)
+ return DRM_VBLANK_SECONDARY;
+ else
+ return 0;
+}
+
static void
drmmode_crtc_init(ScrnInfoPtr pScrn, drmmode_ptr drmmode, int num)
{
@@ -596,6 +607,7 @@ drmmode_crtc_init(ScrnInfoPtr pScrn, drmmode_ptr drmmode, int num)
drmmode_crtc->mode_crtc =
drmModeGetCrtc(drmmode->fd, drmmode->mode_res->crtcs[num]);
drmmode_crtc->drmmode = drmmode;
+ drmmode_crtc->vblank_pipe = drmmode_crtc_vblank_pipe(num);
crtc->driver_private = drmmode_crtc;
}
@@ -1183,6 +1195,11 @@ drmmode_xf86crtc_resize(ScrnInfoPtr scrn, int width, int height)
xf86DrvMsg(scrn->scrnIndex, X_INFO,
"Allocate new frame buffer %dx%d stride\n", width, height);
+ if (drmmode->triple_buffer_pixmap) {
+ screen->DestroyPixmap(drmmode->triple_buffer_pixmap);
+ drmmode->triple_buffer_pixmap = NULL;
+ }
+
old_width = scrn->virtualX;
old_height = scrn->virtualY;
old_pitch = drmmode->front_bo->pitch;
diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.h b/hw/xfree86/drivers/modesetting/drmmode_display.h
index c7e7ef0ca..987608c55 100644
--- a/hw/xfree86/drivers/modesetting/drmmode_display.h
+++ b/hw/xfree86/drivers/modesetting/drmmode_display.h
@@ -43,6 +43,7 @@ struct dumb_bo {
typedef struct {
int fd;
unsigned fb_id;
+ unsigned old_fb_id;
drmModeResPtr mode_res;
drmModeFBPtr mode_fb;
int cpp;
@@ -58,17 +59,42 @@ typedef struct {
Bool shadow_enable;
void *shadow_fb;
+ /**
+ * A screen-sized pixmap when we're doing triple-buffered DRI2
+ * pageflipping.
+ *
+ * One is shared between all drawables that flip to the front
+ * buffer, and it only gets reallocated when root pixmap size
+ * changes.
+ */
+ PixmapPtr triple_buffer_pixmap;
+
+ /** The GEM name for triple_buffer_pixmap */
+ uint32_t triple_buffer_name;
+
DevPrivateKeyRec pixmapPrivateKeyRec;
} drmmode_rec, *drmmode_ptr;
typedef struct {
drmmode_ptr drmmode;
drmModeCrtcPtr mode_crtc;
- int hw_id;
+ uint32_t vblank_pipe;
struct dumb_bo *cursor_bo;
unsigned rotate_fb_id;
uint16_t lut_r[256], lut_g[256], lut_b[256];
DamagePtr slave_damage;
+
+ /**
+ * @{ MSC (vblank count) handling for the PRESENT extension.
+ *
+ * The kernel's vblank counters are 32 bits and apparently full of
+ * lies, and we need to give a reliable 64-bit msc for GL, so we
+ * have to track and convert to a userland-tracked 64-bit msc.
+ */
+ int32_t vblank_offset;
+ uint32_t msc_prev;
+ uint64_t msc_high;
+ /** @} */
} drmmode_crtc_private_rec, *drmmode_crtc_private_ptr;
typedef struct {
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);
+}