/*
 * 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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "radeon.h"
#include "radeon_dri2.h"
#include "radeon_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

#ifdef RADEON_DRI2

#include "radeon_bo_gem.h"

#if DRI2INFOREC_VERSION >= 1
#define USE_DRI2_1_1_0
#endif

#if DRI2INFOREC_VERSION >= 4 && HAVE_LIST_H
#define USE_DRI2_SCHEDULING
#endif

#if XORG_VERSION_CURRENT >= XORG_VERSION_NUMERIC(1,6,99,0, 0)
typedef DRI2BufferPtr BufferPtr;
#else
typedef DRI2Buffer2Ptr BufferPtr;
#endif

struct dri2_buffer_priv {
    PixmapPtr   pixmap;
    unsigned int attachment;
    unsigned int refcnt;
};


#ifndef USE_DRI2_1_1_0
static BufferPtr
radeon_dri2_create_buffers(DrawablePtr drawable,
                           unsigned int *attachments,
                           int count)
{
    ScreenPtr pScreen = drawable->pScreen;
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    RADEONInfoPtr info = RADEONPTR(pScrn);
    BufferPtr buffers;
    struct dri2_buffer_priv *privates;
    PixmapPtr pixmap, depth_pixmap;
    struct radeon_exa_pixmap_priv *driver_priv;
    int i, r, need_enlarge = 0;
    int flags = 0;
    unsigned front_width;
    uint32_t tiling = 0;

    pixmap = screen->GetScreenPixmap(screen);
    front_width = pixmap->drawable.width;

    buffers = calloc(count, sizeof *buffers);
    if (buffers == NULL) {
        return NULL;
    }
    privates = calloc(count, sizeof(struct dri2_buffer_priv));
    if (privates == NULL) {
        free(buffers);
        return NULL;
    }

    depth_pixmap = NULL;
    for (i = 0; i < count; i++) {
        if (attachments[i] == DRI2BufferFrontLeft) {
            if (drawable->type == DRAWABLE_PIXMAP) {
                pixmap = (Pixmap*)drawable;
            } else {
                pixmap = (*pScreen->GetWindowPixmap)((WindowPtr)drawable);
            }
            pixmap->refcnt++;
        } else if (attachments[i] == DRI2BufferStencil && depth_pixmap) {
            pixmap = depth_pixmap;
            pixmap->refcnt++;
        } else {
	    /* tile the back buffer */
	    switch(attachments[i]) {
	    case DRI2BufferDepth:
		if (info->ChipFamily >= CHIP_FAMILY_R600)
		    /* macro is the preferred setting, but the 2D detiling for software
		     * fallbacks in mesa still has issues on some configurations
		     */
		    flags = RADEON_CREATE_PIXMAP_TILING_MICRO;
		else
		    flags = RADEON_CREATE_PIXMAP_TILING_MACRO | RADEON_CREATE_PIXMAP_TILING_MICRO;
		if (IS_R200_3D || info->ChipFamily == CHIP_FAMILY_RV200 || info->ChipFamily == CHIP_FAMILY_RADEON)
		    flags |= RADEON_CREATE_PIXMAP_DEPTH;
		break;
	    case DRI2BufferDepthStencil:
		if (info->ChipFamily >= CHIP_FAMILY_R600) {
		    /* macro is the preferred setting, but the 2D detiling for software
		     * fallbacks in mesa still has issues on some configurations
		     */
		    flags = RADEON_CREATE_PIXMAP_TILING_MICRO;
		    if (info->ChipFamily >= CHIP_FAMILY_CEDAR)
			need_enlarge = 1;
		} else
		    flags = RADEON_CREATE_PIXMAP_TILING_MACRO | RADEON_CREATE_PIXMAP_TILING_MICRO;
		if (IS_R200_3D || info->ChipFamily == CHIP_FAMILY_RV200 || info->ChipFamily == CHIP_FAMILY_RADEON)
		    flags |= RADEON_CREATE_PIXMAP_DEPTH;
		break;
	    case DRI2BufferBackLeft:
	    case DRI2BufferBackRight:
	    case DRI2BufferFakeFrontLeft:
	    case DRI2BufferFakeFrontRight:
		if (info->ChipFamily >= CHIP_FAMILY_R600)
		    /* macro is the preferred setting, but the 2D detiling for software
		     * fallbacks in mesa still has issues on some configurations
		     */
		    flags = RADEON_CREATE_PIXMAP_TILING_MICRO;
		else
		    flags = RADEON_CREATE_PIXMAP_TILING_MACRO;
		break;
	    default:
		flags = 0;
	    }

	    if (flags & RADEON_CREATE_PIXMAP_TILING_MICRO)
		tiling |= RADEON_TILING_MICRO;
	    if (flags & RADEON_CREATE_PIXMAP_TILING_MACRO)
		tiling |= RADEON_TILING_MACRO;

	    if (need_enlarge) {
		/* evergreen uses separate allocations for depth and stencil
		 * so we make an extra large depth buffer to cover stencil
		 * as well.
		 */
		unsigned aligned_width = drawable->width;
		unsigned width_align = drmmode_get_pitch_align(pScrn, drawable->depth / 8, tiling);
		unsigned aligned_height;
		unsigned height_align = drmmode_get_height_align(pScrn, tiling);
		unsigned base_align = drmmode_get_base_align(pScrn, drawable->depth / 8, tiling);
		unsigned pitch_bytes;
		unsigned size;

		if (aligned_width == front_width)
		    aligned_width = pScrn->virtualX;
		aligned_width = RADEON_ALIGN(aligned_width, width_align);
		pitch_bytes = aligned_width * (drawable->depth / 8);
		aligned_height = RADEON_ALIGN(drawable->height, height_align);
		size = pitch_bytes * aligned_height;
		size = RADEON_ALIGN(size, base_align);
		/* add additional size for stencil */
		size += aligned_width * aligned_height;
		aligned_height = RADEON_ALIGN(size / pitch_bytes, height_align);

		pixmap = (*pScreen->CreatePixmap)(pScreen,
						  aligned_width,
						  aligned_height,
						  drawable->depth,
						  flags);

	    } else {
		unsigned aligned_width = drawable->width;

		if (aligned_width == front_width)
		    aligned_width = pScrn->virtualX;

		pixmap = (*pScreen->CreatePixmap)(pScreen,
						  aligned_width,
						  drawable->height,
						  drawable->depth,
						  flags);
	    }
        }

        if (attachments[i] == DRI2BufferDepth) {
            depth_pixmap = pixmap;
        }
	info->exa_force_create = TRUE;
	exaMoveInPixmap(pixmap);
	info->exa_force_create = FALSE;
        driver_priv = exaGetPixmapDriverPrivate(pixmap);
	if (!driver_priv ||
	    radeon_gem_get_kernel_name(driver_priv->bo, &buffers[i].name) != 0) {
	    int j;

	    for (j = 0; j < i; j++)
		(*pScreen->DestroyPixmap)(privates[j].pixmap);
	    (*pScreen->DestroyPixmap)(pixmap);
	    free(privates);
	    free(buffers);
	    return NULL;
	}

        buffers[i].attachment = attachments[i];
        buffers[i].pitch = pixmap->devKind;
        buffers[i].cpp = pixmap->drawable.bitsPerPixel / 8;
        buffers[i].driverPrivate = &privates[i];
        buffers[i].flags = 0;
        privates[i].pixmap = pixmap;
        privates[i].attachment = attachments[i];
    }
    return buffers;
}
#else
static BufferPtr
radeon_dri2_create_buffer(DrawablePtr drawable,
                          unsigned int attachment,
                          unsigned int format)
{
    ScreenPtr pScreen = drawable->pScreen;
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    RADEONInfoPtr info = RADEONPTR(pScrn);
    BufferPtr buffers;
    struct dri2_buffer_priv *privates;
    PixmapPtr pixmap, depth_pixmap;
    struct radeon_exa_pixmap_priv *driver_priv;
    int flags;
    unsigned front_width;
    uint32_t tiling = 0;
    unsigned aligned_width = drawable->width;

    pixmap = pScreen->GetScreenPixmap(pScreen);
    front_width = pixmap->drawable.width;

    pixmap = depth_pixmap = NULL;

    if (attachment == DRI2BufferFrontLeft) {
        if (drawable->type == DRAWABLE_PIXMAP) {
            pixmap = (PixmapPtr)drawable;
        } else {
            pixmap = (*pScreen->GetWindowPixmap)((WindowPtr)drawable);
        }
        pixmap->refcnt++;
    } else if (attachment == DRI2BufferStencil && depth_pixmap) {
        pixmap = depth_pixmap;
        pixmap->refcnt++;
    } else {
	/* tile the back buffer */
	switch(attachment) {
	case DRI2BufferDepth:
	    /* macro is the preferred setting, but the 2D detiling for software
	     * fallbacks in mesa still has issues on some configurations
	     */
	    if (info->ChipFamily >= CHIP_FAMILY_R600) {
		if (info->allowColorTiling2D) {
			flags = RADEON_CREATE_PIXMAP_TILING_MACRO;
		} else {
			flags = RADEON_CREATE_PIXMAP_TILING_MICRO;
		}
		if (info->ChipFamily >= CHIP_FAMILY_CEDAR)
		    flags |= RADEON_CREATE_PIXMAP_SZBUFFER;
	    } else
		flags = RADEON_CREATE_PIXMAP_TILING_MACRO | RADEON_CREATE_PIXMAP_TILING_MICRO;
	    if (IS_R200_3D || info->ChipFamily == CHIP_FAMILY_RV200 || info->ChipFamily == CHIP_FAMILY_RADEON)
		flags |= RADEON_CREATE_PIXMAP_DEPTH;
	    break;
	case DRI2BufferDepthStencil:
	    /* macro is the preferred setting, but the 2D detiling for software
	     * fallbacks in mesa still has issues on some configurations
	     */
	    if (info->ChipFamily >= CHIP_FAMILY_R600) {
		if (info->allowColorTiling2D) {
			flags = RADEON_CREATE_PIXMAP_TILING_MACRO;
		} else {
			flags = RADEON_CREATE_PIXMAP_TILING_MICRO;
		}
		if (info->ChipFamily >= CHIP_FAMILY_CEDAR)
		    flags |= RADEON_CREATE_PIXMAP_SZBUFFER;
	    } else
		flags = RADEON_CREATE_PIXMAP_TILING_MACRO | RADEON_CREATE_PIXMAP_TILING_MICRO;
	    if (IS_R200_3D || info->ChipFamily == CHIP_FAMILY_RV200 || info->ChipFamily == CHIP_FAMILY_RADEON)
		flags |= RADEON_CREATE_PIXMAP_DEPTH;
		
	    break;
	case DRI2BufferBackLeft:
	case DRI2BufferBackRight:
	case DRI2BufferFakeFrontLeft:
	case DRI2BufferFakeFrontRight:
	    if (info->ChipFamily >= CHIP_FAMILY_R600) {
		if (info->allowColorTiling2D) {
			flags = RADEON_CREATE_PIXMAP_TILING_MACRO;
		} else {
			flags = RADEON_CREATE_PIXMAP_TILING_MICRO;
		}
	    } else
		flags = RADEON_CREATE_PIXMAP_TILING_MACRO;
	    break;
	default:
	    flags = 0;
	}

	if (flags & RADEON_CREATE_PIXMAP_TILING_MICRO)
	    tiling |= RADEON_TILING_MICRO;
	if (flags & RADEON_CREATE_PIXMAP_TILING_MACRO)
	    tiling |= RADEON_TILING_MACRO;


	    if (aligned_width == front_width)
		aligned_width = pScrn->virtualX;

	    pixmap = (*pScreen->CreatePixmap)(pScreen,
					      aligned_width,
					      drawable->height,
					      (format != 0)?format:drawable->depth,
					      flags);
    }

    if (!pixmap)
        return NULL;

    buffers = calloc(1, sizeof *buffers);
    if (buffers == NULL)
        goto error;

    if (attachment == DRI2BufferDepth) {
        depth_pixmap = pixmap;
    }
    info->exa_force_create = TRUE;
    exaMoveInPixmap(pixmap);
    info->exa_force_create = FALSE;
    driver_priv = exaGetPixmapDriverPrivate(pixmap);
    if (!driver_priv ||
	(radeon_gem_get_kernel_name(driver_priv->bo, &buffers->name) != 0))
        goto error;

    privates = calloc(1, sizeof(struct dri2_buffer_priv));
    if (privates == NULL)
        goto error;

    buffers->attachment = attachment;
    buffers->pitch = pixmap->devKind;
    buffers->cpp = pixmap->drawable.bitsPerPixel / 8;
    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;
}
#endif

