summaryrefslogtreecommitdiffstats
path: root/src/vivante_dri2.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vivante_dri2.c')
-rw-r--r--src/vivante_dri2.c751
1 files changed, 751 insertions, 0 deletions
diff --git a/src/vivante_dri2.c b/src/vivante_dri2.c
new file mode 100644
index 0000000..42c3876
--- /dev/null
+++ b/src/vivante_dri2.c
@@ -0,0 +1,751 @@
+/*
+ * Vivante GPU Acceleration Xorg driver
+ *
+ * Written by Russell King, 2012, derived in part from the
+ * Intel xorg X server driver.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+
+/* xorg includes */
+#include "dixstruct.h"
+#include "fb.h"
+#include "gcstruct.h"
+#include "xf86.h"
+#include "dri2.h"
+
+/* drm includes */
+#include <xf86drm.h>
+#include <armada_bufmgr.h>
+
+#include "vivante_accel.h"
+#include "vivante_dri2.h"
+#include "vivante_utils.h"
+
+#if DRI2INFOREC_VERSION < 4
+#error DRI2 is too old!
+#endif
+
+struct vivante_dri2_info {
+ char *devname;
+};
+
+struct vivante_dri2_buffer {
+ DRI2BufferRec dri2;
+ PixmapPtr pixmap;
+ unsigned ref;
+};
+
+enum event_type {
+ DRI2_SWAP,
+ DRI2_SWAP_CHAIN,
+ DRI2_FLIP,
+ DRI2_WAITMSC,
+};
+
+struct vivante_dri_wait {
+ struct vivante_dri_wait *next;
+ struct list drawable_list;
+ struct list client_list;
+ XID drawable_id;
+ ClientPtr client;
+ enum event_type type;
+ unsigned crtc;
+ int frame;
+
+ /* For swaps/flips */
+ DRI2SwapEventPtr event_func;
+ void *event_data;
+ DRI2BufferPtr front;
+ DRI2BufferPtr back;
+};
+
+#if HAS_DEVPRIVATEKEYREC
+typedef DevPrivateKeyRec vivante_dri2_client_key_t;
+#else
+typedef int vivante_dri2_client_key_t;
+#endif
+
+static vivante_dri2_client_key_t vivante_dri2_client_key;
+static RESTYPE wait_client_restype, wait_drawable_restype;
+
+#if HAS_DIXREGISTERPRIVATEKEY
+#define vivante_dri2_get_client_private(c) \
+ dixGetPrivateAddr(&(c)->devPrivates, &vivante_dri2_client_key)
+#define vivante_dri2_register_private() \
+ dixRegisterPrivateKey(&vivante_dri2_client_key, PRIVATE_CLIENT, \
+ sizeof(XID))
+#else
+#define vivante_dri2_get_client_private(c) \
+ dixLookupPrivate(&(c)->devPrivates, &vivante_dri2_client_key)
+#define vivante_dri2_register_private() \
+ dixRequestPrivate(&vivante_dri2_client_key, sizeof(XID))
+#endif
+
+static int vivante_dri2_drawable_crtc(DrawablePtr draw)
+{
+ /* FIXME */
+ return -1;
+}
+
+static void vivante_dri2_buffer_reference(DRI2Buffer2Ptr buffer)
+{
+ struct vivante_dri2_buffer *priv = buffer->driverPrivate;
+
+ priv->ref++;
+}
+
+static DrawablePtr
+vivante_dri2_get_drawable(DRI2BufferPtr buffer, DrawablePtr drawable)
+{
+ struct vivante_dri2_buffer *buf = buffer->driverPrivate;
+
+ return buffer->attachment == DRI2BufferFrontLeft ?
+ drawable : &buf->pixmap->drawable;
+}
+
+static PixmapPtr vivante_dri2_get_front_pixmap(DrawablePtr drawable)
+{
+ PixmapPtr pixmap = vivante_drawable_pixmap(drawable);
+ struct vivante_pixmap *vpix = vivante_get_pixmap_priv(pixmap);
+
+ if (!vpix)
+ return NULL;
+
+ pixmap->refcnt++;
+
+ return pixmap;
+}
+
+static PixmapPtr
+vivante_dri2_get_pixmap(DRI2BufferPtr buffer)
+{
+ struct vivante_dri2_buffer *buf = buffer->driverPrivate;
+ return buf->pixmap;
+}
+
+static int vivante_dri2_client_gone(void *data, XID id)
+{
+ struct list *list = data;
+
+ while (!list_is_empty(list)) {
+ struct vivante_dri_wait *wait;
+
+ wait = list_first_entry(list, struct vivante_dri_wait, client_list);
+ list_del(&wait->client_list);
+ wait->client = NULL;
+ }
+ free(list);
+
+ return Success;
+}
+
+static int vivante_dri2_drawable_gone(void *data, XID id)
+{
+ struct list *list = data;
+
+ while (!list_is_empty(list)) {
+ struct vivante_dri_wait *wait;
+
+ wait = list_first_entry(list, struct vivante_dri_wait, drawable_list);
+ list_del(&wait->drawable_list);
+ wait->drawable_id = None;
+ }
+ free(list);
+
+ return Success;
+}
+
+static XID client_id(ClientPtr client)
+{
+ XID *ptr = vivante_dri2_get_client_private(client);
+
+ if (*ptr == 0)
+ *ptr = FakeClientID(client->index);
+
+ return *ptr;
+}
+
+static Bool add_reslist(RESTYPE type, XID id, struct list *node)
+{
+ struct list *list;
+ void *ptr = NULL;
+
+ dixLookupResourceByType(&ptr, id, type, NULL, DixWriteAccess);
+ list = ptr;
+ if (!list) {
+ list = malloc(sizeof *list);
+ if (!list)
+ return FALSE;
+
+ if (!AddResource(id, type, list)) {
+ free(list);
+ return FALSE;
+ }
+
+ list_init(list);
+ }
+
+ list_add(node, list);
+
+ return TRUE;
+}
+
+static Bool
+can_exchange(DrawablePtr drawable, DRI2BufferPtr front, DRI2BufferPtr back)
+{
+ PixmapPtr front_pix = vivante_dri2_get_pixmap(front);
+ PixmapPtr back_pix = vivante_dri2_get_pixmap(back);
+
+ if (!DRI2CanFlip(drawable))
+ return FALSE;
+
+ /* Front and back must be the same size and bpp */
+ if (front_pix->drawable.width != back_pix->drawable.width ||
+ front_pix->drawable.height != back_pix->drawable.height ||
+ front_pix->drawable.bitsPerPixel != back_pix->drawable.bitsPerPixel)
+ return FALSE;
+
+ return TRUE;
+}
+
+static DRI2Buffer2Ptr
+vivante_dri2_CreateBuffer(DrawablePtr drawable, unsigned int attachment,
+ unsigned int format)
+{
+ struct vivante_dri2_buffer *buf;
+ struct vivante_pixmap *vpix;
+ ScreenPtr pScreen = drawable->pScreen;
+ PixmapPtr pixmap = NULL;
+ uint32_t name;
+
+fprintf(stderr, "%s: %p %u %u\n", __func__, drawable, attachment, format);
+ if (attachment == DRI2BufferFrontLeft) {
+ pixmap = vivante_dri2_get_front_pixmap(drawable);
+ if (!pixmap) {
+ drawable = &pixmap->drawable;
+ pixmap = NULL;
+ }
+ }
+
+ if (pixmap == NULL) {
+ int width = drawable->width;
+ int height = drawable->height;
+ int depth = format ? format : drawable->depth;
+
+ pixmap = pScreen->CreatePixmap(pScreen, width, height, depth, 0);
+ if (!pixmap)
+ goto err;
+ }
+
+ vpix = vivante_get_pixmap_priv(pixmap);
+ if (!vpix)
+ goto err;
+
+ buf = calloc(1, sizeof *buf);
+ if (!buf)
+ goto err;
+
+ if (!vpix->bo || drm_armada_bo_flink(vpix->bo, &name)) {
+ free(buf);
+ goto err;
+ }
+
+ buf->dri2.attachment = attachment;
+ buf->dri2.name = name;
+ buf->dri2.pitch = pixmap->devKind;
+ buf->dri2.cpp = pixmap->drawable.bitsPerPixel / 8;
+ buf->dri2.flags = 0;
+ buf->dri2.format = format;
+ buf->dri2.driverPrivate = buf;
+ buf->pixmap = pixmap;
+ buf->ref = 1;
+
+ return &buf->dri2;
+
+ err:
+ if (pixmap)
+ pScreen->DestroyPixmap(pixmap);
+
+ return NULL;
+}
+
+static void
+vivante_dri2_DestroyBuffer(DrawablePtr drawable, DRI2Buffer2Ptr buffer)
+{
+ if (buffer) {
+ struct vivante_dri2_buffer *buf = buffer->driverPrivate;
+ ScreenPtr screen;
+
+ /* This should never happen... due to how we allocate the private */
+ assert(buf != NULL);
+
+ if (--buf->ref != 0)
+ return;
+
+ screen = buf->pixmap->drawable.pScreen;
+ screen->DestroyPixmap(buf->pixmap);
+
+ free(buf);
+ }
+}
+
+static void
+vivante_dri2_CopyRegion(DrawablePtr drawable, RegionPtr pRegion,
+ DRI2BufferPtr dstBuf, DRI2BufferPtr srcBuf)
+{
+ ScreenPtr screen = drawable->pScreen;
+ DrawablePtr src = vivante_dri2_get_drawable(srcBuf, drawable);
+ DrawablePtr dst = vivante_dri2_get_drawable(dstBuf, drawable);
+ RegionPtr clip;
+ GCPtr gc;
+
+ gc = GetScratchGC(dst->depth, screen);
+ if (!gc)
+ return;
+
+ clip = REGION_CREATE(screen, NULL, 0);
+ REGION_COPY(screen, clip, pRegion);
+ gc->funcs->ChangeClip(gc, CT_REGION, clip, 0);
+ ValidateGC(dst, gc);
+
+ /*
+ * FIXME: wait for scanline to be outside the region to be copied...
+ * that is an interesting problem for Dove/GAL stuff because they're
+ * independent, and there's no way for the GPU to know where the
+ * scan position is. For now, just do the copy anyway.
+ */
+ gc->ops->CopyArea(src, dst, gc, 0, 0,
+ drawable->width, drawable->height, 0, 0);
+
+ FreeScratchGC(gc);
+}
+
+static struct vivante_dri_wait *
+new_wait_info(ClientPtr client, DrawablePtr draw, enum event_type type)
+{
+ struct vivante_dri_wait *wait = calloc(1, sizeof *wait);
+
+ if (wait) {
+ wait->drawable_id = draw->id;
+ wait->client = client;
+ wait->type = type;
+
+ list_init(&wait->client_list);
+ list_init(&wait->drawable_list);
+
+ if (!add_reslist(wait_drawable_restype, draw->id,
+ &wait->drawable_list) ||
+ !add_reslist(wait_client_restype, client_id(client),
+ &wait->client_list)) {
+ list_del(&wait->client_list);
+ list_del(&wait->drawable_list);
+ free(wait);
+ wait = NULL;
+ }
+ }
+ return wait;
+}
+
+static void del_wait_info(struct vivante_dri_wait *wait)
+{
+ list_del(&wait->client_list);
+ list_del(&wait->drawable_list);
+
+ vivante_dri2_DestroyBuffer(NULL, wait->front);
+ vivante_dri2_DestroyBuffer(NULL, wait->back);
+
+ free(wait);
+}
+
+static Bool
+vivante_dri2_ScheduleFlip(DrawablePtr drawable, struct vivante_dri_wait *wait)
+{
+ return FALSE;
+}
+
+static void
+vivante_dri2_blit(ClientPtr client, DrawablePtr draw, DRI2BufferPtr front,
+ DRI2BufferPtr back, unsigned frame, unsigned tv_sec, unsigned tv_usec,
+ DRI2SwapEventPtr func, void *data)
+{
+ RegionRec region;
+ BoxRec box;
+
+ box.x1 = 0;
+ box.y1 = 0;
+ box.x2 = draw->width;
+ box.y2 = draw->height;
+ RegionInit(&region, &box, 0);
+
+ vivante_dri2_CopyRegion(draw, &region, front, back);
+
+ DRI2SwapComplete(client, draw, frame, tv_sec, tv_usec,
+ DRI2_BLIT_COMPLETE, func, data);
+}
+
+void
+vivante_dri2_vblank(int fd, unsigned frame, unsigned tv_sec, unsigned tv_usec,
+ void *event)
+{
+ struct vivante_dri_wait *wait = event;
+ DrawablePtr draw;
+
+ if (!wait->drawable_id)
+ goto out;
+
+ if (dixLookupDrawable(&draw, wait->drawable_id, serverClient, M_ANY,
+ DixWriteAccess) != Success)
+ goto out;
+
+ switch (wait->type) {
+ case DRI2_FLIP:
+ if (can_exchange(draw, wait->front, wait->back) &&
+ vivante_dri2_ScheduleFlip(draw, wait))
+ return;
+ /* FALLTHROUGH */
+
+ case DRI2_SWAP:
+ vivante_dri2_blit(wait->client, draw, wait->front, wait->back,
+ frame, tv_sec, tv_usec,
+ wait->client ? wait->event_func : NULL,
+ wait->event_data);
+ break;
+
+ case DRI2_WAITMSC:
+ if (wait->client)
+ DRI2WaitMSCComplete(wait->client, draw, frame, tv_sec, tv_usec);
+ break;
+
+ default:
+ xf86DrvMsg(xf86Screens[draw->pScreen->myNum]->scrnIndex, X_WARNING,
+ "%s: unknown vblank event received\n",
+ __FUNCTION__);
+ break;
+ }
+
+ out:
+ del_wait_info(wait);
+}
+
+static uint32_t drm_req_crtc(unsigned crtc)
+{
+ /*
+ * We only support newer kernels here - always
+ * encode the CRTC id in the high crtc field.
+ */
+ return crtc << DRM_VBLANK_HIGH_CRTC_SHIFT;
+}
+
+static int
+vivante_dri2_waitvblank(struct vivante *vivante, drmVBlank *vbl, unsigned crtc,
+ const char *func)
+{
+ static int limit = 5;
+ int ret;
+
+ vbl->request.type = DRM_VBLANK_RELATIVE | drm_req_crtc(crtc);
+ vbl->request.sequence = 0;
+
+ ret = drmWaitVBlank(vivante->drm_fd, vbl);
+ if (ret && limit) {
+ xf86DrvMsg(vivante->scrnIndex, X_WARNING,
+ "%s: get vblank counter failed: %s\n",
+ func, strerror(errno));
+ limit--;
+ }
+ return ret;
+}
+
+static int
+vivante_dri2_ScheduleSwap(ClientPtr client, DrawablePtr draw,
+ DRI2BufferPtr front, DRI2BufferPtr back, CARD64 *target_msc,
+ CARD64 divisor, CARD64 remainder, DRI2SwapEventPtr func, void *data)
+{
+ struct vivante *vivante = vivante_get_screen_priv(draw->pScreen);
+ struct vivante_dri_wait *wait;
+ drmVBlank vbl;
+ CARD64 cur_msc;
+ int ret, crtc;
+
+ crtc = vivante_dri2_drawable_crtc(draw);
+
+ /* Drawable not displayed... just complete */
+ if (crtc < 0)
+ goto blit;
+
+ *target_msc &= 0xffffffff;
+ divisor &= 0xffffffff;
+ remainder &= 0xffffffff;
+
+ wait = new_wait_info(client, draw, DRI2_SWAP);
+ if (!wait)
+ goto blit;
+
+ wait->crtc = crtc;
+ wait->event_func = func;
+ wait->event_data = data;
+ wait->front = front;
+ wait->back = back;
+
+ vivante_dri2_buffer_reference(front);
+ vivante_dri2_buffer_reference(back);
+
+ ret = vivante_dri2_waitvblank(vivante, &vbl, crtc, __FUNCTION__);
+ if (ret)
+ goto blit_free;
+
+ cur_msc = vbl.reply.sequence;
+
+ /* Flips need to be submitted one frame before */
+ if (can_exchange(draw, front, back)) {
+ wait->type = DRI2_FLIP;
+ if (*target_msc > 0)
+ *target_msc -= 1;
+ }
+
+ if (divisor == 0 || cur_msc < *target_msc) {
+ if (wait->type == DRI2_FLIP && vivante_dri2_ScheduleFlip(draw, wait))
+ return TRUE;
+
+ /*
+ * If target_msc has been reached or passed, set it to cur_msc
+ * to ensure we return a reasonable value back to the caller.
+ * This makes the swap_interval logic more robust.
+ */
+ if (cur_msc >= *target_msc)
+ *target_msc = cur_msc;
+
+ vbl.request.sequence = *target_msc;
+ } else {
+ vbl.request.sequence = cur_msc - (cur_msc % divisor) + remainder;
+
+ /*
+ * If the calculated deadline sequence is smaller than or equal
+ * to cur_msc, it means we've passed the 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 <= cur_msc)
+ vbl.request.sequence += divisor;
+
+ /* Account for 1 frame extra pageflip delay if flip > 0 */
+ if (wait->type == DRI2_FLIP)
+ vbl.request.sequence -= 1;
+ }
+
+ vbl.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT | drm_req_crtc(crtc);
+ if (wait->type != DRI2_FLIP)
+ vbl.request.type |= DRM_VBLANK_NEXTONMISS;
+
+ vbl.request.signal = (unsigned long)wait;
+ ret = drmWaitVBlank(vivante->drm_fd, &vbl);
+ if (ret) {
+ xf86DrvMsg(vivante->scrnIndex, X_WARNING,
+ "get vblank counter failed: %s\n",
+ strerror(errno));
+ goto blit_free;
+ }
+
+ *target_msc = vbl.reply.sequence + (wait->type == DRI2_FLIP);
+ wait->frame = *target_msc;
+
+ return TRUE;
+
+ blit_free:
+ del_wait_info(wait);
+ blit:
+ vivante_dri2_blit(client, draw, front, back, 0, 0, 0, func, data);
+ *target_msc = 0;
+ return TRUE;
+}
+
+static int vivante_dri2_GetMSC(DrawablePtr draw, CARD64 *ust, CARD64 *msc)
+{
+ struct vivante *vivante = vivante_get_screen_priv(draw->pScreen);
+ drmVBlank vbl;
+ int ret, crtc;
+
+ crtc = vivante_dri2_drawable_crtc(draw);
+
+ /* Drawable not displayed, make up a value */
+ if (crtc < 0) {
+ *ust = 0;
+ *msc = 0;
+ return TRUE;
+ }
+
+ ret = vivante_dri2_waitvblank(vivante, &vbl, crtc, __FUNCTION__);
+ if (ret)
+ return FALSE;
+
+ *ust = ((CARD64)vbl.reply.tval_sec * 1000000) + vbl.reply.tval_usec;
+ *msc = vbl.reply.sequence;
+
+ return TRUE;
+}
+
+static int
+vivante_dri2_ScheduleWaitMSC(ClientPtr client, DrawablePtr draw,
+ CARD64 target_msc, CARD64 divisor, CARD64 remainder)
+{
+ struct vivante *vivante = vivante_get_screen_priv(draw->pScreen);
+ struct vivante_dri_wait *wait;
+ drmVBlank vbl;
+ int ret, crtc;
+ CARD64 cur_msc;
+
+ target_msc &= 0xffffffff;
+ divisor &= 0xffffffff;
+ remainder &= 0xffffffff;
+
+ crtc = vivante_dri2_drawable_crtc(draw);
+
+ /* Drawable not displayed, just complete */
+ if (crtc < 0)
+ goto out;
+
+ wait = new_wait_info(client, draw, DRI2_WAITMSC);
+ if (!wait)
+ goto out;
+
+ /* Get current count */
+ ret = vivante_dri2_waitvblank(vivante, &vbl, crtc, __FUNCTION__);
+ if (ret)
+ goto out_free;
+
+ cur_msc = vbl.reply.sequence;
+
+ /*
+ * If the divisor is zero, or cur_msc is smaller than target_msc, we
+ * just need to make sure target_msc passes before waking up the client.
+ */
+ if (divisor == 0 || cur_msc < target_msc) {
+ if (cur_msc >= target_msc)
+ target_msc = cur_msc;
+
+ vbl.request.sequence = target_msc;
+ } else {
+ /*
+ * If we get here, target_msc has already passed or we
+ * don't have one, so queue an event that will satisfy
+ * the divisor/remainder equation.
+ */
+ vbl.request.sequence = cur_msc - (cur_msc % divisor) + remainder;
+
+ /*
+ * If calculated remainder is larger than requested
+ * remainder, it means we've passed the point where
+ * seq % divisor == remainder, so we need to wait for
+ * the next time that will happen.
+ */
+ if ((cur_msc & divisor) >= remainder)
+ vbl.request.sequence += divisor;
+ }
+
+ vbl.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT | drm_req_crtc(crtc);
+ vbl.request.signal = (unsigned long)wait;
+ ret = drmWaitVBlank(vivante->drm_fd, &vbl);
+ if (ret) {
+ xf86DrvMsg(vivante->scrnIndex, X_WARNING,
+ "%s: get vblank counter failed: %s\n",
+ __FUNCTION__, strerror(errno));
+ goto out_free;
+ }
+
+ wait->frame = vbl.reply.sequence;
+ DRI2BlockClient(client, draw);
+ return TRUE;
+
+ out_free:
+ del_wait_info(wait);
+ out:
+ DRI2WaitMSCComplete(client, draw, target_msc, 0, 0);
+ return TRUE;
+}
+
+static int dri2_server_generation;
+
+Bool vivante_dri2_ScreenInit(ScreenPtr pScreen, int drm_fd)
+{
+ struct vivante *vivante = vivante_get_screen_priv(pScreen);
+ struct vivante_dri2_info *dri;
+ DRI2InfoRec info;
+ int dri2_major = 0;
+ int dri2_minor = 0;
+ const char *driverNames[1];
+
+ if (xf86LoaderCheckSymbol("DRI2Version"))
+ DRI2Version(&dri2_major, &dri2_minor);
+
+ if (dri2_major < 1 || (dri2_major == 1 && dri2_minor < 1)) {
+ xf86DrvMsg(vivante->scrnIndex, X_WARNING,
+ "DRI2 requires DRI2 module version 1.1.0 or later\n");
+ return FALSE;
+ }
+
+ if (!vivante_dri2_register_private())
+ return FALSE;
+
+ if (dri2_server_generation != serverGeneration) {
+ dri2_server_generation = serverGeneration;
+
+ wait_client_restype = CreateNewResourceType(vivante_dri2_client_gone,
+ "Frame Event Client");
+ wait_drawable_restype = CreateNewResourceType(vivante_dri2_drawable_gone,
+ "Frame Event Drawable");
+
+ if (!wait_client_restype || !wait_drawable_restype) {
+ xf86DrvMsg(vivante->scrnIndex, X_WARNING,
+ "Can not register DRI2 frame event resources\n");
+ return FALSE;
+ }
+ }
+
+ dri = xnfcalloc(1, sizeof *dri);
+ dri->devname = drmGetDeviceNameFromFd(drm_fd);
+
+ vivante->dri2 = dri;
+ vivante->drm_fd = drm_fd;
+
+ memset(&info, 0, sizeof(info));
+ info.version = 4;
+ info.fd = drm_fd;
+ info.driverName = "galdri";
+ info.deviceName = dri->devname;
+
+ info.CreateBuffer = vivante_dri2_CreateBuffer;
+ info.DestroyBuffer = vivante_dri2_DestroyBuffer;
+ info.CopyRegion = vivante_dri2_CopyRegion;
+
+ info.ScheduleSwap = vivante_dri2_ScheduleSwap;
+ info.GetMSC = vivante_dri2_GetMSC;
+ info.ScheduleWaitMSC = vivante_dri2_ScheduleWaitMSC;
+ info.numDrivers = 1;
+ info.driverNames = driverNames;
+ driverNames[0] = info.driverName;
+
+ return DRI2ScreenInit(pScreen, &info);
+}
+
+void vivante_dri2_CloseScreen(int scrnIndex, ScreenPtr pScreen)
+{
+ struct vivante *vivante = vivante_get_screen_priv(pScreen);
+ struct vivante_dri2_info *dri = vivante->dri2;
+
+ if (dri) {
+ DRI2CloseScreen(pScreen);
+
+ vivante->dri2 = NULL;
+ drmFree(dri->devname);
+ free(dri);
+ }
+}