diff options
author | Alex Deucher <alexander.deucher@amd.com> | 2015-04-20 11:57:52 -0400 |
---|---|---|
committer | Alex Deucher <alexander.deucher@amd.com> | 2015-04-20 11:57:52 -0400 |
commit | ff62bf6e9dce55dbde92baf4fa30193c7344ee8a (patch) | |
tree | efcbe45256256ae8cf1ada3761b0e744a13991b0 /src/amdgpu_dri2.c |
amdgpu: add the xf86-video-amdgpu driver
This adds the new xf86-video-amdgpu driver for
newer AMD GPUs.
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Diffstat (limited to 'src/amdgpu_dri2.c')
-rw-r--r-- | src/amdgpu_dri2.c | 1583 |
1 files changed, 1583 insertions, 0 deletions
diff --git a/src/amdgpu_dri2.c b/src/amdgpu_dri2.c new file mode 100644 index 0000000..26906d3 --- /dev/null +++ b/src/amdgpu_dri2.c @@ -0,0 +1,1583 @@ +/* + * Copyright 2008 Kristian Høgsberg + * Copyright 2008 Jérôme Glisse + * + * All Rights Reserved. + * + * 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 on 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 + * NON-INFRINGEMENT. IN NO EVENT SHALL ATI, VA LINUX SYSTEMS AND/OR + * THEIR SUPPLIERS 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amdgpu_drv.h" +#include "amdgpu_dri2.h" +#include "amdgpu_video.h" +#include "amdgpu_pixmap.h" + +#ifdef DRI2 + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include <gbm.h> + +#include "amdgpu_version.h" + +#if HAVE_LIST_H +#include "list.h" +#if !HAVE_XORG_LIST +#define xorg_list list +#define xorg_list_init list_init +#define xorg_list_add list_add +#define xorg_list_del list_del +#define xorg_list_for_each_entry list_for_each_entry +#endif +#endif + +#if DRI2INFOREC_VERSION >= 4 && HAVE_LIST_H +#define USE_DRI2_SCHEDULING +#endif + +#if DRI2INFOREC_VERSION >= 9 +#define USE_DRI2_PRIME +#endif + +#define FALLBACK_SWAP_DELAY 16 + +#include <glamor.h> + +typedef DRI2BufferPtr BufferPtr; + +struct dri2_buffer_priv { + PixmapPtr pixmap; + unsigned int attachment; + unsigned int refcnt; +}; + +static PixmapPtr get_drawable_pixmap(DrawablePtr drawable) +{ + if (drawable->type == DRAWABLE_PIXMAP) + return (PixmapPtr) drawable; + else + return (*drawable->pScreen-> + GetWindowPixmap) ((WindowPtr) drawable); +} + +static PixmapPtr fixup_glamor(DrawablePtr drawable, PixmapPtr pixmap) +{ + PixmapPtr old = get_drawable_pixmap(drawable); + ScreenPtr screen = drawable->pScreen; + struct amdgpu_pixmap *priv = amdgpu_get_pixmap_private(pixmap); + GCPtr gc; + + /* With a glamor pixmap, 2D pixmaps are created in texture + * and without a static BO attached to it. To support DRI, + * we need to create a new textured-drm pixmap and + * need to copy the original content to this new textured-drm + * pixmap, and then convert the old pixmap to a coherent + * textured-drm pixmap which has a valid BO attached to it + * and also has a valid texture, thus both glamor and DRI2 + * can access it. + * + */ + + /* Copy the current contents of the pixmap to the bo. */ + gc = GetScratchGC(drawable->depth, screen); + if (gc) { + ValidateGC(&pixmap->drawable, gc); + gc->ops->CopyArea(&old->drawable, &pixmap->drawable, + gc, + 0, 0, + old->drawable.width, + old->drawable.height, 0, 0); + FreeScratchGC(gc); + } + + amdgpu_set_pixmap_private(pixmap, NULL); + + /* And redirect the pixmap to the new bo (for 3D). */ + glamor_egl_exchange_buffers(old, pixmap); + amdgpu_set_pixmap_private(old, priv); + screen->DestroyPixmap(pixmap); + old->refcnt++; + + screen->ModifyPixmapHeader(old, + old->drawable.width, + old->drawable.height, + 0, 0, priv->stride, NULL); + + return old; +} + +static BufferPtr +amdgpu_dri2_create_buffer2(ScreenPtr pScreen, + DrawablePtr drawable, + unsigned int attachment, unsigned int format) +{ + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + AMDGPUInfoPtr info = AMDGPUPTR(pScrn); + BufferPtr buffers; + struct dri2_buffer_priv *privates; + PixmapPtr pixmap, depth_pixmap; + struct amdgpu_buffer *bo = NULL; + unsigned front_width; + unsigned aligned_width = drawable->width; + unsigned height = drawable->height; + Bool is_glamor_pixmap = FALSE; + int depth; + int cpp; + + if (format) { + depth = format; + + switch (depth) { + case 15: + cpp = 2; + break; + case 24: + cpp = 4; + break; + default: + cpp = depth / 8; + } + } else { + depth = drawable->depth; + cpp = drawable->bitsPerPixel / 8; + } + + pixmap = pScreen->GetScreenPixmap(pScreen); + front_width = pixmap->drawable.width; + + pixmap = depth_pixmap = NULL; + + if (attachment == DRI2BufferFrontLeft) { + pixmap = get_drawable_pixmap(drawable); + if (pScreen != pixmap->drawable.pScreen) + pixmap = NULL; + else if (info->use_glamor && !amdgpu_get_pixmap_bo(pixmap)) { + is_glamor_pixmap = TRUE; + aligned_width = pixmap->drawable.width; + height = pixmap->drawable.height; + pixmap = NULL; + } else + pixmap->refcnt++; + } else if (attachment == DRI2BufferStencil && depth_pixmap) { + pixmap = depth_pixmap; + pixmap->refcnt++; + } + + if (!pixmap && (is_glamor_pixmap || attachment != DRI2BufferFrontLeft)) { + if (aligned_width == front_width) + aligned_width = pScrn->virtualX; + + pixmap = (*pScreen->CreatePixmap) (pScreen, + aligned_width, + height, + depth, + AMDGPU_CREATE_PIXMAP_DRI2); + } + + buffers = calloc(1, sizeof *buffers); + if (buffers == NULL) + goto error; + + if (attachment == DRI2BufferDepth) { + depth_pixmap = pixmap; + } + + if (pixmap) { + struct drm_gem_flink flink; + union gbm_bo_handle bo_handle; + + if (is_glamor_pixmap) + pixmap = fixup_glamor(drawable, pixmap); + bo = amdgpu_get_pixmap_bo(pixmap); + if (!bo) { + goto error; + } + + if (bo->flags & AMDGPU_BO_FLAGS_GBM) { + bo_handle = gbm_bo_get_handle(bo->bo.gbm); + flink.handle = bo_handle.u32; + if (ioctl(info->dri2.drm_fd, DRM_IOCTL_GEM_FLINK, &flink) < 0) + goto error; + buffers->name = flink.name; + } else { + amdgpu_bo_export(bo->bo.amdgpu, + amdgpu_bo_handle_type_gem_flink_name, + &buffers->name); + } + } + + privates = calloc(1, sizeof(struct dri2_buffer_priv)); + if (privates == NULL) + goto error; + + buffers->attachment = attachment; + if (pixmap) { + buffers->pitch = pixmap->devKind; + buffers->cpp = cpp; + } + buffers->driverPrivate = privates; + buffers->format = format; + buffers->flags = 0; /* not tiled */ + privates->pixmap = pixmap; + privates->attachment = attachment; + privates->refcnt = 1; + + return buffers; + +error: + free(buffers); + if (pixmap) + (*pScreen->DestroyPixmap) (pixmap); + return NULL; +} + +DRI2BufferPtr +amdgpu_dri2_create_buffer(DrawablePtr pDraw, unsigned int attachment, + unsigned int format) +{ + return amdgpu_dri2_create_buffer2(pDraw->pScreen, pDraw, + attachment, format); +} + +static void +amdgpu_dri2_destroy_buffer2(ScreenPtr pScreen, + DrawablePtr drawable, BufferPtr buffers) +{ + if (buffers) { + struct dri2_buffer_priv *private = buffers->driverPrivate; + + /* Trying to free an already freed buffer is unlikely to end well */ + if (private->refcnt == 0) { + ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); + + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "Attempted to destroy previously destroyed buffer.\ + This is a programming error\n"); + return; + } + + private->refcnt--; + if (private->refcnt == 0) { + if (private->pixmap) + (*pScreen->DestroyPixmap) (private->pixmap); + + free(buffers->driverPrivate); + free(buffers); + } + } +} + +void amdgpu_dri2_destroy_buffer(DrawablePtr pDraw, DRI2BufferPtr buf) +{ + amdgpu_dri2_destroy_buffer2(pDraw->pScreen, pDraw, buf); +} + +static inline PixmapPtr GetDrawablePixmap(DrawablePtr drawable) +{ + if (drawable->type == DRAWABLE_PIXMAP) + return (PixmapPtr) drawable; + else { + struct _Window *pWin = (struct _Window *)drawable; + return drawable->pScreen->GetWindowPixmap(pWin); + } +} + +static void +amdgpu_dri2_copy_region2(ScreenPtr pScreen, + DrawablePtr drawable, + RegionPtr region, + BufferPtr dest_buffer, BufferPtr src_buffer) +{ + struct dri2_buffer_priv *src_private = src_buffer->driverPrivate; + struct dri2_buffer_priv *dst_private = dest_buffer->driverPrivate; + DrawablePtr src_drawable; + DrawablePtr dst_drawable; + RegionPtr copy_clip; + GCPtr gc; + Bool translate = FALSE; + int off_x = 0, off_y = 0; + + src_drawable = &src_private->pixmap->drawable; + dst_drawable = &dst_private->pixmap->drawable; + + if (src_private->attachment == DRI2BufferFrontLeft) { + src_drawable = drawable; + } + if (dst_private->attachment == DRI2BufferFrontLeft) { +#ifdef USE_DRI2_PRIME + if (drawable->pScreen != pScreen) { + dst_drawable = DRI2UpdatePrime(drawable, dest_buffer); + if (!dst_drawable) + return; + if (dst_drawable != drawable) + translate = TRUE; + } else +#endif + dst_drawable = drawable; + } + + if (translate && drawable->type == DRAWABLE_WINDOW) { + PixmapPtr pPix = GetDrawablePixmap(drawable); + + off_x = drawable->x - pPix->screen_x; + off_y = drawable->y - pPix->screen_y; + } + gc = GetScratchGC(dst_drawable->depth, pScreen); + copy_clip = REGION_CREATE(pScreen, NULL, 0); + REGION_COPY(pScreen, copy_clip, region); + + if (translate) { + REGION_TRANSLATE(pScreen, copy_clip, off_x, off_y); + } + + (*gc->funcs->ChangeClip) (gc, CT_REGION, copy_clip, 0); + ValidateGC(dst_drawable, gc); + + /* If this is a full buffer swap or frontbuffer flush, throttle on the + * previous one + */ + if (dst_private->attachment == DRI2BufferFrontLeft) { + if (REGION_NUM_RECTS(region) == 1) { + BoxPtr extents = REGION_EXTENTS(pScreen, region); + + if (extents->x1 == 0 && extents->y1 == 0 && + extents->x2 == drawable->width && + extents->y2 == drawable->height) { + char pixel[4]; + + /* XXX: This is a pretty big hammer... */ + pScreen->GetImage(drawable, 0, 0, 1, 1, + ZPixmap, ~0, pixel); + } + } + } + + (*gc->ops->CopyArea) (src_drawable, dst_drawable, gc, + 0, 0, drawable->width, drawable->height, off_x, + off_y); + + FreeScratchGC(gc); +} + +void +amdgpu_dri2_copy_region(DrawablePtr pDraw, RegionPtr pRegion, + DRI2BufferPtr pDstBuffer, DRI2BufferPtr pSrcBuffer) +{ + return amdgpu_dri2_copy_region2(pDraw->pScreen, pDraw, pRegion, + pDstBuffer, pSrcBuffer); +} + +#ifdef USE_DRI2_SCHEDULING + +enum DRI2FrameEventType { + DRI2_SWAP, + DRI2_FLIP, + DRI2_WAITMSC, +}; + +typedef struct _DRI2FrameEvent { + XID drawable_id; + ClientPtr client; + enum DRI2FrameEventType type; + int frame; + xf86CrtcPtr crtc; + + /* for swaps & flips only */ + DRI2SwapEventPtr event_complete; + void *event_data; + DRI2BufferPtr front; + DRI2BufferPtr back; + + Bool valid; + + struct xorg_list link; +} DRI2FrameEventRec, *DRI2FrameEventPtr; + +typedef struct _DRI2ClientEvents { + struct xorg_list reference_list; +} DRI2ClientEventsRec, *DRI2ClientEventsPtr; + +#if HAS_DEVPRIVATEKEYREC + +static int DRI2InfoCnt; + +static DevPrivateKeyRec DRI2ClientEventsPrivateKeyRec; +#define DRI2ClientEventsPrivateKey (&DRI2ClientEventsPrivateKeyRec) + +#else + +static int DRI2ClientEventsPrivateKeyIndex; +DevPrivateKey DRI2ClientEventsPrivateKey = &DRI2ClientEventsPrivateKeyIndex; + +#endif /* HAS_DEVPRIVATEKEYREC */ + +#define GetDRI2ClientEvents(pClient) ((DRI2ClientEventsPtr) \ + dixLookupPrivate(&(pClient)->devPrivates, DRI2ClientEventsPrivateKey)) + +static int ListAddDRI2ClientEvents(ClientPtr client, struct xorg_list *entry) +{ + DRI2ClientEventsPtr pClientPriv; + pClientPriv = GetDRI2ClientEvents(client); + + if (!pClientPriv) { + return BadAlloc; + } + + xorg_list_add(entry, &pClientPriv->reference_list); + return 0; +} + +static void ListDelDRI2ClientEvents(ClientPtr client, struct xorg_list *entry) +{ + DRI2ClientEventsPtr pClientPriv; + pClientPriv = GetDRI2ClientEvents(client); + + if (!pClientPriv) { + return; + } + xorg_list_del(entry); +} + +static void amdgpu_dri2_ref_buffer(BufferPtr buffer) +{ + struct dri2_buffer_priv *private = buffer->driverPrivate; + private->refcnt++; +} + +static void amdgpu_dri2_unref_buffer(BufferPtr buffer) +{ + if (buffer) { + struct dri2_buffer_priv *private = buffer->driverPrivate; + amdgpu_dri2_destroy_buffer(&(private->pixmap->drawable), + buffer); + } +} + +static void +amdgpu_dri2_client_state_changed(CallbackListPtr * ClientStateCallback, + pointer data, pointer calldata) +{ + DRI2ClientEventsPtr pClientEventsPriv; + DRI2FrameEventPtr ref; + NewClientInfoRec *clientinfo = calldata; + ClientPtr pClient = clientinfo->client; + pClientEventsPriv = GetDRI2ClientEvents(pClient); + + switch (pClient->clientState) { + case ClientStateInitial: + xorg_list_init(&pClientEventsPriv->reference_list); + break; + case ClientStateRunning: + break; + + case ClientStateRetained: + case ClientStateGone: + if (pClientEventsPriv) { + xorg_list_for_each_entry(ref, + &pClientEventsPriv-> + reference_list, link) { + ref->valid = FALSE; + amdgpu_dri2_unref_buffer(ref->front); + amdgpu_dri2_unref_buffer(ref->back); + } + } + break; + default: + break; + } +} + +static +xf86CrtcPtr amdgpu_dri2_drawable_crtc(DrawablePtr pDraw, Bool consider_disabled) +{ + ScreenPtr pScreen = pDraw->pScreen; + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + xf86CrtcPtr crtc; + + crtc = amdgpu_pick_best_crtc(pScrn, consider_disabled, + pDraw->x, + pDraw->x + pDraw->width, + pDraw->y, pDraw->y + pDraw->height); + + /* Make sure the CRTC is valid and this is the real front buffer */ + if (crtc != NULL && !crtc->rotatedData) + return crtc; + else + return NULL; +} + +static Bool +amdgpu_dri2_schedule_flip(ScrnInfoPtr scrn, ClientPtr client, + DrawablePtr draw, DRI2BufferPtr front, + DRI2BufferPtr back, DRI2SwapEventPtr func, + void *data, unsigned int target_msc) +{ + struct dri2_buffer_priv *back_priv; + struct amdgpu_buffer *bo = NULL; + DRI2FrameEventPtr flip_info; + /* Main crtc for this drawable shall finally deliver pageflip event. */ + xf86CrtcPtr crtc = amdgpu_dri2_drawable_crtc(draw, FALSE); + int ref_crtc_hw_id = crtc ? drmmode_get_crtc_id(crtc) : -1; + + flip_info = calloc(1, sizeof(DRI2FrameEventRec)); + if (!flip_info) + return FALSE; + + flip_info->drawable_id = draw->id; + flip_info->client = client; + flip_info->type = DRI2_SWAP; + flip_info->event_complete = func; + flip_info->event_data = data; + flip_info->frame = target_msc; + flip_info->crtc = crtc; + + xf86DrvMsgVerb(scrn->scrnIndex, X_INFO, AMDGPU_LOGLEVEL_DEBUG, + "%s:%d fevent[%p]\n", __func__, __LINE__, flip_info); + + /* Page flip the full screen buffer */ + back_priv = back->driverPrivate; + bo = amdgpu_get_pixmap_bo(back_priv->pixmap); + + return amdgpu_do_pageflip(scrn, bo, flip_info, ref_crtc_hw_id); +} + +static Bool update_front(DrawablePtr draw, DRI2BufferPtr front) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + AMDGPUInfoPtr info = AMDGPUPTR(scrn); + PixmapPtr pixmap; + struct dri2_buffer_priv *priv = front->driverPrivate; + struct amdgpu_buffer *bo = NULL; + union gbm_bo_handle bo_handle; + struct drm_gem_flink flink; + + pixmap = get_drawable_pixmap(draw); + pixmap->refcnt++; + + bo = amdgpu_get_pixmap_bo(pixmap); + if (bo->flags & AMDGPU_BO_FLAGS_GBM) { + bo_handle = gbm_bo_get_handle(bo->bo.gbm); + flink.handle = bo_handle.u32; + if (ioctl(info->dri2.drm_fd, DRM_IOCTL_GEM_FLINK, &flink) < 0) + return FALSE; + front->name = flink.name; + } else { + amdgpu_bo_export(bo->bo.amdgpu, + amdgpu_bo_handle_type_gem_flink_name, + &front->name); + } + (*draw->pScreen->DestroyPixmap) (priv->pixmap); + front->pitch = pixmap->devKind; + front->cpp = pixmap->drawable.bitsPerPixel / 8; + priv->pixmap = pixmap; + + return TRUE; +} + +static Bool +can_exchange(ScrnInfoPtr pScrn, DrawablePtr draw, + DRI2BufferPtr front, DRI2BufferPtr back) +{ + struct dri2_buffer_priv *front_priv = front->driverPrivate; + struct dri2_buffer_priv *back_priv = back->driverPrivate; + PixmapPtr front_pixmap; + PixmapPtr back_pixmap = back_priv->pixmap; + xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn); + int i; + + for (i = 0; i < xf86_config->num_crtc; i++) { + xf86CrtcPtr crtc = xf86_config->crtc[i]; + if (crtc->enabled && crtc->rotatedData) + return FALSE; + } + + if (!update_front(draw, front)) + return FALSE; + + front_pixmap = front_priv->pixmap; + + if (front_pixmap->drawable.width != back_pixmap->drawable.width) + return FALSE; + + if (front_pixmap->drawable.height != back_pixmap->drawable.height) + return FALSE; + + if (front_pixmap->drawable.bitsPerPixel != + back_pixmap->drawable.bitsPerPixel) + return FALSE; + + if (front_pixmap->devKind != back_pixmap->devKind) + return FALSE; + + return TRUE; +} + +static Bool +can_flip(ScrnInfoPtr pScrn, DrawablePtr draw, + DRI2BufferPtr front, DRI2BufferPtr back) +{ + return draw->type == DRAWABLE_WINDOW && + AMDGPUPTR(pScrn)->allowPageFlip && + pScrn->vtSema && + DRI2CanFlip(draw) && can_exchange(pScrn, draw, front, back); +} + +static void +amdgpu_dri2_exchange_buffers(DrawablePtr draw, DRI2BufferPtr front, + DRI2BufferPtr back) +{ + struct dri2_buffer_priv *front_priv = front->driverPrivate; + struct dri2_buffer_priv *back_priv = back->driverPrivate; + struct amdgpu_buffer *front_bo = NULL; + struct amdgpu_buffer *back_bo = NULL; + ScreenPtr screen; + AMDGPUInfoPtr info; + RegionRec region; + int tmp; + + region.extents.x1 = region.extents.y1 = 0; + region.extents.x2 = front_priv->pixmap->drawable.width; + region.extents.y2 = front_priv->pixmap->drawable.width; + region.data = NULL; + DamageRegionAppend(&front_priv->pixmap->drawable, ®ion); + + /* Swap BO names so DRI works */ + tmp = front->name; + front->name = back->name; + back->name = tmp; + + /* Swap pixmap bos */ + front_bo = amdgpu_get_pixmap_bo(front_priv->pixmap); + back_bo = amdgpu_get_pixmap_bo(back_priv->pixmap); + amdgpu_set_pixmap_bo(front_priv->pixmap, back_bo); + amdgpu_set_pixmap_bo(back_priv->pixmap, front_bo); + + /* Do we need to update the Screen? */ + screen = draw->pScreen; + info = AMDGPUPTR(xf86ScreenToScrn(screen)); + if (front_bo == info->front_buffer) { + amdgpu_bo_ref(back_bo); + amdgpu_bo_unref(&info->front_buffer); + info->front_buffer = back_bo; + amdgpu_set_pixmap_bo(screen->GetScreenPixmap(screen), back_bo); + } + + amdgpu_glamor_exchange_buffers(front_priv->pixmap, back_priv->pixmap); + + DamageRegionProcessPending(&front_priv->pixmap->drawable); +} + +void amdgpu_dri2_frame_event_handler(unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data) +{ + DRI2FrameEventPtr event = event_data; + DrawablePtr drawable; + ScreenPtr screen; + ScrnInfoPtr scrn; + int status; + int swap_type; + BoxRec box; + RegionRec region; + + if (!event->valid) + goto cleanup; + + status = dixLookupDrawable(&drawable, event->drawable_id, serverClient, + M_ANY, DixWriteAccess); + if (status != Success) + goto cleanup; + if (!event->crtc) + goto cleanup; + frame += amdgpu_get_interpolated_vblanks(event->crtc); + + screen = drawable->pScreen; + scrn = xf86ScreenToScrn(screen); + + switch (event->type) { + case DRI2_FLIP: + if (can_flip(scrn, drawable, event->front, event->back) && + amdgpu_dri2_schedule_flip(scrn, + event->client, + drawable, + event->front, + event->back, + event->event_complete, + event->event_data, + event->frame)) { + amdgpu_dri2_exchange_buffers(drawable, event->front, + event->back); + break; + } + /* else fall through to exchange/blit */ + case DRI2_SWAP: + if (DRI2CanExchange(drawable) && + can_exchange(scrn, drawable, event->front, event->back)) { + amdgpu_dri2_exchange_buffers(drawable, event->front, + event->back); + swap_type = DRI2_EXCHANGE_COMPLETE; + } else { + box.x1 = 0; + box.y1 = 0; + box.x2 = drawable->width; + box.y2 = drawable->height; + REGION_INIT(pScreen, ®ion, &box, 0); + amdgpu_dri2_copy_region(drawable, ®ion, event->front, + event->back); + swap_type = DRI2_BLIT_COMPLETE; + } + + DRI2SwapComplete(event->client, drawable, frame, tv_sec, + tv_usec, swap_type, event->event_complete, + event->event_data); + + break; + case DRI2_WAITMSC: + DRI2WaitMSCComplete(event->client, drawable, frame, tv_sec, + tv_usec); + break; + default: + /* Unknown type */ + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: unknown vblank event received\n", __func__); + break; + } + +cleanup: + if (event->valid) { + amdgpu_dri2_unref_buffer(event->front); + amdgpu_dri2_unref_buffer(event->back); + ListDelDRI2ClientEvents(event->client, &event->link); + } + free(event); +} + +drmVBlankSeqType amdgpu_populate_vbl_request_type(xf86CrtcPtr crtc) +{ + drmVBlankSeqType type = 0; + int crtc_id = drmmode_get_crtc_id(crtc); + + if (crtc_id == 1) + type |= DRM_VBLANK_SECONDARY; + else if (crtc_id > 1) +#ifdef DRM_VBLANK_HIGH_CRTC_SHIFT + type |= (crtc_id << DRM_VBLANK_HIGH_CRTC_SHIFT) & + DRM_VBLANK_HIGH_CRTC_MASK; +#else + ErrorF("amdgpu driver bug: %s called for CRTC %d > 1, but " + "DRM_VBLANK_HIGH_CRTC_MASK not defined at build time\n", + __func__, crtc_id); +#endif + + return type; +} + +/* + * This function should be called on a disabled CRTC only (i.e., CRTC + * in DPMS-off state). It will calculate the delay necessary to reach + * target_msc from present time if the CRTC were running. + */ +static +CARD32 amdgpu_dri2_extrapolate_msc_delay(xf86CrtcPtr crtc, CARD64 * target_msc, + CARD64 divisor, CARD64 remainder) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + ScrnInfoPtr pScrn = crtc->scrn; + AMDGPUInfoPtr info = AMDGPUPTR(pScrn); + int nominal_frame_rate = drmmode_crtc->dpms_last_fps; + CARD64 last_vblank_ust = drmmode_crtc->dpms_last_ust; + uint32_t last_vblank_seq = drmmode_crtc->dpms_last_seq; + int interpolated_vblanks = drmmode_crtc->interpolated_vblanks; + int target_seq; + CARD64 now, target_time, delta_t; + int64_t d, delta_seq; + int ret; + CARD32 d_ms; + + if (!last_vblank_ust) { + *target_msc = 0; + return FALLBACK_SWAP_DELAY; + } + ret = drmmode_get_current_ust(info->dri2.drm_fd, &now); + if (ret) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "%s cannot get current time\n", __func__); + *target_msc = 0; + return FALLBACK_SWAP_DELAY; + } + target_seq = (int)*target_msc - interpolated_vblanks; + delta_seq = (int64_t) target_seq - (int64_t) last_vblank_seq; + delta_seq *= 1000000; + target_time = last_vblank_ust; + target_time += delta_seq / nominal_frame_rate; + d = target_time - now; + if (d < 0) { + /* we missed the event, adjust target_msc, do the divisor magic */ + CARD64 current_msc; + current_msc = last_vblank_seq + interpolated_vblanks; + delta_t = now - last_vblank_ust; + delta_seq = delta_t * nominal_frame_rate; + current_msc += delta_seq / 1000000; + current_msc &= 0xffffffff; + if (divisor == 0) { + *target_msc = current_msc; + d = 0; + } else { + *target_msc = + current_msc - (current_msc % divisor) + remainder; + if ((current_msc % divisor) >= remainder) + *target_msc += divisor; + *target_msc &= 0xffffffff; + target_seq = (int)*target_msc - interpolated_vblanks; + delta_seq = + (int64_t) target_seq - (int64_t) last_vblank_seq; + delta_seq *= 1000000; + target_time = last_vblank_ust; + target_time += delta_seq / nominal_frame_rate; + d = target_time - now; + } + } + /* + * convert delay to milliseconds and add margin to prevent the client + * from coming back early (due to timer granularity and rounding + * errors) and getting the same MSC it just got + */ + d_ms = (CARD32) d / 1000; + if ((CARD32) d - d_ms * 1000 > 0) + d_ms += 2; + else + d_ms++; + return d_ms; +} + +/* + * Get current frame count and frame count timestamp, based on drawable's + * crtc. + */ +static int amdgpu_dri2_get_msc(DrawablePtr draw, CARD64 * ust, CARD64 * msc) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + AMDGPUInfoPtr info = AMDGPUPTR(scrn); + drmVBlank vbl; + int ret; + xf86CrtcPtr crtc = amdgpu_dri2_drawable_crtc(draw, TRUE); + + /* Drawable not displayed, make up a value */ + if (crtc == NULL) { + *ust = 0; + *msc = 0; + return TRUE; + } + if (amdgpu_crtc_is_enabled(crtc)) { + /* CRTC is running, read vblank counter and timestamp */ + vbl.request.type = DRM_VBLANK_RELATIVE; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + vbl.request.sequence = 0; + + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "get vblank counter failed: %s\n", + strerror(errno)); + return FALSE; + } + + *ust = + ((CARD64) vbl.reply.tval_sec * 1000000) + + vbl.reply.tval_usec; + *msc = + vbl.reply.sequence + amdgpu_get_interpolated_vblanks(crtc); + *msc &= 0xffffffff; + } else { + /* CRTC is not running, extrapolate MSC and timestamp */ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + CARD64 now, delta_t, delta_seq; + + if (!drmmode_crtc->dpms_last_ust) + return FALSE; + ret = drmmode_get_current_ust(info->dri2.drm_fd, &now); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "%s cannot get current time\n", __func__); + return FALSE; + } + delta_t = now - drmmode_crtc->dpms_last_ust; + delta_seq = delta_t * drmmode_crtc->dpms_last_fps; + delta_seq /= 1000000; + *ust = drmmode_crtc->dpms_last_ust; + delta_t = delta_seq * 1000000; + delta_t /= drmmode_crtc->dpms_last_fps; + *ust += delta_t; + *msc = drmmode_crtc->dpms_last_seq; + *msc += drmmode_crtc->interpolated_vblanks; + *msc += delta_seq; + *msc &= 0xffffffff; + } + return TRUE; +} + +static +CARD32 amdgpu_dri2_deferred_event(OsTimerPtr timer, CARD32 now, pointer data) +{ + DRI2FrameEventPtr event_info = (DRI2FrameEventPtr) data; + DrawablePtr drawable; + ScreenPtr screen; + ScrnInfoPtr scrn; + AMDGPUInfoPtr info; + int status; + CARD64 drm_now; + int ret; + unsigned int tv_sec, tv_usec; + CARD64 delta_t, delta_seq, frame; + drmmode_crtc_private_ptr drmmode_crtc; + TimerFree(timer); + + /* + * This is emulated event, so its time is current time, which we + * have to get in DRM-compatible form (which is a bit messy given + * the information that we have at this point). Can't use now argument + * because DRM event time may come from monotonic clock, while + * DIX timer facility uses real-time clock. + */ + if (!event_info->crtc) { + ErrorF("%s no crtc\n", __func__); + amdgpu_dri2_frame_event_handler(0, 0, 0, data); + return 0; + } + status = + dixLookupDrawable(&drawable, event_info->drawable_id, serverClient, + M_ANY, DixWriteAccess); + if (status != Success) { + ErrorF("%s cannot lookup drawable\n", __func__); + amdgpu_dri2_frame_event_handler(0, 0, 0, data); + return 0; + } + screen = drawable->pScreen; + scrn = xf86ScreenToScrn(screen); + info = AMDGPUPTR(scrn); + ret = drmmode_get_current_ust(info->dri2.drm_fd, &drm_now); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "%s cannot get current time\n", __func__); + amdgpu_dri2_frame_event_handler(0, 0, 0, data); + return 0; + } + tv_sec = (unsigned int)(drm_now / 1000000); + tv_usec = (unsigned int)(drm_now - (CARD64) tv_sec * 1000000); + /* + * calculate the frame number from current time + * that would come from CRTC if it were running + */ + drmmode_crtc = event_info->crtc->driver_private; + delta_t = drm_now - (CARD64) drmmode_crtc->dpms_last_ust; + delta_seq = delta_t * drmmode_crtc->dpms_last_fps; + delta_seq /= 1000000; + frame = (CARD64) drmmode_crtc->dpms_last_seq + delta_seq; + frame &= 0xffffffff; + amdgpu_dri2_frame_event_handler((unsigned int)frame, tv_sec, tv_usec, + data); + return 0; +} + +static +void amdgpu_dri2_schedule_event(CARD32 delay, pointer arg) +{ + OsTimerPtr timer; + + timer = TimerSet(NULL, 0, delay, amdgpu_dri2_deferred_event, arg); + if (delay == 0) { + CARD32 now = GetTimeInMillis(); + amdgpu_dri2_deferred_event(timer, now, arg); + } +} + +/* + * 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 amdgpu_dri2_schedule_wait_msc(ClientPtr client, DrawablePtr draw, + CARD64 target_msc, CARD64 divisor, + CARD64 remainder) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + AMDGPUInfoPtr info = AMDGPUPTR(scrn); + DRI2FrameEventPtr wait_info = NULL; + xf86CrtcPtr crtc = amdgpu_dri2_drawable_crtc(draw, TRUE); + drmVBlank vbl; + int ret; + CARD64 current_msc; + + /* Truncate to match kernel interfaces; means occasional overflow + * misses, but that's generally not a big deal */ + target_msc &= 0xffffffff; + divisor &= 0xffffffff; + remainder &= 0xffffffff; + + /* Drawable not visible, return immediately */ + if (crtc == NULL) + goto out_complete; + + wait_info = calloc(1, sizeof(DRI2FrameEventRec)); + if (!wait_info) + goto out_complete; + + wait_info->drawable_id = draw->id; + wait_info->client = client; + wait_info->type = DRI2_WAITMSC; + wait_info->valid = TRUE; + wait_info->crtc = crtc; + + if (ListAddDRI2ClientEvents(client, &wait_info->link)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "add events to client private failed.\n"); + free(wait_info); + wait_info = NULL; + goto out_complete; + } + + /* + * CRTC is in DPMS off state, calculate wait time from current time, + * target_msc and last vblank time/sequence when CRTC was turned off + */ + if (!amdgpu_crtc_is_enabled(crtc)) { + CARD32 delay; + delay = amdgpu_dri2_extrapolate_msc_delay(crtc, &target_msc, + divisor, remainder); + wait_info->frame = target_msc; + amdgpu_dri2_schedule_event(delay, wait_info); + DRI2BlockClient(client, draw); + return TRUE; + } + + /* Get current count */ + vbl.request.type = DRM_VBLANK_RELATIVE; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + vbl.request.sequence = 0; + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "get vblank counter failed: %s\n", strerror(errno)); + goto out_complete; + } + + current_msc = + vbl.reply.sequence + amdgpu_get_interpolated_vblanks(crtc); + current_msc &= 0xffffffff; + + /* + * 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. + */ + if (current_msc >= target_msc) + target_msc = current_msc; + vbl.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + vbl.request.sequence = target_msc; + vbl.request.sequence -= amdgpu_get_interpolated_vblanks(crtc); + vbl.request.signal = (unsigned long)wait_info; + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "get vblank counter failed: %s\n", + strerror(errno)); + goto out_complete; + } + + wait_info->frame = vbl.reply.sequence; + wait_info->frame += amdgpu_get_interpolated_vblanks(crtc); + 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; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + + vbl.request.sequence = 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) + vbl.request.sequence += divisor; + vbl.request.sequence -= amdgpu_get_interpolated_vblanks(crtc); + + vbl.request.signal = (unsigned long)wait_info; + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "get vblank counter failed: %s\n", strerror(errno)); + goto out_complete; + } + + wait_info->frame = vbl.reply.sequence; + wait_info->frame += amdgpu_get_interpolated_vblanks(crtc); + DRI2BlockClient(client, draw); + + return TRUE; + +out_complete: + if (wait_info) { + ListDelDRI2ClientEvents(wait_info->client, &wait_info->link); + free(wait_info); + } + DRI2WaitMSCComplete(client, draw, target_msc, 0, 0); + return TRUE; +} + +void amdgpu_dri2_flip_event_handler(unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data) +{ + DRI2FrameEventPtr flip = event_data; + DrawablePtr drawable; + ScreenPtr screen; + ScrnInfoPtr scrn; + int status; + PixmapPtr pixmap; + + status = dixLookupDrawable(&drawable, flip->drawable_id, serverClient, + M_ANY, DixWriteAccess); + if (status != Success) { + free(flip); + return; + } + if (!flip->crtc) { + free(flip); + return; + } + frame += amdgpu_get_interpolated_vblanks(flip->crtc); + + screen = drawable->pScreen; + scrn = xf86ScreenToScrn(screen); + + pixmap = screen->GetScreenPixmap(screen); + xf86DrvMsgVerb(scrn->scrnIndex, X_INFO, AMDGPU_LOGLEVEL_DEBUG, + "%s:%d fevent[%p] width %d pitch %d (/4 %d)\n", + __func__, __LINE__, flip, pixmap->drawable.width, + pixmap->devKind, pixmap->devKind / 4); + + /* We assume our flips arrive in order, so we don't check the frame */ + switch (flip->type) { + case DRI2_SWAP: + /* Check for too small vblank count of pageflip completion, taking wraparound + * into account. This usually means some defective kms pageflip completion, + * causing wrong (msc, ust) return values and possible visual corruption. + */ + if ((frame < flip->frame) && (flip->frame - frame < 5)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: Pageflip completion event has impossible msc %d < target_msc %d\n", + __func__, frame, flip->frame); + /* All-Zero values signal failure of (msc, ust) timestamping to client. */ + frame = tv_sec = tv_usec = 0; + } + + DRI2SwapComplete(flip->client, drawable, frame, tv_sec, tv_usec, + DRI2_FLIP_COMPLETE, flip->event_complete, + flip->event_data); + break; + default: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: unknown vblank event received\n", __func__); + /* Unknown type */ + break; + } + + free(flip); +} + +/* + * ScheduleSwap is responsible for requesting a DRM vblank event for the + * appropriate frame. + * + * In the case of a blit (e.g. for a windowed swap) or buffer exchange, + * the vblank requested can simply be the last queued swap frame + the swap + * interval for the drawable. + * + * In the case of a page flip, we request an event for the last queued swap + * frame + swap interval - 1, since we'll need to queue the flip for the frame + * immediately following the received event. + * + * The client will be blocked if it tries to perform further GL commands + * after queueing a swap, though in the Intel case after queueing a flip, the + * client is free to queue more commands; they'll block in the kernel if + * they access buffers busy with the flip. + * + * 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 amdgpu_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); + AMDGPUInfoPtr info = AMDGPUPTR(scrn); + xf86CrtcPtr crtc = amdgpu_dri2_drawable_crtc(draw, TRUE); + drmVBlank vbl; + int ret, flip = 0; + DRI2FrameEventPtr swap_info = NULL; + enum DRI2FrameEventType swap_type = DRI2_SWAP; + CARD64 current_msc; + BoxRec box; + RegionRec region; + + /* Truncate to match kernel interfaces; means occasional overflow + * misses, but that's generally not a big deal */ + *target_msc &= 0xffffffff; + divisor &= 0xffffffff; + remainder &= 0xffffffff; + + /* amdgpu_dri2_frame_event_handler will get called some unknown time in the + * future with these buffers. Take a reference to ensure that they won't + * get destroyed before then. + */ + amdgpu_dri2_ref_buffer(front); + amdgpu_dri2_ref_buffer(back); + + /* either off-screen or CRTC not usable... just complete the swap */ + if (crtc == NULL) + goto blit_fallback; + + swap_info = calloc(1, sizeof(DRI2FrameEventRec)); + if (!swap_info) + goto blit_fallback; + + swap_info->drawable_id = draw->id; + swap_info->client = client; + swap_info->event_complete = func; + swap_info->event_data = data; + swap_info->front = front; + swap_info->back = back; + swap_info->valid = TRUE; + swap_info->crtc = crtc; + if (ListAddDRI2ClientEvents(client, &swap_info->link)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "add events to client private failed.\n"); + free(swap_info); + swap_info = NULL; + goto blit_fallback; + } + + /* + * CRTC is in DPMS off state, fallback to blit, but calculate + * wait time from current time, target_msc and last vblank + * time/sequence when CRTC was turned off + */ + if (!amdgpu_crtc_is_enabled(crtc)) { + CARD32 delay; + delay = amdgpu_dri2_extrapolate_msc_delay(crtc, target_msc, + divisor, remainder); + swap_info->frame = *target_msc; + amdgpu_dri2_schedule_event(delay, swap_info); + return TRUE; + } + + /* Get current count */ + vbl.request.type = DRM_VBLANK_RELATIVE; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + vbl.request.sequence = 0; + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "first get vblank counter failed: %s\n", + strerror(errno)); + *target_msc = 0; + amdgpu_dri2_schedule_event(FALLBACK_SWAP_DELAY, swap_info); + return TRUE; + } + + current_msc = + vbl.reply.sequence + amdgpu_get_interpolated_vblanks(crtc); + current_msc &= 0xffffffff; + + /* Flips need to be submitted one frame before */ + if (can_flip(scrn, draw, front, back)) { + swap_type = DRI2_FLIP; + flip = 1; + } + + swap_info->type = swap_type; + + /* Correct target_msc by 'flip' if swap_type == DRI2_FLIP. + * Do it early, so handling of different timing constraints + * for divisor, remainder and msc vs. target_msc works. + */ + if (*target_msc > 0) + *target_msc -= flip; + + /* + * 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) { + vbl.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT; + /* If non-pageflipping, but blitting/exchanging, we need to use + * DRM_VBLANK_NEXTONMISS to avoid unreliable timestamping later + * on. + */ + if (flip == 0) + vbl.request.type |= DRM_VBLANK_NEXTONMISS; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + + /* 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; + + vbl.request.sequence = *target_msc; + vbl.request.sequence -= amdgpu_get_interpolated_vblanks(crtc); + vbl.request.signal = (unsigned long)swap_info; + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "divisor 0 get vblank counter failed: %s\n", + strerror(errno)); + *target_msc = 0; + amdgpu_dri2_schedule_event(FALLBACK_SWAP_DELAY, + swap_info); + return TRUE; + } + + *target_msc = vbl.reply.sequence + flip; + *target_msc += amdgpu_get_interpolated_vblanks(crtc); + swap_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_EVENT; + if (flip == 0) + vbl.request.type |= DRM_VBLANK_NEXTONMISS; + vbl.request.type |= amdgpu_populate_vbl_request_type(crtc); + + vbl.request.sequence = 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 1 frame swap delay in pageflipping mode + * into account, as well as a potential DRM_VBLANK_NEXTONMISS delay + * if we are blitting/exchanging instead of flipping. + */ + if (vbl.request.sequence <= current_msc) + vbl.request.sequence += divisor; + vbl.request.sequence -= amdgpu_get_interpolated_vblanks(crtc); + + /* Account for 1 frame extra pageflip delay if flip > 0 */ + vbl.request.sequence -= flip; + + vbl.request.signal = (unsigned long)swap_info; + ret = drmWaitVBlank(info->dri2.drm_fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "final get vblank counter failed: %s\n", + strerror(errno)); + *target_msc = 0; + amdgpu_dri2_schedule_event(FALLBACK_SWAP_DELAY, swap_info); + return TRUE; + } + + /* Adjust returned value for 1 fame pageflip offset of flip > 0 */ + *target_msc = vbl.reply.sequence + flip; + *target_msc += amdgpu_get_interpolated_vblanks(crtc); + swap_info->frame = *target_msc; + + return TRUE; + +blit_fallback: + box.x1 = 0; + box.y1 = 0; + box.x2 = draw->width; + box.y2 = draw->height; + REGION_INIT(pScreen, ®ion, &box, 0); + + amdgpu_dri2_copy_region(draw, ®ion, front, back); + + DRI2SwapComplete(client, draw, 0, 0, 0, DRI2_BLIT_COMPLETE, func, data); + if (swap_info) { + ListDelDRI2ClientEvents(swap_info->client, &swap_info->link); + free(swap_info); + } + + amdgpu_dri2_unref_buffer(front); + amdgpu_dri2_unref_buffer(back); + + *target_msc = 0; /* offscreen, so zero out target vblank count */ + return TRUE; +} + +#endif /* USE_DRI2_SCHEDULING */ + +Bool amdgpu_dri2_screen_init(ScreenPtr pScreen) +{ + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + AMDGPUInfoPtr info = AMDGPUPTR(pScrn); + DRI2InfoRec dri2_info = { 0 }; +#ifdef USE_DRI2_SCHEDULING + const char *driverNames[2]; + Bool scheduling_works = TRUE; +#endif + + if (!info->dri2.available) + return FALSE; + + info->dri2.device_name = drmGetDeviceNameFromFd(info->dri2.drm_fd); + + dri2_info.driverName = SI_DRIVER_NAME; + dri2_info.fd = info->dri2.drm_fd; + dri2_info.deviceName = info->dri2.device_name; + dri2_info.version = DRI2INFOREC_VERSION; + dri2_info.CreateBuffer = amdgpu_dri2_create_buffer; + dri2_info.DestroyBuffer = amdgpu_dri2_destroy_buffer; + dri2_info.CopyRegion = amdgpu_dri2_copy_region; + +#ifdef USE_DRI2_SCHEDULING + if (info->drmmode.mode_res->count_crtcs > 2) { +#ifdef DRM_CAP_VBLANK_HIGH_CRTC + uint64_t cap_value; + + if (drmGetCap + (info->dri2.drm_fd, DRM_CAP_VBLANK_HIGH_CRTC, &cap_value)) { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "You need a newer kernel " + "for VBLANKs on CRTC > 1\n"); + scheduling_works = FALSE; + } else if (!cap_value) { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "Your kernel does not " + "handle VBLANKs on CRTC > 1\n"); + scheduling_works = FALSE; + } +#else + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "You need to rebuild against a " + "newer libdrm to handle VBLANKs on CRTC > 1\n"); + scheduling_works = FALSE; +#endif + } + + if (scheduling_works) { + dri2_info.version = 4; + dri2_info.ScheduleSwap = amdgpu_dri2_schedule_swap; + dri2_info.GetMSC = amdgpu_dri2_get_msc; + dri2_info.ScheduleWaitMSC = amdgpu_dri2_schedule_wait_msc; + dri2_info.numDrivers = AMDGPU_ARRAY_SIZE(driverNames); + dri2_info.driverNames = driverNames; + driverNames[0] = driverNames[1] = dri2_info.driverName; + + if (DRI2InfoCnt == 0) { +#if HAS_DIXREGISTERPRIVATEKEY + if (!dixRegisterPrivateKey(DRI2ClientEventsPrivateKey, + PRIVATE_CLIENT, + sizeof(DRI2ClientEventsRec))) + { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "DRI2 registering " + "private key to client failed\n"); + return FALSE; + } +#else + if (!dixRequestPrivate(DRI2ClientEventsPrivateKey, + sizeof(DRI2ClientEventsRec))) { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "DRI2 requesting " + "private key to client failed\n"); + return FALSE; + } +#endif + + AddCallback(&ClientStateCallback, + amdgpu_dri2_client_state_changed, 0); + } + + DRI2InfoCnt++; + } +#endif + +#if DRI2INFOREC_VERSION >= 9 + dri2_info.version = 9; + dri2_info.CreateBuffer2 = amdgpu_dri2_create_buffer2; + dri2_info.DestroyBuffer2 = amdgpu_dri2_destroy_buffer2; + dri2_info.CopyRegion2 = amdgpu_dri2_copy_region2; +#endif + + info->dri2.enabled = DRI2ScreenInit(pScreen, &dri2_info); + return info->dri2.enabled; +} + +void amdgpu_dri2_close_screen(ScreenPtr pScreen) +{ + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + AMDGPUInfoPtr info = AMDGPUPTR(pScrn); + +#ifdef USE_DRI2_SCHEDULING + if (--DRI2InfoCnt == 0) + DeleteCallback(&ClientStateCallback, + amdgpu_dri2_client_state_changed, 0); +#endif + + DRI2CloseScreen(pScreen); + drmFree(info->dri2.device_name); +} + +#endif /* DRI2 */ |