#ifndef USE_DRI2_1_1_0
static void
radeon_dri2_destroy_buffers(DrawablePtr drawable,
                            BufferPtr buffers,
                            int count)
{
    ScreenPtr pScreen = drawable->pScreen;
    struct dri2_buffer_priv *private;
    int i;

    for (i = 0; i < count; i++) {
        private = buffers[i].driverPrivate;
        (*pScreen->DestroyPixmap)(private->pixmap);
    }
    if (buffers) {
        free(buffers[0].driverPrivate);
        free(buffers);
    }
}
#else
static void
radeon_dri2_destroy_buffer(DrawablePtr drawable, BufferPtr buffers)
{
    if(buffers)
    {
        ScreenPtr pScreen = drawable->pScreen;
        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)
        {
            (*pScreen->DestroyPixmap)(private->pixmap);

            free(buffers->driverPrivate);
            free(buffers);
        }
    }
}
#endif

static void
radeon_dri2_copy_region(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;
    ScreenPtr pScreen = drawable->pScreen;
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    DrawablePtr src_drawable;
    DrawablePtr dst_drawable;
    RegionPtr copy_clip;
    GCPtr gc;
    RADEONInfoPtr info = RADEONPTR(pScrn);
    Bool vsync;

    if (src_private->attachment == DRI2BufferFrontLeft) {
        src_drawable = drawable;
    } else {
        src_drawable = &src_private->pixmap->drawable;
    }
    if (dst_private->attachment == DRI2BufferFrontLeft) {
        dst_drawable = drawable;
    } else {
        dst_drawable = &dst_private->pixmap->drawable;
    }
    gc = GetScratchGC(dst_drawable->depth, pScreen);
    copy_clip = REGION_CREATE(pScreen, NULL, 0);
    REGION_COPY(pScreen, copy_clip, region);
    (*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) {
		struct radeon_exa_pixmap_priv *exa_priv =
		    exaGetPixmapDriverPrivate(dst_private->pixmap);

		if (exa_priv && exa_priv->bo)
		    radeon_bo_wait(exa_priv->bo);
	    }
	}
    }

    vsync = info->accel_state->vsync;

    /* Driver option "SwapbuffersWait" defines if we vsync DRI2 copy-swaps. */ 
    info->accel_state->vsync = info->swapBuffersWait;

    (*gc->ops->CopyArea)(src_drawable, dst_drawable, gc,
                         0, 0, drawable->width, drawable->height, 0, 0);

    info->accel_state->vsync = vsync;

    FreeScratchGC(gc);
}


