diff options
author | Jerome Glisse <jglisse@redhat.com> | 2010-05-07 17:35:09 +0200 |
---|---|---|
committer | Jerome Glisse <jglisse@redhat.com> | 2010-05-10 15:23:24 +0200 |
commit | 30591320ec46e491ba20904cc64f3405b51c6505 (patch) | |
tree | a6f3bfe598643d32ae1c5ee21628dffd402e3f9c | |
parent | 766024dcc61c83490540910ce752f9bfe6dddba4 (diff) |
kms: add support for the MSC swap & sync API
This patch is mostly a port over of Intel ddx code for
MSC support. It needs a radeon KMS module with version
2.4 which has the query for hw crtc id.
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Signed-off-by: Jerome Glisse <jglisse@redhat.com>
-rw-r--r-- | src/drmmode_display.c | 57 | ||||
-rw-r--r-- | src/drmmode_display.h | 3 | ||||
-rw-r--r-- | src/radeon_dri2.c | 453 | ||||
-rw-r--r-- | src/radeon_dri2.h | 6 | ||||
-rw-r--r-- | src/radeon_kms.c | 10 | ||||
-rw-r--r-- | src/radeon_video.c | 4 |
6 files changed, 526 insertions, 7 deletions
diff --git a/src/drmmode_display.c b/src/drmmode_display.c index ea361d6b..686de5e8 100644 --- a/src/drmmode_display.c +++ b/src/drmmode_display.c @@ -522,6 +522,32 @@ static const xf86CrtcFuncsRec drmmode_crtc_funcs = { .destroy = NULL, /* XXX */ }; +int drmmode_get_crtc_id(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + return drmmode_crtc->hw_id; +} + +void drmmode_crtc_hw_id(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + ScrnInfoPtr pScrn = crtc->scrn; + RADEONInfoPtr info = RADEONPTR(pScrn); + struct drm_radeon_info ginfo; + int r; + uint32_t tmp; + + memset(&ginfo, 0, sizeof(ginfo)); + ginfo.request = 0x4; + tmp = drmmode_crtc->mode_crtc->crtc_id; + ginfo.value = (uintptr_t)&tmp; + r = drmCommandWriteRead(info->dri->drmFD, DRM_RADEON_INFO, &ginfo, sizeof(ginfo)); + if (r) { + drmmode_crtc->hw_id = -1; + return; + } + drmmode_crtc->hw_id = tmp; +} static void drmmode_crtc_init(ScrnInfoPtr pScrn, drmmode_ptr drmmode, int num) @@ -537,6 +563,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; crtc->driver_private = drmmode_crtc; + drmmode_crtc_hw_id(crtc); return; } @@ -1150,10 +1177,28 @@ static const xf86CrtcConfigFuncsRec drmmode_xf86crtc_config_funcs = { drmmode_xf86crtc_resize }; +static void +drmmode_vblank_handler(int fd, unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data) +{ + radeon_dri2_frame_event_handler(frame, tv_sec, tv_usec, event_data); +} + +static void +drm_wakeup_handler(pointer data, int err, pointer p) +{ + drmmode_ptr drmmode = data; + fd_set *read_mask = p; + + if (err >= 0 && FD_ISSET(drmmode->fd, read_mask)) { + drmHandleEvent(drmmode->fd, &drmmode->event_context); + } +} Bool drmmode_pre_init(ScrnInfoPtr pScrn, drmmode_ptr drmmode, int cpp) { - xf86CrtcConfigPtr xf86_config; + xf86CrtcConfigPtr xf86_config; + RADEONInfoPtr info = RADEONPTR(pScrn); int i; xf86CrtcConfigInit(pScrn, &drmmode_xf86crtc_config_funcs); @@ -1178,6 +1223,16 @@ Bool drmmode_pre_init(ScrnInfoPtr pScrn, drmmode_ptr drmmode, int cpp) xf86InitialConfiguration(pScrn, TRUE); + drmmode->flip_count = 0; + drmmode->event_context.version = DRM_EVENT_CONTEXT_VERSION; + drmmode->event_context.vblank_handler = drmmode_vblank_handler; + drmmode->event_context.page_flip_handler = NULL; + if (info->dri->pKernelDRMVersion->version_minor >= 4) { + AddGeneralSocket(drmmode->fd); + RegisterBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA, + drm_wakeup_handler, drmmode); + } + return TRUE; } diff --git a/src/drmmode_display.h b/src/drmmode_display.h index 86caabba..a9891b2d 100644 --- a/src/drmmode_display.h +++ b/src/drmmode_display.h @@ -48,11 +48,14 @@ typedef struct { struct udev_monitor *uevent_monitor; InputHandlerProc uevent_handler; #endif + drmEventContext event_context; + int flip_count; } drmmode_rec, *drmmode_ptr; typedef struct { drmmode_ptr drmmode; drmModeCrtcPtr mode_crtc; + int hw_id; struct radeon_bo *cursor_bo; struct radeon_bo *rotate_bo; unsigned rotate_fb_id; diff --git a/src/radeon_dri2.c b/src/radeon_dri2.c index 103972f5..0e6292e3 100644 --- a/src/radeon_dri2.c +++ b/src/radeon_dri2.c @@ -324,12 +324,450 @@ radeon_dri2_copy_region(DrawablePtr drawable, radeon_cs_flush_indirect(pScrn); } + +#if DRI2INFOREC_VERSION >= 4 + +enum DRI2FrameEventType { + DRI2_SWAP, + DRI2_FLIP, + DRI2_WAITMSC, +}; + +typedef struct _DRI2FrameEvent { + XID drawable_id; + ClientPtr client; + enum DRI2FrameEventType type; + int frame; + + /* for swaps & flips only */ + DRI2SwapEventPtr event_complete; + void *event_data; + DRI2BufferPtr front; + DRI2BufferPtr back; +} DRI2FrameEventRec, *DRI2FrameEventPtr; + +void radeon_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; + + status = dixLookupDrawable(&drawable, event->drawable_id, serverClient, + M_ANY, DixWriteAccess); + if (status != Success) { + xfree(event); + return; + } + + screen = drawable->pScreen; + scrn = xf86Screens[screen->myNum]; + + switch (event->type) { + case DRI2_FLIP: + case DRI2_SWAP: + box.x1 = 0; + box.y1 = 0; + box.x2 = drawable->width; + box.y2 = drawable->height; + REGION_INIT(pScreen, ®ion, &box, 0); + radeon_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; + } + + xfree(event); +} + +static int radeon_dri2_drawable_crtc(DrawablePtr pDraw) +{ + ScreenPtr pScreen = pDraw->pScreen; + ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; + BoxRec box, crtcbox; + xf86CrtcPtr crtc; + int crtc_id = -1; + + box.x1 = pDraw->x; + box.y1 = pDraw->y; + box.x2 = box.x1 + pDraw->width; + box.y2 = box.y1 + pDraw->height; + + crtc = radeon_covering_crtc(pScrn, &box, NULL, &crtcbox); + + /* Make sure the CRTC is valid and this is the real front buffer */ + if (crtc != NULL && !crtc->rotatedData) { + crtc_id = drmmode_get_crtc_id(crtc); + } + return crtc_id; +} + +/* + * Get current frame count and frame count timestamp, based on drawable's + * crtc. + */ +static int radeon_dri2_get_msc(DrawablePtr draw, CARD64 *ust, CARD64 *msc) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86Screens[screen->myNum]; + RADEONInfoPtr info = RADEONPTR(scrn); + drmVBlank vbl; + int ret; + int crtc= radeon_dri2_drawable_crtc(draw); + + /* Drawable not displayed, make up a value */ + if (crtc == -1) { + *ust = 0; + *msc = 0; + return TRUE; + } + vbl.request.type = DRM_VBLANK_RELATIVE; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + 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; + + return TRUE; +} + +/* + * 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 radeon_dri2_schedule_wait_msc(ClientPtr client, DrawablePtr draw, + CARD64 target_msc, CARD64 divisor, + CARD64 remainder) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86Screens[screen->myNum]; + RADEONInfoPtr info = RADEONPTR(scrn); + DRI2FrameEventPtr wait_info; + drmVBlank vbl; + int ret, crtc = radeon_dri2_drawable_crtc(draw); + 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 == -1) + goto out_complete; + + wait_info = xcalloc(1, sizeof(DRI2FrameEventRec)); + if (!wait_info) + goto out_complete; + + wait_info->drawable_id = draw->id; + wait_info->client = client; + wait_info->type = DRI2_WAITMSC; + + /* Get current count */ + vbl.request.type = DRM_VBLANK_RELATIVE; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + 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; + + /* + * 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; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + vbl.request.sequence = target_msc; + 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; + 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; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + + 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.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; + DRI2BlockClient(client, draw); + + return TRUE; + +out_complete: + DRI2WaitMSCComplete(client, draw, target_msc, 0, 0); + return TRUE; +} + +/* + * 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 radeon_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 = xf86Screens[screen->myNum]; + RADEONInfoPtr info = RADEONPTR(scrn); + drmVBlank vbl; + int ret, crtc= radeon_dri2_drawable_crtc(draw), flip = 0; + DRI2FrameEventPtr swap_info; + 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; + + swap_info = xcalloc(1, sizeof(DRI2FrameEventRec)); + + /* Drawable not displayed... just complete the swap */ + if (crtc == -1 || !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; + + /* Get current count */ + vbl.request.type = DRM_VBLANK_RELATIVE; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + 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)); + goto blit_fallback; + } + + current_msc = vbl.reply.sequence; + 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 (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + + /* 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; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + + /* 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.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)); + goto blit_fallback; + } + + *target_msc = vbl.reply.sequence + flip; + 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; + if (crtc > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + + 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; + + /* 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)); + goto blit_fallback; + } + + /* Adjust returned value for 1 fame pageflip offset of flip > 0 */ + *target_msc = vbl.reply.sequence + flip; + 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); + + radeon_dri2_copy_region(draw, ®ion, front, back); + + DRI2SwapComplete(client, draw, 0, 0, 0, DRI2_BLIT_COMPLETE, func, data); + if (swap_info) + xfree(swap_info); + *target_msc = 0; /* offscreen, so zero out target vblank count */ + return TRUE; +} + +#endif /* DRI2INFOREC_VERSION >= 4 */ + + Bool radeon_dri2_screen_init(ScreenPtr pScreen) { ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; RADEONInfoPtr info = RADEONPTR(pScrn); DRI2InfoRec dri2_info = { 0 }; +#if DRI2INFOREC_VERSION >= 4 + const char *driverNames[1]; +#endif if (!info->useEXA) { xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "DRI2 requires EXA\n"); @@ -359,6 +797,21 @@ radeon_dri2_screen_init(ScreenPtr pScreen) dri2_info.DestroyBuffer = radeon_dri2_destroy_buffer; #endif dri2_info.CopyRegion = radeon_dri2_copy_region; + +#if DRI2INFOREC_VERSION >= 4 + if (info->dri->pKernelDRMVersion->version_minor >= 4) { + dri2_info.version = 4; + dri2_info.ScheduleSwap = radeon_dri2_schedule_swap; + dri2_info.GetMSC = radeon_dri2_get_msc; + dri2_info.ScheduleWaitMSC = radeon_dri2_schedule_wait_msc; + dri2_info.numDrivers = 1; + dri2_info.driverNames = driverNames; + driverNames[0] = dri2_info.driverName; + } else { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "You need a newer kernel for sync extension\n"); + } +#endif + info->dri2.enabled = DRI2ScreenInit(pScreen, &dri2_info); return info->dri2.enabled; } diff --git a/src/radeon_dri2.h b/src/radeon_dri2.h index c391e739..688530f2 100644 --- a/src/radeon_dri2.h +++ b/src/radeon_dri2.h @@ -39,4 +39,10 @@ Bool radeon_dri2_screen_init(ScreenPtr pScreen); void radeon_dri2_close_screen(ScreenPtr pScreen); #endif +int drmmode_get_crtc_id(xf86CrtcPtr crtc); +xf86CrtcPtr radeon_covering_crtc(ScrnInfoPtr pScrn, BoxPtr box, + xf86CrtcPtr desired, BoxPtr crtc_box_ret); +void radeon_dri2_frame_event_handler(unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data); + #endif diff --git a/src/radeon_kms.c b/src/radeon_kms.c index e33c9eff..1ba320be 100644 --- a/src/radeon_kms.c +++ b/src/radeon_kms.c @@ -493,10 +493,6 @@ Bool RADEONPreInit_KMS(ScrnInfoPtr pScrn, int flags) xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Kernel modesetting setup failed\n"); goto fail; } - if (drmmode_pre_init(pScrn, &info->drmmode, pScrn->bitsPerPixel / 8) == FALSE) { - xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Kernel modesetting setup failed\n"); - goto fail; - } info->dri2.enabled = FALSE; info->dri->pKernelDRMVersion = drmGetVersion(info->dri->drmFD); @@ -506,6 +502,12 @@ Bool RADEONPreInit_KMS(ScrnInfoPtr pScrn, int flags) goto fail; } + if (drmmode_pre_init(pScrn, &info->drmmode, pScrn->bitsPerPixel / 8) == FALSE) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Kernel modesetting setup failed\n"); + goto fail; + } + + { struct drm_radeon_gem_info mminfo; diff --git a/src/radeon_video.c b/src/radeon_video.c index 5e2a7238..7b5f6dec 100644 --- a/src/radeon_video.c +++ b/src/radeon_video.c @@ -162,8 +162,7 @@ radeon_pick_best_crtc(ScrnInfoPtr pScrn, return best_crtc; } -#ifndef HAVE_XF86CRTCCLIPVIDEOHELPER -static xf86CrtcPtr +xf86CrtcPtr radeon_covering_crtc(ScrnInfoPtr pScrn, BoxPtr box, xf86CrtcPtr desired, @@ -198,6 +197,7 @@ radeon_covering_crtc(ScrnInfoPtr pScrn, return best_crtc; } +#ifndef HAVE_XF86CRTCCLIPVIDEOHELPER static Bool radeon_crtc_clip_video_helper(ScrnInfoPtr pScrn, xf86CrtcPtr *crtc_ret, |