/* * Copyright 2013 Red Hat Inc. * * 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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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. * * Authors: Ben Skeggs */ #include "nouveau_present.h" #if defined(DRI3) && defined(HAVE_GLAMOR) #include "nv_include.h" #include "nouveau_glamor.h" #include "xf86drmMode.h" struct nouveau_present { struct present_screen_info info; }; static RRCrtcPtr nouveau_present_crtc(WindowPtr window) { ScrnInfoPtr scrn = xf86ScreenToScrn(window->drawable.pScreen); xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); xf86CrtcPtr crtc; unsigned mask; int head; mask = nv_window_belongs_to_crtc(scrn, window->drawable.x, window->drawable.y, window->drawable.width, window->drawable.height); head = ffs(mask) - 1; if (head < 0 || head >= xf86_config->num_crtc) return NULL; crtc = xf86_config->crtc[head]; if (crtc->rotatedData) return NULL; return crtc->randr_crtc; } static int nouveau_present_ust_msc(RRCrtcPtr rrcrtc, uint64_t *ust, uint64_t *msc) { xf86CrtcPtr crtc = rrcrtc->devPrivate; xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(crtc->scrn); NVPtr pNv = NVPTR(crtc->scrn); drmVBlank args; int ret, i; for (i = 0; i < xf86_config->num_crtc; i++) { if (xf86_config->crtc[i] == crtc) break; } if (i == xf86_config->num_crtc) return BadMatch; args.request.type = DRM_VBLANK_RELATIVE; args.request.type |= i << DRM_VBLANK_HIGH_CRTC_SHIFT; args.request.sequence = 0, args.request.signal = 0, ret = drmWaitVBlank(pNv->dev->fd, &args); if (ret) { *ust = *msc = 0; return BadMatch; } *ust = (CARD64)args.reply.tval_sec * 1000000 + args.reply.tval_usec; *msc = args.reply.sequence; return Success; } struct nouveau_present_vblank { uint64_t msc; }; static void nouveau_present_vblank(void *priv, uint64_t name, uint64_t ust, uint32_t msc_lo) { struct nouveau_present_vblank *event = priv; uint64_t msc; msc = (event->msc & 0xffffffff00000000ULL) | msc_lo; if (msc < event->msc) event->msc += 1ULL << 32; present_event_notify(name, ust, msc); } static int nouveau_present_vblank_queue(RRCrtcPtr rrcrtc, uint64_t event_id, uint64_t msc) { xf86CrtcPtr crtc = rrcrtc->devPrivate; xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(crtc->scrn); NVPtr pNv = NVPTR(crtc->scrn); drmVBlank args; struct nouveau_present_vblank *event; void *token; int ret, i; for (i = 0; i < xf86_config->num_crtc; i++) { if (xf86_config->crtc[i] == crtc) break; } if (i == xf86_config->num_crtc) return BadMatch; event = drmmode_event_queue(crtc->scrn, event_id, sizeof(*event), nouveau_present_vblank, &token); if (!event) return BadAlloc; event->msc = msc; args.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT; args.request.type |= i << DRM_VBLANK_HIGH_CRTC_SHIFT; args.request.sequence = msc; args.request.signal = (unsigned long)token; while ((ret = drmWaitVBlank(pNv->dev->fd, &args)) != 0) { if (errno != EBUSY || drmmode_event_flush(crtc->scrn) < 0) return BadAlloc; } return Success; } static void nouveau_present_vblank_abort(RRCrtcPtr rrcrtc, uint64_t event_id, uint64_t msc) { xf86CrtcPtr crtc = rrcrtc->devPrivate; drmmode_event_abort(crtc->scrn, event_id, true); } static void nouveau_present_flush(WindowPtr window) { ScrnInfoPtr scrn = xf86ScreenToScrn(window->drawable.pScreen); NVPtr pNv = NVPTR(scrn); if (pNv->Flush) pNv->Flush(scrn); } struct nouveau_present_flip { uint64_t msc; uint32_t old; int fd; }; static Bool nouveau_present_flip_check(RRCrtcPtr rrcrtc, WindowPtr window, PixmapPtr pixmap, Bool sync_flip) { ScrnInfoPtr scrn = xf86ScreenToScrn(window->drawable.pScreen); xf86CrtcPtr crtc = rrcrtc->devPrivate; if (!scrn->vtSema || !crtc->enabled) return FALSE; return TRUE; } static void nouveau_present_flip(void *priv, uint64_t name, uint64_t ust, uint32_t msc_lo) { struct nouveau_present_flip *flip = priv; uint64_t msc; msc = (flip->msc & ~0xffffffffULL) | msc_lo; if (msc < flip->msc) msc += 1ULL << 32; present_event_notify(name, ust, msc); drmModeRmFB(flip->fd, flip->old); } static Bool nouveau_present_flip_exec(ScrnInfoPtr scrn, uint64_t event_id, int sync, uint64_t target_msc, PixmapPtr pixmap, Bool vsync) { ScreenPtr screen = scrn->pScreen; struct nouveau_pixmap *priv; NVPtr pNv = NVPTR(scrn); uint32_t next_fb; CARD16 stride; CARD32 size; void *token; int ret; priv = nouveau_glamor_pixmap_get(pixmap); if (priv == NULL) { int fd = glamor_fd_from_pixmap(screen, pixmap, &stride, &size); if (fd < 0) return FALSE; priv = calloc(1, sizeof(*priv)); if (!priv) return FALSE; ret = nouveau_bo_prime_handle_ref(pNv->dev, fd, &priv->bo); if (ret) { free(priv); return FALSE; } nouveau_glamor_pixmap_set(pixmap, priv); } ret = drmModeAddFB(pNv->dev->fd, pixmap->drawable.width, pixmap->drawable.height, pixmap->drawable.depth, pixmap->drawable.bitsPerPixel, pixmap->devKind, priv->bo->handle, &next_fb); if (ret == 0) { struct nouveau_present_flip *flip = drmmode_event_queue(scrn, event_id, sizeof(*flip), nouveau_present_flip, &token); if (flip) { xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); int last = 0, i; drmmode_swap(scrn, next_fb, &flip->old); flip->fd = pNv->dev->fd; flip->msc = target_msc; for (i = 0; i < config->num_crtc; i++) { if (config->crtc[i]->enabled) last = i; } for (i = 0; i < config->num_crtc; i++) { int type = vsync ? 0 : DRM_MODE_PAGE_FLIP_ASYNC; int head = drmmode_head(config->crtc[i]); void *user = NULL; if (!config->crtc[i]->enabled) continue; if (token && ((head == sync) || (i == last))) { type |= DRM_MODE_PAGE_FLIP_EVENT; user = token; } ret = drmModePageFlip(pNv->dev->fd, head, next_fb, type, user); if (ret == 0 && user) { token = NULL; } } if (token == NULL) { return TRUE; } drmmode_swap(scrn, flip->old, &next_fb); drmmode_event_abort(scrn, event_id, false); } drmModeRmFB(pNv->dev->fd, next_fb); } return FALSE; } static Bool nouveau_present_flip_next(RRCrtcPtr rrcrtc, uint64_t event_id, uint64_t target_msc, PixmapPtr pixmap, Bool vsync) { xf86CrtcPtr crtc = rrcrtc->devPrivate; ScrnInfoPtr scrn = crtc->scrn; return nouveau_present_flip_exec(scrn, event_id, drmmode_head(crtc), target_msc, pixmap, vsync); } static void nouveau_present_flip_stop(ScreenPtr screen, uint64_t event_id) { PixmapPtr pixmap = screen->GetScreenPixmap(screen); ScrnInfoPtr scrn = xf86ScreenToScrn(screen); nouveau_present_flip_exec(scrn, event_id, 0, 0, pixmap, TRUE); } void nouveau_present_fini(ScreenPtr screen) { ScrnInfoPtr scrn = xf86ScreenToScrn(screen); NVPtr pNv = NVPTR(scrn); if (pNv->present) { free(pNv->present); pNv->present = NULL; } } int nouveau_present_init(ScreenPtr screen) { ScrnInfoPtr scrn = xf86ScreenToScrn(screen); NVPtr pNv = NVPTR(scrn); struct nouveau_present *present; uint64_t value; int ret; if (pNv->AccelMethod != GLAMOR) return -ENOSYS; present = pNv->present = calloc(1, sizeof(*present)); if (!present) return -ENOMEM; present->info.version = PRESENT_SCREEN_INFO_VERSION; present->info.get_crtc = nouveau_present_crtc; present->info.get_ust_msc = nouveau_present_ust_msc; present->info.queue_vblank = nouveau_present_vblank_queue; present->info.abort_vblank = nouveau_present_vblank_abort; present->info.flush = nouveau_present_flush; if (pNv->has_pageflip) { #ifdef DRM_CAP_ASYNC_PAGE_FLIP ret = drmGetCap(pNv->dev->fd, DRM_CAP_ASYNC_PAGE_FLIP, &value); if (ret == 0 && value == 1) present->info.capabilities |= PresentCapabilityAsync; #endif present->info.check_flip = nouveau_present_flip_check; present->info.flip = nouveau_present_flip_next; present->info.unflip = nouveau_present_flip_stop; } return present_screen_init(screen, &present->info); } #endif