#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;

    /* 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 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
radeon_dri2_ref_buffer(BufferPtr buffer)
{
    struct dri2_buffer_priv *private = buffer->driverPrivate;
    private->refcnt++;
}

static void
radeon_dri2_unref_buffer(BufferPtr buffer)
{
    if (buffer) {
        struct dri2_buffer_priv *private = buffer->driverPrivate;
        radeon_dri2_destroy_buffer(&(private->pixmap->drawable), buffer);
    }
}

static void
radeon_dri2_client_state_changed(CallbackListPtr *ClientStateCallback, pointer data, pointer calldata)
{
    DRI2ClientEventsPtr pClientEventsPriv;
    DRI2FrameEventPtr ref = NULL;
    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;
                radeon_dri2_unref_buffer(ref->front);
                radeon_dri2_unref_buffer(ref->back);
            }
        }
        break;
    default:
        break;
    }
}

static int radeon_dri2_drawable_crtc(DrawablePtr pDraw)
{
    ScreenPtr pScreen = pDraw->pScreen;
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    xf86CrtcPtr crtc;
    int crtc_id = -1;

    crtc = radeon_pick_best_crtc(pScrn,
				 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) {
        crtc_id = drmmode_get_crtc_id(crtc);
    }
    return crtc_id;
}

static Bool
radeon_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 radeon_exa_pixmap_priv *exa_priv;
    DRI2FrameEventPtr flip_info;

    /* Main crtc for this drawable shall finally deliver pageflip event. */
    int ref_crtc_hw_id = radeon_dri2_drawable_crtc(draw);

    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;

    xf86DrvMsgVerb(scrn->scrnIndex, X_INFO, RADEON_LOGLEVEL_DEBUG,
		   "%s:%d fevent[%p]\n", __func__, __LINE__, flip_info);

    /* Page flip the full screen buffer */
    back_priv = back->driverPrivate;
    exa_priv = exaGetPixmapDriverPrivate(back_priv->pixmap);

    return radeon_do_pageflip(scrn, exa_priv->bo, flip_info, ref_crtc_hw_id);
}

