diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/driver.c | 7 | ||||
-rw-r--r-- | src/driver.h | 44 | ||||
-rw-r--r-- | src/drmmode_display.h | 13 | ||||
-rw-r--r-- | src/present.c | 874 |
5 files changed, 939 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 0ced1ff..4b7d8d5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,5 +38,6 @@ modesetting_drv_la_SOURCES = \ driver.h \ drmmode_display.c \ drmmode_display.h \ + present.c \ sync.c \ $() diff --git a/src/driver.c b/src/driver.c index a367713..95b7cdc 100644 --- a/src/driver.c +++ b/src/driver.c @@ -1159,6 +1159,12 @@ ScreenInit(SCREEN_INIT_ARGS_DECL) xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Failed to initialize the DRI2 extension.\n"); } + + if (!ms_present_screen_init(pScreen)) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to initialize PRESENT.\n"); + return FALSE; + } } #endif @@ -1236,6 +1242,7 @@ CloseScreen(CLOSE_SCREEN_ARGS_DECL) if (ms->glamor) { ms_dri2_close_screen(pScreen); ms_sync_close_screen(pScreen); + ms_present_close_screen(pScreen); } #endif diff --git a/src/driver.h b/src/driver.h index e879b48..345252a 100644 --- a/src/driver.h +++ b/src/driver.h @@ -32,6 +32,8 @@ #include <xf86drm.h> #include <damage.h> #include "misync.h" +#include "xorg-server.h" +#include "xf86Crtc.h" #include "drmmode_display.h" #define DRV_ERROR(msg) xf86DrvMsg(pScrn->scrnIndex, X_ERROR, msg); @@ -48,6 +50,36 @@ typedef struct ScrnInfoPtr pScrn_2; } EntRec, *EntPtr; +typedef void (*ms_drm_handler_proc)(ScrnInfoPtr scrn, + xf86CrtcPtr crtc, + uint64_t seq, + uint64_t usec, + void *data); + +typedef void (*ms_drm_abort_proc)(ScrnInfoPtr scrn, + xf86CrtcPtr crtc, + void *data); + +typedef void (*ms_pageflip_handler_proc)(uint64_t frame, + uint64_t usec, + void *data); + +typedef void (*ms_pageflip_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; @@ -78,6 +110,15 @@ typedef struct _modesettingRec drmmode_rec drmmode; + drmEventContext event_context; + + int flip_count; + uint64_t fe_msc; + uint64_t fe_usec; + void *pageflip_data; + ms_pageflip_handler_proc pageflip_handler; + ms_pageflip_abort_proc pageflip_abort; + DamagePtr damage; Bool dirty_enabled; @@ -93,3 +134,6 @@ void ms_dri2_close_screen(ScreenPtr screen); Bool ms_sync_screen_init(ScreenPtr screen); void ms_sync_close_screen(ScreenPtr screen); + +Bool ms_present_screen_init(ScreenPtr screen); +void ms_present_close_screen(ScreenPtr screen); diff --git a/src/drmmode_display.h b/src/drmmode_display.h index 18a55a6..59e92de 100644 --- a/src/drmmode_display.h +++ b/src/drmmode_display.h @@ -54,6 +54,7 @@ struct dumb_bo { typedef struct { int fd; unsigned fb_id; + unsigned old_fb_id; drmModeResPtr mode_res; drmModeFBPtr mode_fb; int cpp; @@ -82,6 +83,18 @@ typedef struct { 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. + */ + uint32_t vblank_offset; + uint32_t msc_prev; + uint64_t msc_high; + /** @} */ } drmmode_crtc_private_rec, *drmmode_crtc_private_ptr; typedef struct { diff --git a/src/present.c b/src/present.c new file mode 100644 index 0000000..bf91855 --- /dev/null +++ b/src/present.c @@ -0,0 +1,874 @@ +/* + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <xf86.h> +#include <xf86Crtc.h> +#include <xorg-server.h> +#include <present.h> +#include <glamor.h> +#include <poll.h> +#include "compat-api.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_present_vblank_event { + uint64_t event_id; +}; + +struct ms_pageflip { + ScreenPtr screen; + Bool crtc_for_msc_ust; +}; + +static uint32_t +vblank_pipe_select(int pipe) +{ + if (pipe > 1) + return pipe << DRM_VBLANK_HIGH_CRTC_SHIFT; + else if (pipe > 0) + return DRM_VBLANK_SECONDARY; + else + return 0; +} + +static int +ms_present_crtc_pipe(ScreenPtr screen, RRCrtcPtr randr_crtc) +{ + xf86CrtcPtr crtc; + drmmode_crtc_private_ptr drmmode_crtc; + + if (randr_crtc == NULL) + return 0; + + crtc = randr_crtc->devPrivate; + drmmode_crtc = crtc->driver_private; + + return drmmode_crtc->hw_id; +} + +static Bool +ms_get_kernel_ust_msc(RRCrtcPtr crtc, + uint32_t *msc, uint64_t *ust) +{ + ScreenPtr screen = crtc->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + drmVBlank vbl; + int pipe = ms_present_crtc_pipe(screen, crtc); + int ret; + + /* Get current count */ + vbl.request.type = DRM_VBLANK_RELATIVE | vblank_pipe_select(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 + */ +static 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; +} + +static int +ms_get_crtc_ust_msc(RRCrtcPtr crtc, CARD64 *ust, CARD64 *msc) +{ + xf86CrtcPtr xf86_crtc = crtc->devPrivate; + uint32_t kernel_msc; + + if (!ms_get_kernel_ust_msc(crtc, &kernel_msc, ust)) + return BadMatch; + *msc = ms_kernel_msc_to_crtc_msc(xf86_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. + */ +static uint32_t +ms_crtc_msc_to_kernel_msc(RRCrtcPtr crtc, uint64_t expect) +{ + xf86CrtcPtr xf86_crtc = crtc->devPrivate; + drmmode_crtc_private_rec *drmmode_crtc = xf86_crtc->driver_private; + uint64_t msc; + uint64_t ust; + int64_t diff; + + ms_get_crtc_ust_msc(crtc, &msc, &ust); + 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 (-MAX_VBLANK_OFFSET < drmmode_crtc->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); +} + +/* + * If there are any available, read drm_events + */ +static int +ms_read_drm_events(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + struct pollfd p = { + .fd = ms->fd, + .events = POLLIN + }; + int r; + + do { + r = poll(&p, 1, 0); + } while (r == -1 && (errno == EINTR || errno == EAGAIN)); + + if (r <= 0) + return 0; + + return drmHandleEvent(ms->fd, &ms->event_context); +} + +/* + * Flush the DRM event queue when full; this + * makes space for new requests + */ +static Bool +ms_present_flush_drm_events(ScreenPtr screen) +{ + return ms_read_drm_events(screen) >= 0; +} + +/** + * Called when the queued vblank event has occurred + */ +static void +ms_present_vblank_handler(ScrnInfoPtr scrn, xf86CrtcPtr crtc, + uint64_t msc, uint64_t usec, void *data) +{ + struct ms_present_vblank_event *event = data; + + present_event_notify(event->event_id, usec, msc); + free(event); +} + +/** + * Called when the queued vblank is aborted + */ +static void +ms_present_vblank_abort(ScrnInfoPtr scrn, xf86CrtcPtr crtc, void *data) +{ + struct ms_present_vblank_event *event = data; + + free(event); +} + +/* + * Enqueue a potential drm response; when the associated response + * appears, we've got data to pass to the handler from here + */ +static uint32_t +ms_drm_queue_alloc(RRCrtcPtr crtc, + void *data, + ms_drm_handler_proc handler, + ms_drm_abort_proc abort) +{ + xf86CrtcPtr xf86_crtc = crtc->devPrivate; + ScreenPtr screen = 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 = xf86_crtc; + q->data = data; + q->handler = handler; + q->abort = abort; + + xorg_list_add(&q->list, &ms_drm_queue); + + return q->seq; +} + +/** + * Queue an event to report back to the Present extension when the specified + * MSC has passed. + */ +static int +ms_present_queue_vblank(RRCrtcPtr crtc, + uint64_t event_id, + uint64_t msc) +{ + ScreenPtr screen = crtc->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + int pipe = ms_present_crtc_pipe(screen, crtc); + struct ms_present_vblank_event *event; + drmVBlank vbl; + int ret; + uint32_t seq; + + event = calloc(sizeof(struct ms_present_vblank_event), 1); + if (!event) + return BadAlloc; + event->event_id = event_id; + seq = ms_drm_queue_alloc(crtc, event, + ms_present_vblank_handler, + ms_present_vblank_abort); + if (!seq) { + free(event); + return BadAlloc; + } + + vbl.request.type = (DRM_VBLANK_ABSOLUTE | + DRM_VBLANK_EVENT | + vblank_pipe_select(pipe)); + vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, msc); + vbl.request.signal = seq; + for (;;) { + ret = drmWaitVBlank(ms->fd, &vbl); + if (!ret) + break; + if (errno != EBUSY || !ms_present_flush_drm_events(screen)) + return BadAlloc; + } + + return Success; +} + +static Bool +ms_present_event_match(void *data, void *match_data) +{ + struct ms_present_vblank_event *event = data; + uint64_t *match = match_data; + + return *match == event->event_id; +} + +/** + * 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->scrn, q->crtc, q->data); + free(q); +} + +/** + * Externally usable abort function that uses a callback to match a single queued + * entry to abort + */ +static void +ms_drm_abort(ScrnInfoPtr scrn, + Bool (*match)(void *data, void *match_data), void *match_data) +{ + struct ms_drm_queue *q, *tmp; + + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (match(q->data, match_data)) { + ms_drm_abort_one(q); + break; + } + } +} + +/* + * Abort by drm queue sequence number + */ +static void +ms_drm_abort_seq(ScrnInfoPtr scrn, uint32_t seq) +{ + struct ms_drm_queue *q, *tmp; + + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (q->seq == seq) { + ms_drm_abort_one(q); + break; + } + } +} + +/* + * Remove a pending vblank event from the DRM queue so that it is not reported + * to the extension + */ +static void +ms_present_abort_vblank(RRCrtcPtr crtc, uint64_t event_id, uint64_t msc) +{ + ScreenPtr screen = crtc->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + + ms_drm_abort(scrn, ms_present_event_match, &event_id); +} + +/** + * 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); + } +} + +/* + * Test to see if page flipping is possible on the target crtc + */ +static Bool +ms_present_check_flip(RRCrtcPtr crtc, + WindowPtr window, + PixmapPtr pixmap, + Bool sync_flip) +{ + xf86CrtcPtr xf86_crtc = crtc->devPrivate; + ScreenPtr screen = window->drawable.pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + if (!scrn->vtSema) + return FALSE; + + if (ms->drmmode.shadow_enable) + return FALSE; + + if (!xf86_crtc->enabled) + return FALSE; + + return TRUE; +} + +/** + * Once the flip has been completed on all pipes, notify the + * extension code telling it when that happened + */ +static void +ms_present_flip_event(uint64_t msc, uint64_t ust, void *pageflip_data) +{ + struct ms_present_vblank_event *event = pageflip_data; + + present_event_notify(event->event_id, ust, msc); + free(event); +} + +/** + * The flip has been aborted, free the structure + */ +static void +ms_present_flip_abort(void *pageflip_data) +{ + struct ms_present_vblank_event *event = pageflip_data; + + free(event); +} + + +/** + * Notify the page flip caller that the flip is complete + */ +static void +ms_pageflip_complete(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + /* Release framebuffer */ + drmModeRmFB(ms->fd, ms->drmmode.old_fb_id); + + if (!ms->pageflip_handler) + return; + + ms->pageflip_handler(ms->fe_msc, ms->fe_usec, ms->pageflip_data); +} + +/** + * Called after processing a pageflip complete event from DRM. + * + * Update the saved msc/ust values as needed, then check to see if the + * whole set of events are complete and notify the application at that + * point. + */ +static Bool +ms_handle_pageflip(struct ms_pageflip *flip, uint64_t msc, uint64_t usec) +{ + ScreenPtr screen = flip->screen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + if (flip->crtc_for_msc_ust) { + /* Cache msc, ust for later delivery with a Present event or + * GLX reply. + */ + ms->fe_msc = msc; + ms->fe_usec = usec; + } + free(flip); + + ms->flip_count--; + + /* Tell the caller if this was the last DRM flip complete event expected. */ + return ms->flip_count == 0; +} + +/* + * Called from the DRM event queue when a single flip has completed + */ +static void +ms_pageflip_handler(ScrnInfoPtr scrn, xf86CrtcPtr crtc, + uint64_t msc, uint64_t usec, void *data) +{ + struct ms_pageflip *flip = data; + ScreenPtr screen = flip->screen; + + if (ms_handle_pageflip(flip, msc, usec)) + ms_pageflip_complete(screen); +} + +/* + * Called from the DRM queue abort code when a flip has been aborted + */ +static void +ms_pageflip_abort(ScrnInfoPtr scrn, xf86CrtcPtr crtc, void *data) +{ + struct ms_pageflip *flip = data; + modesettingPtr ms = modesettingPTR(scrn); + + if (ms_handle_pageflip(flip, 0, 0)) { + /* Release framebuffer */ + drmModeRmFB(ms->fd, ms->drmmode.old_fb_id); + + if (!ms->pageflip_abort) + return; + + ms->pageflip_abort(ms->pageflip_data); + } +} + +static Bool +ms_do_pageflip(PixmapPtr new_front, + int ref_crtc_hw_id, + Bool async, + void *pageflip_data, + ms_pageflip_handler_proc pageflip_handler, + ms_pageflip_abort_proc pageflip_abort) +{ + ScreenPtr screen = new_front->drawable.pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); + uint16_t pitch; + struct ms_pageflip *flip; + uint32_t new_fb_id; + uint32_t flags; + uint32_t seq; + uint32_t size; + int i; + int fd; + struct dumb_bo *new_front_bo; + + glamor_block_handler(screen); + + /* Take a detour through a dmabuf fd to get a GEM handle for our + * new front, since there's no GL interface to get the GEM handle + * (just a dmabuf fd, or an old-style GEM name) + */ + fd = glamor_fd_from_pixmap(screen, new_front, &pitch, &size); + if (fd < 0) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "Failed to get fd for flip to new front.\n"); + return FALSE; + } + new_front_bo = dumb_get_bo_from_fd(ms->fd, fd, pitch, size); + close(fd); + if (!new_front_bo) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "Failed to open fd for new front.\n"); + return FALSE; + } + + /* + * Create a new handle for the back buffer + */ + if (drmModeAddFB(ms->fd, scrn->virtualX, scrn->virtualY, + scrn->depth, scrn->bitsPerPixel, pitch, + new_front_bo->handle, &new_fb_id)) + goto error_out; + + dumb_bo_destroy(ms->fd, new_front_bo); + + ms->pageflip_data = pageflip_data; + ms->pageflip_handler = pageflip_handler; + ms->pageflip_abort = pageflip_abort; + + /* + * Queue flips on all enabled CRTCs + * Note that if/when we get per-CRTC buffers, we'll have to update this. + * Right now it assumes a single shared fb across all CRTCs, with the + * kernel fixing up the offset of each CRTC as necessary. + * + * Also, flips queued on disabled or incorrectly configured displays + * may never complete; this is a configuration error. + */ + ms->fe_msc = 0; + ms->fe_usec = 0; + + flags = DRM_MODE_PAGE_FLIP_EVENT; + if (async) + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + for (i = 0; i < config->num_crtc; i++) { + xf86CrtcPtr crtc = config->crtc[i]; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + int this_fb_id; + + if (!config->crtc[i]->enabled) + continue; + + flip = calloc(1, sizeof(struct ms_pageflip)); + if (flip == NULL) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue: carrier alloc failed.\n"); + goto error_undo; + } + + /* Only the reference crtc will finally deliver its page flip + * completion event. All other crtc's events will be discarded. + */ + flip->crtc_for_msc_ust = (drmmode_crtc->hw_id == ref_crtc_hw_id); + flip->screen = screen; + + seq = ms_drm_queue_alloc(crtc->randr_crtc, flip, + ms_pageflip_handler, ms_pageflip_abort); + if (!seq) { + free(flip); + goto error_undo; + } + + this_fb_id = new_fb_id; + if (drmmode_crtc->rotate_fb_id) + this_fb_id = drmmode_crtc->rotate_fb_id; + again: + if (drmModePageFlip(ms->fd, + drmmode_crtc->hw_id, + this_fb_id, + flags, (void *) (uintptr_t) seq)) { + int err = errno; + if (ms_read_drm_events(screen)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue retry\n"); + goto again; + } + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue failed: %s\n", strerror(err)); + ms_drm_abort_seq(scrn, seq); + goto error_undo; + } + ms->flip_count++; + } + + ms->drmmode.old_fb_id = ms->drmmode.fb_id; + ms->drmmode.fb_id = new_fb_id; + + if (!ms->flip_count) + ms_pageflip_complete(screen); + + return TRUE; + + error_undo: + drmModeRmFB(ms->fd, new_fb_id); + for (i = 0; i < config->num_crtc; i++) { + if (config->crtc[i]->enabled) { + ErrorF("XXX: crtc apply\n"); + /*ms_crtc_apply(config->crtc[i]);*/ + } + } + + error_out: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, "Page flip failed: %s\n", + strerror(errno)); + + ms->flip_count = 0; + return FALSE; +} + +/** + * Queue a flip on 'crtc' to 'pixmap' at 'target_msc'. If 'sync_flip' is true, + * then wait for vblank. Otherwise, flip immediately + */ +static Bool +ms_present_flip(RRCrtcPtr crtc, + uint64_t event_id, + uint64_t target_msc, + PixmapPtr pixmap, + Bool sync_flip) +{ + ScreenPtr screen = crtc->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + struct ms_present_vblank_event *event; + int pipe = ms_present_crtc_pipe(screen, crtc); + Bool ret; + + if (!ms_present_check_flip(crtc, screen->root, pixmap, sync_flip)) + return FALSE; + + event = calloc(1, sizeof(struct ms_present_vblank_event)); + if (!event) + return FALSE; + + event->event_id = event_id; + + ret = ms_do_pageflip(pixmap, pipe, !sync_flip, + event, + ms_present_flip_event, + ms_present_flip_abort); + if (!ret) + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "present flip failed\n"); + return ret; +} + +/** + * Queue a flip back to the normal frame buffer + */ +static void +ms_present_unflip(ScreenPtr screen, uint64_t event_id) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + struct ms_present_vblank_event *event; + PixmapPtr pixmap = screen->GetScreenPixmap(screen); + Bool ret; + + event = calloc(1, sizeof(struct ms_present_vblank_event)); + if (!event) + return; + + event->event_id = event_id; + + ret = ms_do_pageflip(pixmap, 0, FALSE, event, + ms_present_flip_event, ms_present_flip_abort); + if (!ret) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, "present unflip failed\n"); + } +} + +static void +ms_present_flush(WindowPtr window) +{ + ScreenPtr screen = window->drawable.pScreen; + + /* XXX: This function is terribly named */ + glamor_block_handler(screen); +} + +static RRCrtcPtr +ms_present_get_crtc(WindowPtr window) +{ + ScreenPtr screen = window->drawable.pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); + int c; + + /* XXX: fill in */ + for (c = 0; c < xf86_config->num_crtc; c++) { + xf86CrtcPtr crtc = xf86_config->crtc[c]; + + if (crtc->enabled) + return crtc->randr_crtc; + } + + return NULL; +} + +static present_screen_info_rec ms_present_screen_info = { + .version = 0, + + .get_crtc = ms_present_get_crtc, + .get_ust_msc = ms_get_crtc_ust_msc, + .queue_vblank = ms_present_queue_vblank, + .abort_vblank = ms_present_abort_vblank, + .flush = ms_present_flush, + + .capabilities = PresentCapabilityNone, + .check_flip = ms_present_check_flip, + .flip = ms_present_flip, + .unflip = ms_present_unflip, +}; + +static Bool +has_async_flip(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + int ret; + uint64_t value; + + ret = drmGetCap(ms->fd, DRM_CAP_ASYNC_PAGE_FLIP, &value); + if (ret == 0) + return value == 1; + + return FALSE; +} + +/* + * 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(q->scrn, q->crtc, msc, + (uint64_t) sec * 1000000 + usec, q->data); + free(q); + break; + } + } +} + +Bool +ms_present_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; + ms->flip_count = 0; + + /* 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); + + if (has_async_flip(screen)) + ms_present_screen_info.capabilities |= PresentCapabilityAsync; + + return present_screen_init(screen, &ms_present_screen_info); +} + +void +ms_present_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); +} |