static Bool
update_front(DrawablePtr draw, DRI2BufferPtr front)
{
    int r;
    PixmapPtr pixmap;
    struct dri2_buffer_priv *priv = front->driverPrivate;
    struct radeon_exa_pixmap_priv *driver_priv;

    if (draw->type == DRAWABLE_PIXMAP)
	pixmap = (PixmapPtr)draw;
    else
	pixmap = (*draw->pScreen->GetWindowPixmap)((WindowPtr)draw);

    pixmap->refcnt++;

    exaMoveInPixmap(pixmap);
    driver_priv = exaGetPixmapDriverPrivate(pixmap);
    r = radeon_gem_get_kernel_name(driver_priv->bo, &front->name);
    if (r) {
	(*draw->pScreen->DestroyPixmap)(pixmap);
	return FALSE;
    }
    (*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 &&
	   RADEONPTR(pScrn)->allowPageFlip &&
	   pScrn->vtSema &&
	   DRI2CanFlip(draw) &&
	   can_exchange(pScrn, draw, front, back);
}

static void
radeon_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 radeon_exa_pixmap_priv *front_radeon, *back_radeon;
    ScreenPtr screen;
    RADEONInfoPtr info;
    struct radeon_bo *bo;
    int tmp;

    /* Swap BO names so DRI works */
    tmp = front->name;
    front->name = back->name;
    back->name = tmp;

    /* Swap pixmap bos */
    front_radeon = exaGetPixmapDriverPrivate(front_priv->pixmap);
    back_radeon = exaGetPixmapDriverPrivate(back_priv->pixmap);
    bo = back_radeon->bo;
    back_radeon->bo = front_radeon->bo;
    front_radeon->bo = bo;

    /* Do we need to update the Screen? */
    screen = draw->pScreen;
    info = RADEONPTR(xf86ScreenToScrn(screen));
    if (front_radeon->bo == info->front_bo) {
	radeon_bo_unref(info->front_bo);
	info->front_bo = back_radeon->bo;
	radeon_bo_ref(info->front_bo);
	front_radeon = exaGetPixmapDriverPrivate(screen->GetScreenPixmap(screen));
        front_radeon->bo = bo;
    }
}

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;

    if (!event->valid)
	goto cleanup;

    status = dixLookupDrawable(&drawable, event->drawable_id, serverClient,
                               M_ANY, DixWriteAccess);
    if (status != Success)
        goto cleanup;

    screen = drawable->pScreen;
    scrn = xf86ScreenToScrn(screen);

    switch (event->type) {
    case DRI2_FLIP:
	if (can_flip(scrn, drawable, event->front, event->back) &&
	    radeon_dri2_schedule_flip(scrn,
				      event->client,
				      drawable,
				      event->front,
				      event->back,
				      event->event_complete,
				      event->event_data,
				      event->frame)) {
	    radeon_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)) {
	    radeon_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, &region, &box, 0);
	    radeon_dri2_copy_region(drawable, &region, 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) {
        radeon_dri2_unref_buffer(event->front);
        radeon_dri2_unref_buffer(event->back);
        ListDelDRI2ClientEvents(event->client, &event->link);
    }
    free(event);
}

static drmVBlankSeqType populate_vbl_request_type(RADEONInfoPtr info, int crtc)
{
    drmVBlankSeqType type = 0;

    if (crtc == 1)
        type |= DRM_VBLANK_SECONDARY;
    else if (crtc > 1)
#ifdef DRM_VBLANK_HIGH_CRTC_SHIFT
	type |= (crtc << DRM_VBLANK_HIGH_CRTC_SHIFT) &
		DRM_VBLANK_HIGH_CRTC_MASK;
#else
	ErrorF("radeon driver bug: %s called for CRTC %d > 1, but "
	       "DRM_VBLANK_HIGH_CRTC_MASK not defined at build time\n",
	       __func__, crtc);
#endif

    return type; 
}

/*
 * 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 = xf86ScreenToScrn(screen);
    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;
    vbl.request.type |= populate_vbl_request_type(info, 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;

    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 = xf86ScreenToScrn(screen);
    RADEONInfoPtr info = RADEONPTR(scrn);
    DRI2FrameEventPtr wait_info = NULL;
    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 = 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;

    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;
    }

    /* Get current count */
    vbl.request.type = DRM_VBLANK_RELATIVE;
    vbl.request.type |= populate_vbl_request_type(info, 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;

    /*
     * 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 |= populate_vbl_request_type(info, crtc);
        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;
    vbl.request.type |= populate_vbl_request_type(info, 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.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:
    if (wait_info) {
        ListDelDRI2ClientEvents(wait_info->client, &wait_info->link);
        free(wait_info);
    }
    DRI2WaitMSCComplete(client, draw, target_msc, 0, 0);
    return TRUE;
}

void radeon_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;
    }

    screen = drawable->pScreen;
    scrn = xf86ScreenToScrn(screen);

    pixmap = screen->GetScreenPixmap(screen);
    xf86DrvMsgVerb(scrn->scrnIndex, X_INFO, RADEON_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 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 = xf86ScreenToScrn(screen);
    RADEONInfoPtr info = RADEONPTR(scrn);
    drmVBlank vbl;
    int ret, crtc= radeon_dri2_drawable_crtc(draw), 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;

    /* radeon_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. 
     */
    radeon_dri2_ref_buffer(front);
    radeon_dri2_ref_buffer(back);

    /* Drawable not displayed... just complete the swap */
    if (crtc == -1)
        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;
    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;
    }

    /* Get current count */
    vbl.request.type = DRM_VBLANK_RELATIVE;
    vbl.request.type |= populate_vbl_request_type(info, 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));
        goto blit_fallback;
    }

    current_msc = vbl.reply.sequence;

    /* 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 |= populate_vbl_request_type(info, 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.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;
    vbl.request.type |= populate_vbl_request_type(info, 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;

    /* 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, &region, &box, 0);

    radeon_dri2_copy_region(draw, &region, 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);
    }

    radeon_dri2_unref_buffer(front);
    radeon_dri2_unref_buffer(back);

    *target_msc = 0; /* offscreen, so zero out target vblank count */
    return TRUE;
}

#endif /* USE_DRI2_SCHEDULING */


Bool
radeon_dri2_screen_init(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    RADEONInfoPtr info = RADEONPTR(pScrn);
    DRI2InfoRec dri2_info = { 0 };
#ifdef USE_DRI2_SCHEDULING
    RADEONEntPtr pRADEONEnt   = RADEONEntPriv(pScrn);
    const char *driverNames[2];
    Bool scheduling_works = TRUE;
#endif

    if (!info->useEXA) {
        xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "DRI2 requires EXA\n");
        return FALSE;
    }

    info->dri2.device_name = drmGetDeviceNameFromFd(info->dri2.drm_fd);

    if ( (info->ChipFamily >= CHIP_FAMILY_R600) ) {
        dri2_info.driverName = R600_DRIVER_NAME;
    } else if ( (info->ChipFamily >= CHIP_FAMILY_R300) ) {
        dri2_info.driverName = R300_DRIVER_NAME;
    } else if ( info->ChipFamily >= CHIP_FAMILY_R200 ) {
        dri2_info.driverName = R200_DRIVER_NAME;
    } else {
        dri2_info.driverName = RADEON_DRIVER_NAME;
    }
    dri2_info.fd = info->dri2.drm_fd;
    dri2_info.deviceName = info->dri2.device_name;
#ifndef USE_DRI2_1_1_0
    dri2_info.version = 1;
    dri2_info.CreateBuffers = radeon_dri2_create_buffers;
    dri2_info.DestroyBuffers = radeon_dri2_destroy_buffers;
#else
    dri2_info.version = DRI2INFOREC_VERSION;
    dri2_info.CreateBuffer = radeon_dri2_create_buffer;
    dri2_info.DestroyBuffer = radeon_dri2_destroy_buffer;
#endif
    dri2_info.CopyRegion = radeon_dri2_copy_region;

#ifdef USE_DRI2_SCHEDULING
    if (info->dri->pKernelDRMVersion->version_minor < 4) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "You need a newer kernel for "
		   "sync extension\n");
	scheduling_works = FALSE;
    }

    if (scheduling_works && 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 = radeon_dri2_schedule_swap;
        dri2_info.GetMSC = radeon_dri2_get_msc;
        dri2_info.ScheduleWaitMSC = radeon_dri2_schedule_wait_msc;
        dri2_info.numDrivers = RADEON_ARRAY_SIZE(driverNames);
        dri2_info.driverNames = driverNames;
        driverNames[0] = driverNames[1] = dri2_info.driverName;

	if (pRADEONEnt->dri2_info_cnt == 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, radeon_dri2_client_state_changed, 0);
	}

	pRADEONEnt->dri2_info_cnt++;
    }
#endif

    info->dri2.enabled = DRI2ScreenInit(pScreen, &dri2_info);
    return info->dri2.enabled;
}

void radeon_dri2_close_screen(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    RADEONInfoPtr info = RADEONPTR(pScrn);
#ifdef USE_DRI2_SCHEDULING
    RADEONEntPtr pRADEONEnt   = RADEONEntPriv(pScrn);

    if (--pRADEONEnt->dri2_info_cnt == 0)
    	DeleteCallback(&ClientStateCallback, radeon_dri2_client_state_changed, 0);
#endif
    DRI2CloseScreen(pScreen);
    drmFree(info->dri2.device_name);
}

#endif
